Summary
safe-outputs.github-token accepts a same-job ${{ steps.<id>.outputs.token }} expression (added in #47766 / #48101, to survive the masked-cross-job scrub of #25122). But there is no frontmatter hook to actually inject that mint step before every consumer in the safe_outputs and conclusion jobs. So the accepted expression compiles to a lock that fails actionlint (references an undefined step) and would be empty at runtime.
Same-job minting therefore only works for the agent job (via top-level pre-steps, #25242). For a token-minting action like octo-sts/action (masked output, no stored secret), there is no working configuration on the current compiler.
Verified identical on v0.85.4 and v0.86.2.
Repro
Minimal frontmatter (any masked token-minting action reproduces it; octo-sts shown):
permissions:
contents: read
id-token: write
pre-steps:
- name: Mint token (agent job) — this half works
id: octosts
uses: octo-sts/action@v1.1.1
with: { scope: ${{ github.repository }}, identity: my-policy }
safe-outputs:
push-to-pull-request-branch:
id-token: write
steps:
- name: Mint token (safe-output jobs)
id: octosts
uses: octo-sts/action@v1.1.1
with: { scope: ${{ github.repository }}, identity: my-policy }
github-token: ${{ steps.octosts.outputs.token }}
gh aw compile --actionlint produces (abridged):
lock.yml: error: [expression] property "octosts" is not defined ... # safe_outputs "Checkout repository" token:
lock.yml: error: [expression] property "octosts" is not defined ... # safe_outputs "Configure Git credentials" GIT_TOKEN:
lock.yml: error: [expression] property "octosts" is not defined ... # conclusion "Process no-op messages" github-token:
lock.yml: error: [expression] property "octosts" is not defined ... # conclusion "Log detection run" / "Record missing tool" / "Record incomplete" / "Update reaction comment"
Root cause
safe-outputs.github-token is applied to three jobs (agent, safe_outputs, conclusion), but the only step-injection hooks don't cover the consumers:
-
safe_outputs job — safe-outputs.steps are documented to run "after checking out the repository … and before any safe-output code executes." But the job's own Checkout repository and Configure Git credentials steps (emitted for push-to-pull-request-branch) consume the token and run before safe-outputs.steps. So the mint step lands after two of its consumers. There is no safe-outputs.pre-steps (pre-checkout) hook.
-
conclusion job — has no step-injection hook at all, yet the compiler wires the token into its unguarded reporting handlers (Process no-op messages, Log detection run, Record missing tool, Record incomplete, Update reaction comment). These run every time the job runs (if: always() && needs.agent.result != 'skipped' …), so at runtime they'd hit actions/github-script with an empty required github-token → Error: Input required and not supplied: github-token.
This is the same failure mode as #25122, but for the two jobs that #25242 (agent-only pre-steps) did not address.
Why the existing paths don't cover it
Proposed fixes (any one closes the gap)
safe-outputs.pre-steps — inject before the safe_outputs checkout/git-credential steps (mirror of top-level pre-steps for the agent job), and apply the same steps to the conclusion job. Then ${{ steps.<id>.outputs.token }} resolves in every consumer.
- Give the
conclusion job a step-injection hook (it currently has none).
- Treat a same-job
steps.*.outputs.token in safe-outputs.github-token as first-class the way safe-outputs.github-app is — i.e. the compiler orders the referenced mint step ahead of every consumer in each safe-output job (or errors clearly if it can't).
Environment
Summary
safe-outputs.github-tokenaccepts a same-job${{ steps.<id>.outputs.token }}expression (added in #47766 / #48101, to survive the masked-cross-job scrub of #25122). But there is no frontmatter hook to actually inject that mint step before every consumer in thesafe_outputsandconclusionjobs. So the accepted expression compiles to a lock that failsactionlint(references an undefined step) and would be empty at runtime.Same-job minting therefore only works for the agent job (via top-level
pre-steps, #25242). For a token-minting action likeocto-sts/action(masked output, no stored secret), there is no working configuration on the current compiler.Verified identical on v0.85.4 and v0.86.2.
Repro
Minimal frontmatter (any masked token-minting action reproduces it; octo-sts shown):
gh aw compile --actionlintproduces (abridged):Root cause
safe-outputs.github-tokenis applied to three jobs (agent,safe_outputs,conclusion), but the only step-injection hooks don't cover the consumers:safe_outputsjob —safe-outputs.stepsare documented to run "after checking out the repository … and before any safe-output code executes." But the job's ownCheckout repositoryandConfigure Git credentialssteps (emitted forpush-to-pull-request-branch) consume the token and run beforesafe-outputs.steps. So the mint step lands after two of its consumers. There is nosafe-outputs.pre-steps(pre-checkout) hook.conclusionjob — has no step-injection hook at all, yet the compiler wires the token into its unguarded reporting handlers (Process no-op messages,Log detection run,Record missing tool,Record incomplete,Update reaction comment). These run every time the job runs (if: always() && needs.agent.result != 'skipped' …), so at runtime they'd hitactions/github-scriptwith an empty requiredgithub-token→Error: Input required and not supplied: github-token.This is the same failure mode as #25122, but for the two jobs that #25242 (agent-only
pre-steps) did not address.Why the existing paths don't cover it
${{ steps.*.outputs.* }}insafe-outputs.github-tokenfor same-job token minting #47766 / Allowsteps.*.outputs.*ingithub-tokenexpressions for same-job safe-output auth #48101 made the schema acceptsteps.*.outputs.*, but didn't add a way to place the mint step ahead of these consumers — so the expression is accepted but unresolvable here.pre-stepsto agent job for same-job token minting (with import support) #25242 fixed the agent checkout via top-levelpre-steps;safe_outputs/conclusionwere out of scope.safe-outputs.github-appworks (compiler injectscreate-github-app-tokenbefore each consumer natively), but requires a stored App private key — which defeats the point of a keyless STS like octo-sts.github-app.credentials.stepsfor secret-manager-backed App creds; it doesn't cover an arbitrary masked token-minting action feedingsafe-outputs.github-token, nor thesafe-outputs.steps-ordering / conclusion-hook gap.Proposed fixes (any one closes the gap)
safe-outputs.pre-steps— inject before thesafe_outputscheckout/git-credential steps (mirror of top-levelpre-stepsfor the agent job), and apply the same steps to theconclusionjob. Then${{ steps.<id>.outputs.token }}resolves in every consumer.conclusionjob a step-injection hook (it currently has none).steps.*.outputs.tokeninsafe-outputs.github-tokenas first-class the waysafe-outputs.github-appis — i.e. the compiler orders the referenced mint step ahead of every consumer in each safe-output job (or errors clearly if it can't).Environment
v0.85.4andv0.86.2(gh aw compile --dir workflows --actionlint --zizmor --poutine)pre-stepsto agent job for same-job token minting (with import support) #25242, Allow${{ steps.*.outputs.* }}insafe-outputs.github-tokenfor same-job token minting #47766, Allowsteps.*.outputs.*ingithub-tokenexpressions for same-job safe-output auth #48101, Support secret-manager-backed GitHub App credentials without masked cross-job outputs #33592