Describe the bug
When workload identity federation (WIF) is used together with the plugins / plugin_marketplaces inputs, the action reliably fails with:
Claude Code returned an error result: API Error: Token exchange failed with status 401
(request-id req_...): {"error":{"type":"authentication_error","message":"Authentication failed"}}
Ensure your federation rule matches your identity token.
The run dies approximately 3 minutes after start with num_turns: 1 and total_cost_usd: 0 — no API call ever succeeds. The Claude Console authentication history fills up with platform_federated_authentication failure events with reason jti_reused.
Removing the plugins input, with no other workflow changes, makes the same workflow succeed.
Root cause
This was traced with an eBPF process/network sensor on the runner and reproduced locally against a mock that enforces single-use jti.
-
A GitHub OIDC token is single-use at the Anthropic token-exchange endpoint. The same jti cannot be exchanged twice.
-
With plugins configured, the action spawns several short-lived claude processes per job:
claude plugin marketplace add
- one
claude plugin install per plugin
- the main query, via the Agent SDK
-
Each process resolves federation from the bare env vars:
ANTHROPIC_FEDERATION_RULE_ID
- other related federation env vars
ANTHROPIC_IDENTITY_TOKEN_FILE
Each process then performs its own token exchange using the same identity-token file.
The first process succeeds. Every subsequent process re-presents the now-consumed jti and receives 401 jti_reused.
-
By the time the main query runs, the token has already been burned. It retries the exchange with exponential backoff for approximately 3 minutes. The same file still contains the same jti, because the action's background refresh runs every 4 minutes, and the job eventually fails.
eBPF process trace from a failing run — five separate claude processes each contact api.anthropic.com with the same identity-token file:
16:06:36 pid 2225 claude (native binary, install-time handshake)
16:06:45 pid 2259 claude plugin marketplace add
16:06:46 pid 2306 claude plugin install differential-review@trailofbits
16:06:47 pid 2315 claude plugin install fp-check@trailofbits
16:06:48 pid 2324 claude (main query, agent-sdk) ← fails, token already consumed
The federation rule itself is fine. Exchanging the same workflow's OIDC token once via a raw curl to POST /v1/oauth/token returns 200, and a second exchange of the same JWT returns the exact 401 above.
To reproduce
- Configure WIF using a federation rule for a GitHub Actions repo, following the docs.
- Run the workflow below on a GitHub-hosted runner using
ubuntu-24.04.
- The run fails after approximately 3 minutes with the token-exchange
401.
- The Console shows one successful exchange followed by repeated
jti_reused failures for the same jti.
- Remove the
plugins / plugin_marketplaces inputs.
- The run succeeds.
This was reproduced on:
v1.0.139, which bundles Claude Code 2.1.167
v1.0.144, which bundles Claude Code 2.1.173
This does not appear to be fixed by version bumps.
In our org, this happened on 100% of runs with plugins — 10+ runs across two repos and three workflows — and 0% of runs without plugins.
Expected behavior
Plugins and WIF should work together.
The exchanged access token should be shared across the claude processes spawned by the action, so the single-use identity token is only exchanged once per token lifetime.
Workflow YAML file
Minimal reproduction:
name: wif-plugins-repro
on: workflow_dispatch
permissions:
id-token: write
contents: read
jobs:
repro:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: anthropics/claude-code-action/base-action@v1.0.144
with:
prompt: "Say hi in one word and stop. Do not use any tools."
anthropic_federation_rule_id: "fdrl_***"
anthropic_organization_id: "***"
anthropic_service_account_id: "svac_***"
anthropic_workspace_id: "wrkspc_***"
claude_args: '--model claude-opus-4-6 --allowedTools "Read"'
plugin_marketplaces: |
https://github.com/trailofbits/skills.git
plugins: |
differential-review@trailofbits
fp-check@trailofbits
API Provider
Additional context
Sample failing request IDs, in UTC, in case server-side logs help:
req_011CbwgekzRwSukwKoVFBMnm — 2026-06-11 15:09
req_011CbwiRpxvNGfJSs13mMVnT — 2026-06-11 around 16:09
Happy to share the federation rule / org IDs privately.
The jti_reused events are misleading during diagnosis. The first event per jti is a success, or the real error, and everything after that is retry noise. We initially chased the federation rule configuration because of this.
Proposed fix
Happy to open a PR.
The Anthropic SDK only enables its on-disk credentials cache:
<config_dir>/credentials/<profile>.json
This cache is shared across processes, but it is only enabled when federation is loaded from a profile config file — not from bare env vars.
If setupWorkloadIdentity() additionally writes a profile pointing at the identity-token file and selects it via:
ANTHROPIC_CONFIG_DIR
ANTHROPIC_PROFILE
then the first process exchanges once, and every other process reuses the cached access token.
The env vars can remain as a fallback for older CLIs.
Implemented and verified here:
https://github.com/KeisukeYamashita/claude-code-action/compare/main...fix/wif-shared-credentials-cache
With that change, the exact configuration above, which previously failed 100% of the time, succeeds:
is_error: false
- main query completes in approximately 6 seconds
- the eBPF trace shows the same five processes, but only one token exchange
- existing test suite passes:
702/702
- new unit tests were added for the profile file
Describe the bug
When workload identity federation (WIF) is used together with the
plugins/plugin_marketplacesinputs, the action reliably fails with:The run dies approximately 3 minutes after start with
num_turns: 1andtotal_cost_usd: 0— no API call ever succeeds. The Claude Console authentication history fills up withplatform_federated_authenticationfailure events with reasonjti_reused.Removing the
pluginsinput, with no other workflow changes, makes the same workflow succeed.Root cause
This was traced with an eBPF process/network sensor on the runner and reproduced locally against a mock that enforces single-use
jti.A GitHub OIDC token is single-use at the Anthropic token-exchange endpoint. The same
jticannot be exchanged twice.With plugins configured, the action spawns several short-lived
claudeprocesses per job:claude plugin marketplace addclaude plugin installper pluginEach process resolves federation from the bare env vars:
ANTHROPIC_FEDERATION_RULE_IDANTHROPIC_IDENTITY_TOKEN_FILEEach process then performs its own token exchange using the same identity-token file.
The first process succeeds. Every subsequent process re-presents the now-consumed
jtiand receives401 jti_reused.By the time the main query runs, the token has already been burned. It retries the exchange with exponential backoff for approximately 3 minutes. The same file still contains the same
jti, because the action's background refresh runs every 4 minutes, and the job eventually fails.eBPF process trace from a failing run — five separate
claudeprocesses each contactapi.anthropic.comwith the same identity-token file:The federation rule itself is fine. Exchanging the same workflow's OIDC token once via a raw
curltoPOST /v1/oauth/tokenreturns200, and a second exchange of the same JWT returns the exact401above.To reproduce
ubuntu-24.04.401.jti_reusedfailures for the samejti.plugins/plugin_marketplacesinputs.This was reproduced on:
v1.0.139, which bundles Claude Code2.1.167v1.0.144, which bundles Claude Code2.1.173This does not appear to be fixed by version bumps.
In our org, this happened on 100% of runs with plugins — 10+ runs across two repos and three workflows — and 0% of runs without plugins.
Expected behavior
Plugins and WIF should work together.
The exchanged access token should be shared across the
claudeprocesses spawned by the action, so the single-use identity token is only exchanged once per token lifetime.Workflow YAML file
Minimal reproduction:
API Provider
Additional context
Sample failing request IDs, in UTC, in case server-side logs help:
req_011CbwgekzRwSukwKoVFBMnm— 2026-06-11 15:09req_011CbwiRpxvNGfJSs13mMVnT— 2026-06-11 around 16:09Happy to share the federation rule / org IDs privately.
The
jti_reusedevents are misleading during diagnosis. The first event perjtiis a success, or the real error, and everything after that is retry noise. We initially chased the federation rule configuration because of this.Proposed fix
Happy to open a PR.
The Anthropic SDK only enables its on-disk credentials cache:
This cache is shared across processes, but it is only enabled when federation is loaded from a profile config file — not from bare env vars.
If
setupWorkloadIdentity()additionally writes a profile pointing at the identity-token file and selects it via:ANTHROPIC_CONFIG_DIRANTHROPIC_PROFILEthen the first process exchanges once, and every other process reuses the cached access token.
The env vars can remain as a fallback for older CLIs.
Implemented and verified here:
With that change, the exact configuration above, which previously failed 100% of the time, succeeds:
is_error: false702/702