Skip to content

on.github-token not propagated to checkout and hash check steps in activation job (breaks cross-org workflow_call) #26043

@bbonafed

Description

@bbonafed

Problem

When a reusable workflow is called cross-org via workflow_call, the activation job's "Checkout .github and .agents folders" step and "Check workflow lock file" step both use the default GITHUB_TOKEN. Since GITHUB_TOKEN is scoped to the caller's repo, it cannot access the callee's repo contents cross-org.

The on.github-token frontmatter field exists specifically for cross-org scenarios — it's already wired to resolveActivationToken() and correctly propagated to the reaction, status comment, and label removal steps. However, the checkout and hash check steps don't use it.

Cascading failure

  1. Sparse checkout fails silentlyactions/checkout has no token: override, so it can't clone the callee's .github folder cross-org.
  2. Hash check API returns 404check_workflow_timestamp_api.cjs uses the default token, which can't read the callee repo via API.
  3. Local filesystem fallback fails — falls back to $GITHUB_WORKSPACE, but the callee's files aren't there (because step 1 failed).
  4. Result: ERR_CONFIG: Lock file is outdated or unverifiable — false positive that blocks the workflow.

Observed behavior (debug logs)

Cross-repo invocation detected: workflow source is "<callee-org>/<callee-repo>",
  current repo is "<caller-org>/<caller-repo>"
GET /repos/<callee-org>/<callee-repo>/contents/.github/workflows/<name>.lock.yml - 404
Unable to fetch lock file content for hash comparison via API, trying local filesystem fallback
Local lock file not found: /home/runner/work/<caller-repo>/<caller-repo>/.github/workflows/<name>.lock.yml
Warning: Could not compare frontmatter hashes - assuming lock file is outdated
Error: ERR_CONFIG: Lock file '<name>.lock.yml' is outdated or unverifiable!

Affected code

1. pkg/workflow/checkout_step_generator.goGenerateGitHubFolderCheckoutStep()

Never emits a token: field, even when checking out a cross-repo target:

if repository != "" {
    fmt.Fprintf(&sb, "          repository: %s\n", repository)
}
// No token: field emitted — defaults to GITHUB_TOKEN

2. pkg/workflow/compiler_activation_job.go — "Check workflow lock file" step

Does not call resolveActivationToken(data), unlike the reaction and comment steps:

steps = append(steps, "        with:\n")
steps = append(steps, "          script: |\n")
// No github-token: emitted — defaults to GITHUB_TOKEN

Suggested fix

  1. Checkout step: Accept an optional token parameter in GenerateGitHubFolderCheckoutStep() and emit token: when it's not the default GITHUB_TOKEN.

  2. Hash check step: Use the same resolveActivationToken(data) pattern already used by the reaction and comment steps:

hashToken := c.resolveActivationToken(data)
if hashToken != "${{ secrets.GITHUB_TOKEN }}" {
    steps = append(steps, fmt.Sprintf("          github-token: %s\n", hashToken))
}

What this fixes

Eliminates the need for consumers to manually patch compiled lock files for cross-org workflow_call. Users would configure on.github-token in their .md frontmatter (which many already do for reactions/comments), and the compiler would propagate it to all activation steps that need callee repo access.

What this does NOT fix

Users will still need to provide a cross-org token (PAT or GitHub App) via on.github-token and pass it as a secret from the caller. This is a platform constraint — GITHUB_TOKEN is repo-scoped and cannot access other repos regardless of visibility.

Workaround

Manually inject github-token: ${{ secrets.CROSS_ORG_TOKEN || github.token }} into the compiled .lock.yml after every gh aw compile. This must be re-applied after every recompilation.

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions