Summary
When a single GitHub App is installed across multiple organizations and a workflow operates on a target repository whose org is supplied at dispatch time (e.g. via workflow_dispatch input trigger_ref: owner/repo), gh-aw currently forces github-app.owner to be a compile-time literal. There is no ergonomic way to scope the minted token to the target's org installation, because:
actions/create-github-app-token mints one token per installation, and each org is a separate installation. Cross-org tokens don't exist.
- gh-aw injects the
Generate GitHub App token step before any user steps: run, so a bash pre-step cannot compute owner and expose it.
- GitHub Actions expressions don't include a string-split function, so
${{ inputs.trigger_ref }} (e.g. acme/foo) cannot be reduced to just the org part (acme) inside the generated with.owner value.
The net effect: workflows that want to be re-usable across multiple App-installation owners either hardcode a single owner: (and silently fail for everything else), or have to introduce a redundant trigger_org input alongside trigger_ref and plumb it through every dispatcher and dispatched workflow — purely as a workaround for (2) and (3) above.
Use case
A central "ops" repo that runs evaluation / triage / reporting workflows against target repositories spread across multiple GitHub organizations, where:
- The GitHub App has been installed in each org.
- The target repo is provided at dispatch time as a single
owner/repo input.
- The workflow needs:
checkout of the target repo (target-org-scoped token).
tools.github (GitHub MCP) reading from the target repo.
safe-outputs writing back to the target repo (issues, PRs, comments).
All three of those require a token minted from the target's App installation, not the workflow repo's org installation.
Current workaround (what we'd like to retire)
- Add a
trigger_org workflow input alongside trigger_ref (default <some-fallback> for backward compat).
- Every dispatcher computes
trigger_org="${trigger_ref%%/*}" and passes both inputs.
- Every consumer workflow references
owner: ${{ github.event.inputs.trigger_org || 'fallback-org' }} in:
tools.github.github-app.owner
safe-outputs.github-app.owner
checkout[*].github-app.owner
- Every
actions/create-github-app-token pre-step in non-gh-aw workflows mints one token per known org and selects by repo prefix at runtime.
Concerns with this workaround:
trigger_org is mechanically derivable from trigger_ref — it's a redundant input that has to be threaded through every workflow in the chain (orchestrator → area agents → topic specialists → aggregator → re-dispatch helpers).
- Drift risk: forgetting to pass
trigger_org on one dispatch silently falls back to the default org, producing 404s when the App tries to read the target.
- Documentation tax: every workflow's input contract grows by one field that exists only to work around this gap.
Proposed solutions (in priority order)
1. Auto-derive github-app.owner from checkout.repository
When a workflow has both a checkout entry with repository: <expr-or-literal> and a sibling github-app: block whose owner: is omitted, gh-aw could synthesize the owner at compile time as the part of repository before the /.
checkout:
- repository: ${{ github.event.inputs.trigger_ref }}
github-app:
client-id: ${{ secrets.MY_APP_ID }}
private-key: ${{ secrets.MY_APP_KEY }}
# owner: omitted — gh-aw derives "acme" from inputs.trigger_ref at runtime
repositories: ["*"]
Same idea for the top-level tools.github.github-app and safe-outputs.github-app — if their owner: is omitted and the workflow has a single checkout.repository, derive from that.
This is the cleanest path because it requires no new schema surface — just smarter defaulting.
Implementation sketch: gh-aw emits the Generate GitHub App token step with something like:
with:
owner: ${{ fromJSON(toJSON(github.event.inputs.trigger_ref)) }} # …or similar
…but since Actions expressions have no split, the practical emit would be a tiny inline shell step that exports OWNER to $GITHUB_OUTPUT, then the actions/create-github-app-token step references it via ${{ steps.derive-owner.outputs.owner }}. The inline step would be generated by gh-aw, not the user.
2. Support an explicit expression-friendly computed owner
Allow github-app.owner to be set to a small set of well-known computed values:
github-app:
client-id: ${{ secrets.MY_APP_ID }}
private-key: ${{ secrets.MY_APP_KEY }}
owner: from-repository # synthesized from sibling repository:
# or
owner: from-input.trigger_ref # synthesized from workflow input
Less elegant than (1) but more discoverable, and unambiguous when there are multiple checkout entries with different repository: values.
3. Allow a user pre-step to run before gh-aw's token-minting step
Today, user steps: run after the gh-aw-injected Generate GitHub App token for checkout (0) step, which means a pre-step cannot influence owner. A new pre-steps: (or similar) hook that runs before the token step would let users derive owner themselves and pass it via $GITHUB_OUTPUT:
pre-steps:
- name: Derive owner
id: derive
run: echo "owner=${TRIGGER_REF%%/*}" >> "$GITHUB_OUTPUT"
env:
TRIGGER_REF: ${{ github.event.inputs.trigger_ref }}
checkout:
- repository: ${{ github.event.inputs.trigger_ref }}
github-app:
owner: ${{ steps.derive.outputs.owner }}
This is the most general solution but the broadest schema change.
4. Add a tiny string-split helper to gh-aw's expression evaluator
If gh-aw already does any expression rewriting at compile time, exposing something like ${{ aw.split(inputs.trigger_ref, '/')[0] }} (rendered into a shell step or Actions-script) would unblock this with minimal new surface.
Acceptance criteria
- A single workflow file can operate against target repositories whose orgs are not known at compile time, given that the App is installed in each org.
- Users do not need to declare a redundant
trigger_org-style input.
- The change is backward-compatible: existing workflows with a literal
owner: <org> continue to work unchanged.
Out of scope / not asking for
- Cross-org tokens from
actions/create-github-app-token (upstream Actions limitation).
- Automatic detection of which orgs the App is installed in (deployment-time concern).
- Changes to the workflow input contract beyond what's strictly needed for the derivation.
Summary
When a single GitHub App is installed across multiple organizations and a workflow operates on a target repository whose org is supplied at dispatch time (e.g. via
workflow_dispatchinputtrigger_ref: owner/repo), gh-aw currently forcesgithub-app.ownerto be a compile-time literal. There is no ergonomic way to scope the minted token to the target's org installation, because:actions/create-github-app-tokenmints one token per installation, and each org is a separate installation. Cross-org tokens don't exist.Generate GitHub App tokenstep before any usersteps:run, so a bash pre-step cannot computeownerand expose it.${{ inputs.trigger_ref }}(e.g.acme/foo) cannot be reduced to just the org part (acme) inside the generatedwith.ownervalue.The net effect: workflows that want to be re-usable across multiple App-installation owners either hardcode a single
owner:(and silently fail for everything else), or have to introduce a redundanttrigger_orginput alongsidetrigger_refand plumb it through every dispatcher and dispatched workflow — purely as a workaround for (2) and (3) above.Use case
A central "ops" repo that runs evaluation / triage / reporting workflows against target repositories spread across multiple GitHub organizations, where:
owner/repoinput.checkoutof the target repo (target-org-scoped token).tools.github(GitHub MCP) reading from the target repo.safe-outputswriting back to the target repo (issues, PRs, comments).All three of those require a token minted from the target's App installation, not the workflow repo's org installation.
Current workaround (what we'd like to retire)
trigger_orgworkflow input alongsidetrigger_ref(default<some-fallback>for backward compat).trigger_org="${trigger_ref%%/*}"and passes both inputs.owner: ${{ github.event.inputs.trigger_org || 'fallback-org' }}in:tools.github.github-app.ownersafe-outputs.github-app.ownercheckout[*].github-app.owneractions/create-github-app-tokenpre-step in non-gh-aw workflows mints one token per known org and selects by repo prefix at runtime.Concerns with this workaround:
trigger_orgis mechanically derivable fromtrigger_ref— it's a redundant input that has to be threaded through every workflow in the chain (orchestrator → area agents → topic specialists → aggregator → re-dispatch helpers).trigger_orgon one dispatch silently falls back to the default org, producing 404s when the App tries to read the target.Proposed solutions (in priority order)
1. Auto-derive
github-app.ownerfromcheckout.repositoryWhen a workflow has both a
checkoutentry withrepository: <expr-or-literal>and a siblinggithub-app:block whoseowner:is omitted, gh-aw could synthesize the owner at compile time as the part ofrepositorybefore the/.Same idea for the top-level
tools.github.github-appandsafe-outputs.github-app— if theirowner:is omitted and the workflow has a singlecheckout.repository, derive from that.This is the cleanest path because it requires no new schema surface — just smarter defaulting.
Implementation sketch: gh-aw emits the
Generate GitHub App tokenstep with something like:…but since Actions expressions have no
split, the practical emit would be a tiny inline shell step that exportsOWNERto$GITHUB_OUTPUT, then theactions/create-github-app-tokenstep references it via${{ steps.derive-owner.outputs.owner }}. The inline step would be generated by gh-aw, not the user.2. Support an explicit expression-friendly computed
ownerAllow
github-app.ownerto be set to a small set of well-known computed values:Less elegant than (1) but more discoverable, and unambiguous when there are multiple
checkoutentries with differentrepository:values.3. Allow a user pre-step to run before gh-aw's token-minting step
Today, user
steps:run after the gh-aw-injectedGenerate GitHub App token for checkout (0)step, which means a pre-step cannot influenceowner. A newpre-steps:(or similar) hook that runs before the token step would let users deriveownerthemselves and pass it via$GITHUB_OUTPUT:This is the most general solution but the broadest schema change.
4. Add a tiny string-split helper to gh-aw's expression evaluator
If gh-aw already does any expression rewriting at compile time, exposing something like
${{ aw.split(inputs.trigger_ref, '/')[0] }}(rendered into a shell step or Actions-script) would unblock this with minimal new surface.Acceptance criteria
trigger_org-style input.owner: <org>continue to work unchanged.Out of scope / not asking for
actions/create-github-app-token(upstream Actions limitation).