Summary
After #24251, the GitHub App token for the MCP server is minted in the activation job and passed to agent (and other jobs) via a job output. In practice the token never arrives — GitHub Actions runner silently drops the output with:
Warning: Skip output 'github_mcp_app_token' since it may contain secret.
GITHUB_MCP_SERVER_TOKEN is empty in the agent job; the GitHub MCP server runs unauthenticated.
Root Cause
actions/create-github-app-token unconditionally calls ::add-mask:: on the token it produces. Once a value is masked, the GitHub Actions runner refuses to propagate it as a job output — it emits the warning above and substitutes an empty string. This is intentional runner hardening (introduced around runner v2.308) to prevent secrets leaking across job boundaries via outputs.
The compiled activation job:
- name: Generate GitHub App token
id: github-mcp-app-token
uses: actions/create-github-app-token@... # v3
with:
app-id: ${{ vars.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
...
outputs:
github_mcp_app_token: ${{ steps.github-mcp-app-token.outputs.token }}
# ^^^ masked → runner skips this output entirely
The security intent of #24251 is sound — app-id/private-key should not reach the agent job. But the chosen mechanism (job output) is incompatible with how actions/create-github-app-token works.
Reproduction
- Configure any workflow with
tools.github.github-app: and compile with v0.66.1.
- Trigger the workflow.
- In the activation job's "Complete job" step, observe:
Warning: Skip output 'github_mcp_app_token' since it may contain secret.
GITHUB_MCP_SERVER_TOKEN is empty in the agent job.
Suggested Fix
The runner blocks masked values from crossing job boundaries via outputs:, but the fix options include:
- Duplicate the minting step into each consuming job (
agent, etc.), scoped so private-key is excluded from the AWF sandbox environment — the --exclude-env GITHUB_MCP_SERVER_TOKEN flag is already present on the awf invocation, so the key itself never enters the sandbox.
- Use a custom minting script that does not call
::add-mask::, allowing the output to propagate (trades away log masking).
- Pass via artifact rather than job output (more complex, probably not worth it).
The cleanest approach is likely reverting the activation-job output pattern for github_mcp_app_token and instead generating the token directly inside each job that needs it.
References
Summary
After #24251, the GitHub App token for the MCP server is minted in the
activationjob and passed toagent(and other jobs) via a job output. In practice the token never arrives — GitHub Actions runner silently drops the output with:GITHUB_MCP_SERVER_TOKENis empty in theagentjob; the GitHub MCP server runs unauthenticated.Root Cause
actions/create-github-app-tokenunconditionally calls::add-mask::on the token it produces. Once a value is masked, the GitHub Actions runner refuses to propagate it as a job output — it emits the warning above and substitutes an empty string. This is intentional runner hardening (introduced around runner v2.308) to prevent secrets leaking across job boundaries via outputs.The compiled
activationjob:The security intent of #24251 is sound —
app-id/private-keyshould not reach the agent job. But the chosen mechanism (job output) is incompatible with howactions/create-github-app-tokenworks.Reproduction
tools.github.github-app:and compile with v0.66.1.GITHUB_MCP_SERVER_TOKENis empty in the agent job.Suggested Fix
The runner blocks masked values from crossing job boundaries via
outputs:, but the fix options include:agent, etc.), scoped soprivate-keyis excluded from the AWF sandbox environment — the--exclude-env GITHUB_MCP_SERVER_TOKENflag is already present on theawfinvocation, so the key itself never enters the sandbox.::add-mask::, allowing the output to propagate (trades away log masking).The cleanest approach is likely reverting the activation-job output pattern for
github_mcp_app_tokenand instead generating the token directly inside each job that needs it.References