Derive omitted GitHub App owners from effective checkout target#37976
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR extends gh-aw’s GitHub App token minting so that when github-app.owner is omitted, the installation owner can be derived from the workflow’s effective checkout.repository (including expression-based repositories via a generated runtime pre-step). This targets multi-org workflows where the target repo is only known at dispatch time.
Changes:
- Added owner-derivation logic (compile-time for literal
owner/repo, runtime pre-step for expression-based repositories). - Refactored GitHub App token step generation to accept an optional repository source for owner inference, and updated safe-outputs + GitHub MCP token minting to use stable step IDs directly.
- Added tests covering owner inference and ensuring key workflow builders emit the derivation pre-step.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/safe_outputs_jobs.go | Routes safe-output job GitHub App token minting through the repository-aware builder for owner inference. |
| pkg/workflow/safe_outputs_app_config.go | Refactors token mint step generation to resolve/derive owner (optionally via new pre-step). |
| pkg/workflow/github_app_owner_derivation.go | Introduces repository-to-owner derivation helpers and runtime owner extraction step generation. |
| pkg/workflow/github_app_owner_derivation_test.go | Adds unit tests for owner derivation and integration-style assertions for generated steps. |
| pkg/workflow/compiler_yaml_step_generation.go | Updates OTLP token mint step helper call signature. |
| pkg/workflow/compiler_safe_outputs_job.go | Uses repository-aware GitHub App token minting for consolidated safe-outputs job. |
| pkg/workflow/compiler_github_mcp_steps.go | Switches to explicit step IDs (no post-rewrite) and passes repository source for owner inference. |
| pkg/workflow/checkout_step_generator.go | Switches checkout app-token generation to use explicit step IDs and pass repository source for owner inference. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 8/8 changed files
- Comments generated: 2
| owner, ownerSteps := resolveGitHubAppOwner(app, ownerSourceRepository, stepName, stepID) | ||
| steps = append(steps, ownerSteps...) |
| func inferSingleCheckoutRepositoryForGitHubAppOwner(data *WorkflowData) string { | ||
| if data == nil { | ||
| return "" | ||
| } | ||
|
|
||
| checkoutMgr := NewCheckoutManager(data.CheckoutConfigs) | ||
| var repository string | ||
| for _, entry := range checkoutMgr.ordered { | ||
| if entry.key.repository == "" { | ||
| continue | ||
| } | ||
| if repository == "" { | ||
| repository = entry.key.repository | ||
| continue | ||
| } | ||
| if entry.key.repository != repository { | ||
| return "" | ||
| } | ||
| } | ||
|
|
||
| return repository | ||
| } |
|
🧪 Test Quality Sentinel completed test quality analysis. |
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
✅ PR Code Quality Reviewer completed the code quality review. |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🏗️ Design Decision Gate — ADR RequiredThis PR makes significant changes to core business logic (269 new lines under 📄 Draft ADR committed:
📋 What to do next
Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision. ❓ Why ADRs MatterADRs create a searchable, permanent record of why the codebase looks the way it does. This change introduces a non-obvious owner-derivation rule (single distinct checkout repo, compile-time literal vs. runtime pre-step, explicit-owner precedence) that future contributors will need the rationale for. 📋 Michael Nygard ADR Format ReferenceAn ADR must contain these four sections to be considered complete:
ADRs are stored in
|
🧪 Test Quality Sentinel Report✅ Test Quality Score: 85/100 — Excellent
📊 Metrics & Test Classification (2 tests analyzed)
Test Classification Details
Language SupportTests analyzed:
🚨 Flagged Items — Requires Review (2 issue(s))🚨
|
There was a problem hiding this comment.
❌ Test Quality Sentinel: 85/100. Coding-guideline violation detected. The new file pkg/workflow/github_app_owner_derivation_test.go is missing the mandatory //go:build !integration or //go:build integration build tag on line 1 (every other *_test.go in pkg/workflow/ carries this tag). Additionally, all assert.*/require.* calls in both test functions lack the required descriptive message argument. Please review the flagged items in the comment above.
There was a problem hiding this comment.
Incomplete migration of buildGitHubAppTokenMintStep callers
notify_comment.go:64 still calls buildGitHubAppTokenMintStep (which hard-codes ownerSourceRepository = ""), silently bypassing the cross-org owner derivation this PR introduces. Both safe_outputs_jobs.go and compiler_safe_outputs_job.go — which mint the same data.SafeOutputs.GitHubApp token — were updated; this one was not. For any multi-org workflow that uses the notify-comment feature, the minted token will target the wrong GitHub App installation. See inline comment on safe_outputs_app_config.go:213.
Other findings
Missing test coverage for the conflicting-checkout fallback
inferSingleCheckoutRepositoryForGitHubAppOwner returns "" for both no explicit checkouts and conflicting repos. Only the single-checkout path is tested. The silent github.repository_owner fallback on a multi-repo conflict has no test, making it easy to regress. See inline comment on github_app_owner_derivation_test.go:138.
repositoryExpr not whitespace-normalised before YAML embedding
The codebase applies githubExpressionWhitespaceReplacer before embedding expressions into generated YAML conditions; the new GH_AW_TARGET_REPOSITORY: %s line skips this. Low probability but inconsistent — see inline comment on github_app_owner_derivation.go:53.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
proxy.golang.org
To allow these domains, add them to the
network.allowedlist in your workflow frontmatter:
network:
allowed:
- defaults
- "proxy.golang.org"See Network Configuration for more information.
🔎 Code quality review by PR Code Quality Reviewer · ⌖ 14.1 AIC
| // owner/repo slug — actions/create-github-app-token expects repo names only when owner is also set). | ||
| func (c *Compiler) buildGitHubAppTokenMintStep(app *GitHubAppConfig, permissions *Permissions, fallbackRepoExpr string) []string { | ||
| return c.buildGitHubAppTokenMintStepWithMeta(app, permissions, fallbackRepoExpr, "Generate GitHub App token", "safe-outputs-app-token") | ||
| return c.buildGitHubAppTokenMintStepWithMeta(app, permissions, fallbackRepoExpr, "", "Generate GitHub App token", "safe-outputs-app-token") |
There was a problem hiding this comment.
notify_comment.go not updated: buildGitHubAppTokenMintStep (which passes ownerSourceRepository = "") still has three callers in unchanged files; notify_comment.go:64 appears to be an unintentional omission.
💡 Details
Both safe_outputs_jobs.go and compiler_safe_outputs_job.go were updated to call buildGitHubAppTokenMintStepForRepository with the inferred checkout repository, enabling cross-org owner derivation. However, notify_comment.go line 64 mints the same data.SafeOutputs.GitHubApp token through the old path:
// notify_comment.go:64 — still uses empty ownerSourceRepository
steps = append(steps, c.buildGitHubAppTokenMintStep(data.SafeOutputs.GitHubApp, permissions, appTokenFallbackRepo)...)For multi-org workflows where checkout.repository belongs to a different org, the notify-comment job will mint a token scoped to ${{ github.repository_owner }} (the workflow repo org) instead of the checkout repo org. The token will be valid but targeted at the wrong installation, causing a 404 from actions/create-github-app-token or silently using a fallback installation.
Suggested fix: mirror the same change made to safe_outputs_jobs.go:
steps = append(steps, c.buildGitHubAppTokenMintStepForRepository(
data.SafeOutputs.GitHubApp,
permissions,
appTokenFallbackRepo,
inferSingleCheckoutRepositoryForGitHubAppOwner(data),
)...)The two other unchanged callers (create_code_scanning_alert.go with the default checkout app and compiler_safe_outputs_steps.go with CreateCheckRun.GitHubApp) are likely intentional exclusions, but notify_comment.go is not.
| assert.Contains(t, safeOutputsSteps, "id: safe-outputs-app-token-owner") | ||
| assert.Contains(t, safeOutputsSteps, "owner: ${{ steps.safe-outputs-app-token-owner.outputs.owner }}") | ||
| assert.Contains(t, safeOutputsSteps, "GH_AW_TARGET_REPOSITORY: ${{ github.event.inputs.trigger_ref }}") | ||
| } |
There was a problem hiding this comment.
Missing test for conflicting-checkout fallback: inferSingleCheckoutRepositoryForGitHubAppOwner returns "" when multiple checkouts target different repositories, silently falling back to ${{ github.repository_owner }} — but this critical path has no test coverage.
💡 Suggested test case
Add to TestWorkflowBuildersDeriveGitHubAppOwnerFromCheckoutRepository (or a dedicated unit test for inferSingleCheckoutRepository...):
// conflicting repos → fall back to github.repository_owner
conflictData := &WorkflowData{
CheckoutConfigs: []*CheckoutConfig{
{Repository: "org-a/repo-1", GitHubApp: &GitHubAppConfig{...}},
{Repository: "org-b/repo-2", GitHubApp: &GitHubAppConfig{...}},
},
...
}
assert.Equal(t, "", inferSingleCheckoutRepositoryForGitHubAppOwner(conflictData))
// and verify the compiled step uses ${{ github.repository_owner }} as ownerAlso worth adding:
- Same repo repeated: two checkouts with identical
repositoryvalues should still derive the literal owner. - No explicit checkouts (all
repository == ""): should return""→ fallback togithub.repository_owner.
Without these cases, a future refactor of inferSingleCheckoutRepositoryForGitHubAppOwner could accidentally change the fallback behaviour (e.g. returning the first repo instead of "") without any test failing.
| steps = append(steps, fmt.Sprintf(" - name: %s\n", ownerStepName)) | ||
| steps = append(steps, fmt.Sprintf(" id: %s\n", ownerStepID)) | ||
| steps = append(steps, " env:\n") | ||
| steps = append(steps, fmt.Sprintf(" GH_AW_TARGET_REPOSITORY: %s\n", repositoryExpr)) |
There was a problem hiding this comment.
Unescaped repositoryExpr injected into YAML env block: repositoryExpr is embedded into the generated YAML without whitespace normalisation, inconsistent with how githubExpressionWhitespaceReplacer is used elsewhere in this package for the same pattern.
💡 Details
safe_outputs_app_config.go already defines githubExpressionWhitespaceReplacer to strip \r\n, \n, \r, \t from expressions before they are embedded in YAML conditions (see buildIgnoreIfMissingCondition). The same normalisation is not applied here:
// current — raw expression injected verbatim
steps = append(steps, fmt.Sprintf(" GH_AW_TARGET_REPOSITORY: %s\n", repositoryExpr))A repositoryExpr value containing a newline (e.g. from a multi-line GitHub expression, or a workflow author accidentally adding one) would break the YAML block structure by injecting additional keys into the env: map.
Suggested fix — normalise before embedding:
cleanExpr := githubExpressionWhitespaceReplacer.Replace(repositoryExpr)
steps = append(steps, fmt.Sprintf(" GH_AW_TARGET_REPOSITORY: %s\n", cleanExpr))This is a low-probability event in practice (YAML scalars in source workflows cannot contain literal newlines), but the inconsistency with the established codebase pattern is worth fixing.
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /tdd, /zoom-out, /grill-with-docs, and /improve-codebase-architecture — requesting changes primarily on a correctness concern in the multi-repo ambiguity path and test coverage gaps.
📋 Key Themes & Highlights
Blocking
- Silent wrong-owner fallback on multi-repo ambiguity (
github_app_owner_derivation.go:28): when multiple distinct checkout repositories are configured,inferSingleCheckoutRepositoryForGitHubAppOwnerreturns""and the caller silently mints a token forgithub.repository_owner— potentially the wrong org for cross-org workflows.
Test Coverage Gaps (/tdd)
- No test for
ownerSourceRepo: ""(empty →github.repository_ownerfallback) - No test for multi-distinct-repos case (boundary condition of
inferSingleCheckoutRepositoryForGitHubAppOwner) - No test for bare repo name without org prefix (e.g.,
"just-a-repo") .wikisuffix stripping in the bash pre-step is untestedbuildSafeOutputsJobFromPartspath not exercised (onlybuildConsolidatedSafeOutputsJobis tested)- Step ordering (owner-derivation step before mint step) not validated by any assertion
Architecture / Style (/improve-codebase-architecture, /grill-with-docs)
buildGitHubAppTokenMintStepForRepositoryadds a third wrapper level without meaningful abstraction- Owner-step name is derived via a fragile hard-coded string replacement
Positive Highlights
- ✅ Clean refactor replacing string post-processing (
strings.ReplaceAllon YAML output) with direct parameter passing - ✅ Good env-variable isolation in the bash pre-step (
GH_AW_TARGET_REPOSITORY) mitigates injection risk - ✅ Explicit owner still takes precedence — backward-compatible
- ✅ Three derivation paths (literal, expression, absent) are well-structured and independently documented
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 555.5 AIC · ⌖ 26 AIC
| continue | ||
| } | ||
| if entry.key.repository != repository { | ||
| return "" |
There was a problem hiding this comment.
[/zoom-out] Silent fallback on multi-repo ambiguity may produce the wrong token owner — the multi-checkout cross-org case this PR enables.
💡 Analysis
inferSingleCheckoutRepositoryForGitHubAppOwner returns "" for two distinct situations:
- No explicit checkout repository configured → fallback to
${{ github.repository_owner }}is correct. - Multiple distinct checkout repos configured → also returns
"", silently using the caller's repo owner.
Case 2 is the crux: checkout: [{repository: org-a/repo}, {repository: org-b/repo}] mints a token for the calling repo's org instead of either target. Consider returning a distinct sentinel or emitting a compile-time diagnostic so this ambiguity is surfaced rather than silently swallowed.
| } | ||
|
|
||
| ownerStepID := stepID + "-owner" | ||
| ownerStepName := strings.Replace(stepName, "Generate GitHub App token", "Derive GitHub App owner", 1) |
There was a problem hiding this comment.
[/grill-with-docs] The owner-step name is derived by replacing a hard-coded magic string, creating a hidden coupling to the calling step's name format.
💡 Suggestion
ownerStepName := strings.Replace(stepName, "Generate GitHub App token", "Derive GitHub App owner", 1)
if ownerStepName == stepName {
ownerStepName = "Derive GitHub App owner"
}This silently falls back to a generic name whenever the parent step name doesn't contain the magic string — making it fragile to caller-side renames. Consider accepting a ownerStepName parameter directly (parallel to ownerStepID), or defining the name as a constant so the dependency is explicit.
| expectedOwner: "owner: ${{ steps.safe-outputs-app-token-owner.outputs.owner }}", | ||
| expectedContains: "id: safe-outputs-app-token-owner", | ||
| }, | ||
| } |
There was a problem hiding this comment.
[/tdd] Missing test case for ownerSourceRepo: "" — no test verifies that a blank owner-source falls back to ${{ github.repository_owner }} and emits no pre-step.
💡 Suggested addition to the table
{
name: "no owner source falls back to github.repository_owner",
app: &GitHubAppConfig{
AppID: "${{ vars.APP_ID }}",
PrivateKey: "${{ secrets.APP_PRIVATE_KEY }}",
},
ownerSourceRepo: "",
expectedOwner: "owner: ${{ github.repository_owner }}",
unexpectedContains: "-owner",
},This baseline case documents the default behavior and catches any regression where an empty ownerSourceRepository accidentally triggers a pre-step.
| "Generate GitHub App token", | ||
| "safe-outputs-app-token", | ||
| ) | ||
| stepsStr := strings.Join(steps, "") |
There was a problem hiding this comment.
[/tdd] Tests join all steps into a flat string and assert substring presence, so they cannot detect wrong step ordering. The owner-derivation step must appear before the token-mint step — a regression swapping the order would go undetected.
💡 Suggestion
Assert on the slice directly before joining, or use strings.Index to compare positions:
stepsStr := strings.Join(steps, "")
// Verify order: owner derivation step precedes the mint step
ownerIdx := strings.Index(stepsStr, "id: safe-outputs-app-token-owner")
mintIdx := strings.Index(stepsStr, "id: safe-outputs-app-token\n")
require.Greater(t, mintIdx, ownerIdx, "owner derivation step must precede token mint step")| } | ||
| } | ||
|
|
||
| func TestWorkflowBuildersDeriveGitHubAppOwnerFromCheckoutRepository(t *testing.T) { |
There was a problem hiding this comment.
[/tdd] TestWorkflowBuildersDeriveGitHubAppOwnerFromCheckoutRepository combines three independent concerns (checkout, MCP, safe-outputs) into one test function. A failure in any one of them obscures where the problem is.
💡 Suggestion
Split into three focused sub-tests or table entries:
TestCheckoutAppTokenStepsDerivesOwnerFromRepositoryTestMCPAppTokenStepsDerivesOwnerFromRepositoryTestSafeOutputsAppTokenStepsDerivesOwnerFromRepository
Each can share the same data fixture but assert independently, giving precise failure messages and making it easy to add edge-case variants per path.
| assert.Contains(t, mcpSteps, "owner: ${{ steps.github-mcp-app-token-owner.outputs.owner }}") | ||
| assert.Contains(t, mcpSteps, "GH_AW_TARGET_REPOSITORY: ${{ github.event.inputs.trigger_ref }}") | ||
|
|
||
| job, _, err := compiler.buildConsolidatedSafeOutputsJob(data, string(constants.AgentJobName), "test.md") |
There was a problem hiding this comment.
[/tdd] buildConsolidatedSafeOutputsJob is tested, but buildSafeOutputsJobFromParts (in compiler_safe_outputs_job.go) is a separate code path that also calls buildGitHubAppTokenMintStepForRepository. It has different appTokenFallbackRepo logic for workflow_call triggers and is not exercised here.
💡 Suggestion
Add a test that drives buildSafeOutputsJobFromParts (or a higher-level caller that routes through it) with a workflow_call trigger and an explicit checkout repository, and assert that the inferred owner is emitted correctly. This closes the gap between the two safe-outputs compilation paths.
| PrivateKey: "${{ secrets.APP_PRIVATE_KEY }}", | ||
| }, | ||
| ownerSourceRepo: "acme/project", | ||
| expectedOwner: "owner: acme", |
There was a problem hiding this comment.
[/tdd] Missing test for a bare repository name without an org prefix (e.g., ownerSourceRepo: "just-a-repo"). The bash pre-step correctly guards against this with [[ "$owner" == "$repo" ]], but the behavior is not specified by any test.
💡 Suggested test case
{
name: "bare repo name without slash emits pre-step that will fail at runtime",
app: &GitHubAppConfig{
AppID: "${{ vars.APP_ID }}",
PrivateKey: "${{ secrets.APP_PRIVATE_KEY }}",
},
ownerSourceRepo: "just-a-repo",
// deriveLiteralGitHubAppOwner returns false (no slash), so a pre-step is emitted
expectedContains: "id: safe-outputs-app-token-owner",
expectedContains: "GH_AW_TARGET_REPOSITORY: just-a-repo",
},This documents that a non-owner/repo literal triggers a runtime step (which will fail with a clear message), rather than silently emitting a wrong owner.
| } | ||
|
|
||
| func (c *Compiler) buildGitHubAppTokenMintStepWithMeta(app *GitHubAppConfig, permissions *Permissions, fallbackRepoExpr string, stepName string, stepID string) []string { | ||
| func (c *Compiler) buildGitHubAppTokenMintStepForRepository(app *GitHubAppConfig, permissions *Permissions, fallbackRepoExpr string, ownerSourceRepository string) []string { |
There was a problem hiding this comment.
[/improve-codebase-architecture] buildGitHubAppTokenMintStepForRepository is a thin wrapper that differs from buildGitHubAppTokenMintStep only by the ownerSourceRepository argument, making the API surface larger without adding clarity.
💡 Suggestion
The two wrappers could be merged by making ownerSourceRepository a parameter of buildGitHubAppTokenMintStep (replacing the old signature that always passed ""), or by collapsing buildGitHubAppTokenMintStepForRepository back into a direct call to buildGitHubAppTokenMintStepWithMeta at its call sites. Either way, having three levels (WithMeta, ForRepository, plain) for what is essentially one operation with optional arguments increases cognitive overhead.
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
[/tdd] No test exercises the multi-distinct-repos path where inferSingleCheckoutRepositoryForGitHubAppOwner returns "" due to ambiguity (two different repositories configured). This is a boundary condition of the new derivation logic.
💡 Suggested test
func TestInferSingleCheckoutRepositoryMultipleDistinctRepos(t *testing.T) {
data := &WorkflowData{
CheckoutConfigs: []*CheckoutConfig{
{Repository: "org-a/repo"},
{Repository: "org-b/other"},
},
}
got := inferSingleCheckoutRepositoryForGitHubAppOwner(data)
assert.Equal(t, "", got, "multiple distinct repos should return empty string")
}Without this, the documented behavior of the function (returns "" for ambiguous case) is unspecified by tests, and the silent fallback to ${{ github.repository_owner }} in that scenario is unverified.
| steps = append(steps, " shell: bash\n") | ||
| steps = append(steps, " run: |\n") | ||
| steps = append(steps, " set -euo pipefail\n") | ||
| steps = append(steps, " repo=\"${GH_AW_TARGET_REPOSITORY%.wiki}\"\n") |
There was a problem hiding this comment.
[/tdd] The .wiki suffix stripping (${GH_AW_TARGET_REPOSITORY%.wiki}) is implemented but not tested. A future change to this line could silently break derivation for wiki repositories.
💡 Suggested test case
Add a table entry in TestBuildGitHubAppTokenMintStepOwnerDerivation:
{
name: "wiki repository expression emits pre-step with wiki stripped",
app: &GitHubAppConfig{
AppID: "${{ vars.APP_ID }}",
PrivateKey: "${{ secrets.APP_PRIVATE_KEY }}",
},
ownerSourceRepo: "${{ inputs.wiki_repo }}",
expectedContains: "GH_AW_TARGET_REPOSITORY%.wiki",
},Alternatively, test the bash step content directly for the presence of the .wiki stripping line when a non-literal source is used.
|
@copilot run pr-finisher skill |
Ran the PR finisher pass on HEAD
|
|
@copilot review all review comments from GitHub-actions bot |
|
@copilot do a security analysis for this change to determine if it enables some malicious scenarios or confusion. Ensure extensive logging. |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Reviewed all current GitHub-actions bot review comments and pushed follow-up fixes in commits Implemented:
Validation: |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Implemented in commits Security analysis outcome: owner derivation now has stronger auditability around confusion/misrouting risks (ambiguous checkout sources, workflow_call fallback source, expression-based runtime derivation). Added extensive logging at both compile time and runtime:
Validation run: targeted workflow owner-derivation tests passed, full lint/test/build suite had passed on this branch before these final log-only refinements, and parallel validation returned no actionable security findings. |
When a workflow targets repositories across multiple orgs,
github-app.ownercould only be a static literal, which made app-token minting fail for dispatch-timeowner/repotargets. This change teaches gh-aw to derive the installation owner from the workflow’s effective checkout target whenowner:is omitted, so multi-org workflows no longer need a redundanttrigger_orginput.What changed
github-app.owneris omitted.workflow_callworkflows with no explicitcheckout.repository, owner derivation now falls back to${{ needs.activation.outputs.target_repo }}.Where derivation now applies
checkout[*].github-appsafe-outputs.github-app(including conclusion/notify-comment token minting)tools.github.github-appDerivation behavior
owner/repovalues, gh-aw emits the owner directly at compile time.actions/create-github-app-token.github-app.ownerstill takes precedence unchanged.Implementation notes
Tests
${{ github.repository_owner }}${{ needs.activation.outputs.target_repo }}.wikisuffix handling in runtime derivationExample
This compiles to generated token-minting steps that derive
ownerfrom the effective target repository rather than defaulting togithub.repository_owner.