-
Notifications
You must be signed in to change notification settings - Fork 295
Description
Summary
PR #20301 fixed the activation job checkout for platform workflows that are called by a pure workflow_call relay (i.e. a relay whose outermost trigger is itself on: workflow_call). However, the fix does not cover event-driven relays — relays triggered by a native event (e.g. on: issue_comment, on: push, on: label) that then call the platform workflow via uses:. For these, github.event_name is never 'workflow_call' inside the called workflow, so the conditional expression introduced in #20301 always evaluates false and the activation job checks out the caller's repo instead of the platform repo.
inlined-imports: true remains the only working workaround for event-driven relay patterns.
Agent Analysis
This was traced from a live workflow run where the relay was triggered by an issue_comment event.
Root cause
When the outer relay fires on issue_comment and calls the platform workflow via uses::
# app-repo/.github/workflows/relay.yml
on:
issue_comment:
types: [created]
jobs:
relay:
uses: my-org/platform-repo/.github/workflows/gateway.lock.yml@main
with:
source_repo: ${{ github.repository }}
secrets: inheritGitHub propagates the original triggering event — issue_comment — through the workflow_call chain unchanged. Inside gateway.lock.yml, the GitHub Actions context variables are:
| Variable | Value |
|---|---|
github.event_name |
'issue_comment' — not 'workflow_call' |
github.repository |
'my-org/app-repo' (the caller) |
github.action_repository |
empty in run: steps |
The conditional from #20301, now emitted by generateCheckoutGitHubFolderForActivation():
repository: ${{ github.event_name == 'workflow_call' && github.action_repository || github.repository }}…evaluates to github.repository = 'my-org/app-repo'. The activation job clones the caller's repo. Any step that references .github/ files — runtime imports, prompt templates — fails immediately with ERR_SYSTEM: Runtime import file not found (or equivalent command not found / ENOENT).
Why github.event_name == 'workflow_call' only works in limited cases
github.event_name is set by the GitHub Actions runner to the event that triggered the outermost workflow in the chain. It is only 'workflow_call' when the relay file itself declares on: workflow_call as its trigger, e.g.:
# relay.yml — pure reusable workflow
on:
workflow_call:
inputs:
source_repo:
type: stringThis is the scenario validated by the smoke-workflow-call smoke test added in #20301. The test calls the gateway with on: workflow_call as the outermost event, so github.event_name is correctly 'workflow_call' inside the called workflow.
For every other relay pattern — the common, real-world deployment where a repo reacts to user events and calls the platform workflow — github.event_name propagates the native event. The fix from #20301 does not apply.
Affected code
pkg/workflow/compiler_activation_job.go—generateCheckoutGitHubFolderForActivation(), specifically the call:return cm.GenerateGitHubFolderCheckoutStep( "${{ github.event_name == 'workflow_call' && github.action_repository || github.repository }}", GetActionPin, )
pkg/workflow/compiler_activation_job_test.go— theworkflowCallRepoconstant encodes the broken expression and is asserted against; tests do not cover the event-driven relay scenariodocs/src/content/docs/patterns/central-repo-ops.mdx— the "How It Works" section documents the expression as correct without noting this limitation; the smoke test that validates the pattern uses a pureworkflow_callrelay which masks the gap
Proposed fix
Use github.workflow_ref as the cross-repo discriminator instead of github.event_name.
github.workflow_ref always contains the full owner/repo/.github/workflows/<file>@ref path of the workflow currently running, regardless of the triggering event. It is set by the runner based on which workflow file is executing, not on how that workflow was started.
When a platform workflow (my-org/platform-repo/.github/workflows/gateway.lock.yml) is called cross-repo:
github.repository='my-org/app-repo'(the caller)github.workflow_refstarts withmy-org/platform-repo/...
The platform repo slug is always the owner/repo prefix of github.workflow_ref (before /.github/). When github.repository does not match that prefix, the checkout should target the platform repo, not the caller.
Implementation options
Option A — Two-step: resolve then checkout (no compile-time knowledge required)
Emit a run: step before the checkout that parses GITHUB_WORKFLOW_REF and writes the target repo to GITHUB_OUTPUT:
- name: Resolve workflow host repo
id: resolve-host
run: |
HOST_REPO=$(echo "$GITHUB_WORKFLOW_REF" | cut -d'/' -f1,2)
if [[ "$HOST_REPO" != "$GITHUB_REPOSITORY" ]]; then
echo "target_repo=$HOST_REPO" >> "$GITHUB_OUTPUT"
else
echo "target_repo=$GITHUB_REPOSITORY" >> "$GITHUB_OUTPUT"
fi
env:
GITHUB_WORKFLOW_REF: ${{ github.workflow_ref }}
GITHUB_REPOSITORY: ${{ github.repository }}
- name: Checkout .github and .agents folders
uses: actions/checkout@...
with:
repository: ${{ steps.resolve-host.outputs.target_repo }}
sparse-checkout: |
.github
.agents
sparse-checkout-cone-mode: true
fetch-depth: 1
persist-credentials: falseThis is safe and correct across all relay patterns. The if [[ "$HOST_REPO" != "$GITHUB_REPOSITORY" ]] guard means same-repo invocations still use github.repository unmodified.
Option B — Inline expression using github.workflow_ref
If the compiler can emit the owner/repo portion of github.workflow_ref as a known literal at compile time (it can, via git remote get-url origin or equivalent), the checkout repository: value can be a constant:
repository: my-org/platform-repoThis avoids the run: step but requires the compiler to know the platform repo slug at compile time. This is already available via the source file path and git context when gh aw compile runs.
Implementation plan
-
pkg/workflow/compiler_activation_job.go—generateCheckoutGitHubFolderForActivation():- Replace the call to
GenerateGitHubFolderCheckoutStepwith the brokengithub.event_name == 'workflow_call' && github.action_repository || github.repositoryexpression - Emit a
run:step (Option A) that parsesGITHUB_WORKFLOW_REFintoGITHUB_OUTPUT, followed by a checkout step referencing${{ steps.resolve-host.outputs.target_repo }} - Alternatively, if the compiler has access to the platform repo slug literal, emit it directly (Option B)
- The
!data.InlinedImportsguard should remain — wheninlined-imports: true, no checkout is needed
- Replace the call to
-
pkg/workflow/compiler_activation_job_test.go:- Update
workflowCallRepoconstant or remove it and assert on the new step structure - Add a test case
"event-driven relay (issue_comment outermost trigger)"that verifies the checkout correctly targets the platform repo even whenevent_name != 'workflow_call' - Verify the generated lock file does not contain the
github.event_name == 'workflow_call'guard
- Update
-
pkg/workflow/activation_checkout_test.go(integration):- Add a test compiling a workflow with mixed
on: [issue_comment, workflow_call]triggers - Verify the activation job checkout does not rely on
github.event_name == 'workflow_call'
- Add a test compiling a workflow with mixed
-
docs/src/content/docs/patterns/central-repo-ops.mdx:- In the "How It Works" section, replace the description of the broken expression with the corrected mechanism
- Add a clear note: the
github.event_name == 'workflow_call'guard only works when the relay's outermost trigger ison: workflow_call. Event-driven relays (on: issue_comment,on: push, etc.) require a different approach - Document
inlined-imports: trueas the available workaround for event-driven relays until a fix is released
-
smoke-water.yml/ smoke test coverage:- Add a second smoke test where the relay is triggered by a label event (or simulate via
workflow_dispatchwith an explicitevent_nameoverride) so the CI catches regressions for event-driven relay patterns
- Add a second smoke test where the relay is triggered by a label event (or simulate via
Current workaround
Set inlined-imports: true in the platform workflow's frontmatter. This causes the compiler to bake all referenced .md content into the lock file at compile time, eliminating the need for the activation job to check out the platform repo's .github/ folder at runtime.
---
inlined-imports: true
on:
issue_comment:
types: [created]
workflow_call:
inputs: ...
---The trade-off: any change to imported markdown files requires recompilation and a new commit. Runtime edits to prompt files have no effect until recompiled.
Reproduction
- Create a platform workflow with
on: [issue_comment, workflow_call]; compile withgh aw compile --strict - In a separate repository, create an event-driven relay:
on: issue_comment: types: [created] jobs: relay: uses: my-org/platform-repo/.github/workflows/my-workflow.lock.yml@main with: source_repo: ${{ github.repository }} secrets: inherit
- Post an issue comment in the app repo to trigger the relay
- Observe the activation job checking out the app repo and failing on any step that references
.github/files from the platform repo
Expected: activation job checks out my-org/platform-repo's .github/ folder
Actual: activation job checks out my-org/app-repo; workflow fails with file-not-found errors
This reproduces on v0.57.2 with the fix from #20301 already applied.