Version: gh-aw v0.72.1
Symptom
Every consumer workflow_call dispatch against a private gh-aw callee fails at the lock.yml's activation step with:
Cross-repo invocation detected: workflow source is "<callee>",
current repo is "<caller>"
GET /repos/<callee>/contents/.github/workflows/<chore>.lock.yml
?ref=<callee-ref-sha> — 404 Not Found
Could not fetch content for .github/workflows/<chore>.lock.yml: Not Found
Unable to fetch lock file content for hash comparison via API,
trying local filesystem fallback
Local lock file not found: …/work/<caller>/<caller>/.github/workflows/<chore>.lock.yml
##[warning]Could not compare frontmatter hashes — assuming lock file is outdated
##[error]ERR_CONFIG: Lock file '.github/workflows/<chore>.lock.yml' is outdated
or unverifiable! Could not verify frontmatter hash for
'.github/workflows/<chore>.md'. Run 'gh aw compile' to regenerate the lock file.
The check_workflow_timestamp_api.cjs activation step then sets stale_lock_file_failed=true, and the conclusion job marks the whole run failed before the agent ever runs.
Concrete reproducer: gominimal/min-ctl run 25906428806 calls gominimal/min-aw/.github/workflows/worker-fix.lock.yml@v0.6.3. Both repos private, same org. Fails as above on every dispatch.
Root cause
check_workflow_timestamp_api.cjs issues the Contents API call against the callee repo using GITHUB_TOKEN from the caller's runner. For a private callee:
GITHUB_TOKEN on a workflow run is repo-scoped to the running repo (the caller, e.g. gominimal/min-ctl).
- The Contents API on a private repo requires read access to that specific repo.
gominimal/min-ctl's GITHUB_TOKEN has no scope on gominimal/min-aw → 404, even though both repos are in the same org and workflow_call itself was already authorized via repo-actions-access settings.
This works fine when the callee is public (elastic/ai-github-actions is public; their consumers don't see this) — public Contents API doesn't require auth. The gap is private-callee-private-caller.
Why the existing fallbacks don't help
- API path uses
GITHUB_TOKEN → 404 for private callees.
GH_AW_GITHUB_TOKEN fallback secret slot is declared optional in the lock.yml's workflow_call.secrets. Consumers can provide a token here, but the documentation/templates don't make this clear, and there's no practical way to mint a short-lived cross-repo token without long-lived PAT storage (security cost).
- Local filesystem fallback looks for the lock.yml on the caller's checkout. Consumers using the wrapper distribution pattern don't ship
.lock.yml files locally (that's the point — thin wrappers).
Proposed fix
The lock.yml already receives APP_PRIVATE_KEY via workflow_call.secrets (used for safe-output writes via actions/create-github-app-token). If the corresponding GitHub App is installed on the callee repo as well, an App-minted installation token has cross-repo Contents read access.
check_workflow_timestamp_api.cjs should:
- Detect cross-repo invocation (it already does — emits "Cross-repo invocation detected" log).
- Detect that the initial Contents API call returned 404 / 401 / 403.
- Mint a fresh installation token using
APP_PRIVATE_KEY + vars.APP_ID (both already available to the activation step), scoped to the callee repo.
- Retry the Contents API call with the minted token.
- If the mint fails (no APP_PRIVATE_KEY in secrets, App not installed on callee, etc.), fall through to today's behavior with a clearer error message: "Configure GH_AW_GITHUB_TOKEN with cross-repo Contents read access, or install the gh-aw App on the callee repo."
Alternative if internal mint is too invasive: emit a clearer error directing consumers to set up GH_AW_GITHUB_TOKEN. The current message ("outdated or unverifiable") sends users toward gh aw compile, which doesn't address the auth gap.
Workaround for current users
Ship .lock.yml mirrors on the consumer side alongside .md mirrors. The filesystem fallback then succeeds. Trade-off: ~80–90 KB per chore on the consumer (defeats the thin-wrapper goal). See gominimal/min-ctl#66 for the workaround in practice.
Related
Version: gh-aw v0.72.1
Symptom
Every consumer workflow_call dispatch against a private gh-aw callee fails at the lock.yml's
activationstep with:The check_workflow_timestamp_api.cjs activation step then sets
stale_lock_file_failed=true, and the conclusion job marks the whole run failed before the agent ever runs.Concrete reproducer: gominimal/min-ctl run 25906428806 calls
gominimal/min-aw/.github/workflows/worker-fix.lock.yml@v0.6.3. Both repos private, same org. Fails as above on every dispatch.Root cause
check_workflow_timestamp_api.cjsissues the Contents API call against the callee repo usingGITHUB_TOKENfrom the caller's runner. For a private callee:GITHUB_TOKENon a workflow run is repo-scoped to the running repo (the caller, e.g.gominimal/min-ctl).gominimal/min-ctl'sGITHUB_TOKENhas no scope ongominimal/min-aw→ 404, even though both repos are in the same org and workflow_call itself was already authorized via repo-actions-access settings.This works fine when the callee is public (
elastic/ai-github-actionsis public; their consumers don't see this) — public Contents API doesn't require auth. The gap is private-callee-private-caller.Why the existing fallbacks don't help
GITHUB_TOKEN→ 404 for private callees.GH_AW_GITHUB_TOKENfallback secret slot is declared optional in the lock.yml'sworkflow_call.secrets. Consumers can provide a token here, but the documentation/templates don't make this clear, and there's no practical way to mint a short-lived cross-repo token without long-lived PAT storage (security cost)..lock.ymlfiles locally (that's the point — thin wrappers).Proposed fix
The lock.yml already receives
APP_PRIVATE_KEYviaworkflow_call.secrets(used for safe-output writes viaactions/create-github-app-token). If the corresponding GitHub App is installed on the callee repo as well, an App-minted installation token has cross-repo Contents read access.check_workflow_timestamp_api.cjsshould:APP_PRIVATE_KEY+vars.APP_ID(both already available to the activation step), scoped to the callee repo.Alternative if internal mint is too invasive: emit a clearer error directing consumers to set up
GH_AW_GITHUB_TOKEN. The current message ("outdated or unverifiable") sends users towardgh aw compile, which doesn't address the auth gap.Workaround for current users
Ship
.lock.ymlmirrors on the consumer side alongside.mdmirrors. The filesystem fallback then succeeds. Trade-off: ~80–90 KB per chore on the consumer (defeats the thin-wrapper goal). See gominimal/min-ctl#66 for the workaround in practice.Related