anthropics / anthropics/claude-code-action
claude_code_oauth_token from claude setup-token rejected with 401 in the action, while the same account authenticates fine locally
- Langage dominant
- TypeScript
- Étoiles
- 8.9k
- Forks
- 2.1k
- Métriques de merge des PR
- Métriques de PR en attente
Description
# `claude_code_oauth_token` from `claude setup-token` rejected with 401 in the action, while the same account authenticates fine locally
## Summary
Every run of `anthropics/claude-code-action` fails ~2 seconds after Claude Code initializes, with:
```
"error_status": 401,
"error": "authentication_failed",
"api_error_status": 401,
"terminal_reason": "api_error",
"result": "Failed to authenticate. API Error: 401 OAuth access token is invalid."
```
The action reports this as `subtype: "success"` with `is_error: true`, which surfaces to the job as `Claude execution failed: result is_error:true` — the underlying 401 is only visible with `show_full_output: true`.
The same account authenticates and completes requests fine via the local CLI. Four separately minted tokens produce the identical 401, on two different pinned action versions and two different models.
## Environment
| | |
|---|---|
| Action version | `v1.0.189` (`6b082c41935b4c8a3b8b0ef85ba4ba4d9eeb8975`) — also reproduced on `v1.0.183` |
| Runner | `ubuntu-latest` (GitHub-hosted) |
| Local CLI | Claude Code `2.1.226` |
| Auth method | `claude setup-token` → repo secret → `claude_code_oauth_token` input |
| Account | Max subscription, `authMethod: claude.ai`, `apiProvider: firstParty` |
| Model | `--model claude-opus-5` — also reproduced on `claude-opus-4-8` |
| Repo | Private |
## Reproduction
1. `claude auth login` (browser flow completes)
2. Verify the session is live:
```
$ claude auth status
{ "loggedIn": true, "authMethod": "claude.ai", "apiProvider": "firstParty",
"subscriptionType": "max", ... }
$ claude --model claude-opus-5 -p "say OK"
OK
```
3. `claude setup-token`, paste the result into the repo secret:
```
gh secret set CLAUDE_CODE_OAUTH_TOKEN -R /
```
4. Trigger the workflow (`@claude` comment on a PR, or a `pull_request` event).
5. The run fails ~2s after init with the 401 above. `num_turns: 1`, `total_cost_usd: 0`.
Step 3→4 was performed within roughly one minute in the last attempt, so token age is not a factor.
## What we ruled out
| Hypothesis | Result |
|---|---|
| Token expired | No — reproduced with a token minted <1 min before the run |
| Stale/mistyped secret value | No — **four** separately minted tokens, identical 401; set via `gh secret set` (stdin, no paste-wrapping) |
| Local session expired | Was true initially, then fixed. Post-`auth login` the CLI works; the action still 401s |
| Account lacks model access | No — `claude --model claude-opus-5 -p "say OK"` returns `OK` on the same account |
| Model-specific | No — identical on `claude-opus-4-8` and `claude-opus-5` |
| Action version | No — identical on `v1.0.183` and `v1.0.189` |
| Workflow misconfiguration | No — checkout succeeds, the action's own permission check passes (`Permission level retrieved: admin`, `Trigger result: true`), Claude Code initializes with the right model, then the first authenticated call 401s |
| Rate limiting / usage cap | Not indicated — the error is `authentication_failed` / 401, not 429 |
Additional detail: `total_cost_usd: 0` on every failing run. On a Max-plan OAuth token that is the expected value regardless of outcome (usage bills against the subscription), so it is *not* offered here as evidence of anything — noting it only because it is easy to misread.
## Expected vs actual
**Expected:** a token minted by `claude setup-token` from a live, logged-in Max session authenticates in the action the same way it does locally.
**Actual:** the action's first authenticated request is rejected with `401 OAuth access token is invalid`.
## Secondary issue: the failure is undiagnosable by default
With default settings, the only signal is:
```
##[error]Claude result reported subtype success with is_error:true (run did not complete successfully)
##[error]Action failed with error: Claude execution failed: result is_error:true
```
The actual cause (`401 ... token is invalid`) requires `show_full_output: true`, which also prints the full model output — including the PR diff and review prompt — into the step log. That is a poor tradeoff for reading one auth error. Surfacing `error`/`error_status`/`terminal_reason` in the failure annotation, while keeping model output redacted, would make this class of failure self-diagnosing.
Related: the action reports `subtype: "success"` alongside `is_error: true`, which is contradictory and makes log-grepping for failures harder than it needs to be.
## Also worth noting (may be unrelated)
Two runs in a sibling repo reported job conclusion `success` while actually having skipped:
```
##[warning]Skipping action due to workflow validation: Workflow validation failed.
The workflow file must exist and have identical content to the version on the
repository's default branch.
```
The self-skip on workflow-file PRs is documented and expected, but reporting it as `success` makes "did the review actually run?" impossible to answer from the conclusion alone. A `skipped` conclusion (or a distinct marker) would be clearer.
Guide de contribution
Ouvrir le guide de contribution
Évaluation
Cette issue n'a pas encore été évaluée.