anthropics / anthropics/claude-code-action

`pull_request.labeled` / `issues.labeled` events fail with 401 `Bad credentials` at `checkHumanActor` under default OIDC + federation auth

Open
#1,348 1 comment 0 reactions 0 assignees View on GitHub
area:permissions bug p2
Dominant language
TypeScript
Stars
8.9k
Forks
2.1k
Avg merge
3d 9h
Merged PRs (30d)
10

Description

## Summary

When `anthropics/claude-code-action` is invoked on a `pull_request.labeled` or `issues.labeled` event with the default OIDC + Anthropic-federation auth flow (no `github_token` input passed), the action fails with `Bad credentials - https://docs.github.com/rest` and exits 1 at `checkHumanActor`, before any review work begins. The same workflow, same step, same secrets configuration succeeds when triggered by `issue_comment.created`.

This is observed at SHA `4481e6d3c7bbb88db2a928ca3444c536f589c7c1` (a relatively recent pin; the issue is present in `main` as of the time of writing).

Related but not identical: #1095 (open) flags the closely-related `track_progress: true` failure for the same event type at the mode-detection layer. The failure documented here happens further down the call chain at `checkHumanActor`, and does not require `track_progress` to be set.

## Steps to reproduce

Workflow file (minimal repro):

```yaml
name: Claude review
on:
pull_request:
types: [labeled]
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
jobs:
review:
if: ${{ github.event.label.name == 'claude-review' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 1 }
- uses: anthropics/claude-code-action@v1 # or pinned SHA
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: "Review this PR briefly."
# NOTE: no github_token input — uses default OIDC + federation
```

1. Open a PR in a repo with this workflow installed.
2. Apply a label named `claude-review` to the PR.
3. Observe the workflow run.

## Expected behavior

The action proceeds through `prepareAgentMode` and runs the prompt, posting a review comment as `claude[bot]` (or whatever identity the OIDC-federation flow produces).

## Actual behavior

The action's `setupGitHubToken()` succeeds (OIDC token obtained, exchange with Anthropic federation succeeds, installation token returned). `checkWritePermissions()` succeeds (`Permission level retrieved: admin` for a repo-admin actor). Then `prepareAgentMode` is entered, and its first awaited operation — `checkHumanActor(octokit.rest, context)` — fails with `Bad credentials` from `octokit.users.getByUsername({ username: actor })`.

Action log fragment (timestamps from a real diagnostic run):

```
2026-05-23T15:59:44.4585303Z Checking permissions for actor:
2026-05-23T15:59:44.5586123Z Permission level retrieved: admin
2026-05-23T15:59:44.6033970Z Preparing with mode: agent for event: pull_request
2026-05-23T15:59:44.6056665Z ##[error]Action failed with error: Bad credentials - https://docs.github.com/rest
2026-05-23T15:59:44.6060105Z ##[error]Process completed with exit code 1.
```

~50ms gap between "Permission level retrieved" (last successful call) and "Bad credentials" (the failure). The mode-dispatch log line in between indicates the failure happens during `prepareAgentMode`, not during mode detection.

## Empirical observations

The same workflow, same step, same `secrets.ANTHROPIC_API_KEY`, same OIDC + federation auth, run on the **same repository** on different event types:

| Trigger | Result |
|---|---|
| `issue_comment.created` (`@claude review` in a comment) | ✅ Action runs to completion; review posted by `claude[bot]` |
| `pull_request.labeled` | ❌ Fails at `checkHumanActor` with `Bad credentials` |
| `issues.labeled` | ❌ Same failure as `pull_request.labeled` (symmetric) |

The `claude[bot]` reviews on `issue_comment.created` confirm the OIDC + federation flow is functional in the repository and that no persistent OAuth credential is required — the installation token is obtained at runtime.

## Source-traced mechanism (per reading source at SHA `4481e6d3`)

- `src/github/token.ts`: `setupGitHubToken()` follows the OIDC + federation path when `OVERRIDE_GITHUB_TOKEN` (from `inputs.github_token`) is not set. Returns an installation token from `https://api.anthropic.com/api/github/github-app-token-exchange`.
- `src/entrypoints/run.ts`: prepares `octokit` from this token, then calls `checkWritePermissions(...)` (succeeds for repo-scoped `getCollaboratorPermissionLevel`), then dispatches to `prepareAgentMode` (because `inputs.prompt` is set and the event matches agent mode).
- `src/modes/agent/index.ts`: `prepareAgentMode`'s first awaited operation is `await checkHumanActor(octokit.rest, context)`.
- `src/github/validation/actor.ts`: `checkHumanActor` calls `octokit.users.getByUsername({ username: actor })` — `GET /users/{username}`. This is a GitHub-global endpoint, not repo-scoped.

The installation token works for `/repos/{owner}/{repo}/...` calls (such as `getCollaboratorPermissionLevel`) but returns 401 `Bad credentials` for `/users/{username}` (a global endpoint) on `pull_request.labeled` and `issues.labeled` events specifically. The same call succeeds on `issue_comment.created` events.

This pattern is consistent with Anthropic's federation server issuing installation tokens with different effective scopes per event type — `pull_request.labeled` / `issues.labeled` get tokens that lack the implicit scope for `/users/{username}`, while `issue_comment.created` gets a token that has it. We can't verify this server-side from the action's source alone; the empirical signature is what's observable.

Additional evidence supporting "labeled events are not fully designed for" in the action:

- `src/modes/detector.ts` PR-event branch lists `supportedActions = ["opened", "synchronize", "ready_for_review", "reopened"]`. `"labeled"` is not in the list. The event falls through to the default `return "agent"` at the bottom of `detectMode()`, so mode selection happens to land on agent mode by coincidence rather than by design.

The mode-detector fall-through is the same observation #1095 makes for the `track_progress` failure. Our failure is downstream of that — `checkHumanActor` doesn't gate by event type, so the fall-through mode selection cascades into an auth flow that the OIDC-issued token doesn't support.

## Workaround

Passing `github_token: ${{ secrets.GITHUB_TOKEN }}` via `with:` to the action short-circuits `setupGitHubToken()` at the `if (providedToken)` early return. The action uses the workflow's default token directly, bypassing the OIDC exchange. `users.getByUsername` succeeds because the workflow-default `GITHUB_TOKEN` has the global-endpoint scope. Posting identity becomes `github-actions[bot]` instead of `claude[bot]`.

This works but has the downsides:

- Posting identity changes from `claude[bot]` to `github-actions[bot]`.
- The action's intended auth posture (OIDC + federation) is bypassed entirely.
- Users seeking the `claude[bot]` identity must run a separate workflow for `issue_comment.created` triggers and accept the dual-identity in their repos.

## Suggested directions (not prescriptive)

A few directions that would address this without breaking existing flows:

1. **Add `"labeled"` to `supportedActions` in `detectMode` for PR and Issue branches.** Makes the mode-selection explicit rather than fall-through. This is essentially what #1180 (closed unmerged) attempted for the `track_progress` case. Doesn't fix the auth-scope mismatch on its own.
2. **Skip `checkHumanActor` for installation-token auth on `*.labeled` events**, since the actor's bot-vs-human distinction is already partially handled by `checkWritePermissions` for entity contexts. The actor calling `getByUsername` is a separate concern from the actor having write access, and the latter is already validated.
3. **Issue installation tokens with consistent effective scopes across event types** from the Anthropic federation server side. The most direct fix; requires server-side change.
4. **Catch 401 from `users.getByUsername` in `checkHumanActor` and fall through to a lighter-weight check** (e.g., trust `checkWritePermissions`'s result as a proxy for "actor is allowed to trigger this run"). Similar to the 404-handling already present for bot actors in PR #1144.

I have no preference between these directions — sharing them as starting points for the team's call.

## Related upstream issues / PRs

- #1095 (open) — `track_progress` failing on `pull_request.labeled`. Same architectural root cause (`"labeled"` not in `supportedActions`), different surfacing point (mode-detection vs `checkHumanActor`).
- #1180 (closed unmerged, 2026-04-05) — proposed adding `"labeled"` to `supportedActions` to fix #1095. Closed ~5 minutes after creation with no public reason.
- #1144 (closed-merged, 2026-05-14) — added 404-handling in `checkHumanActor` for non-User actors (e.g. Copilot). Adjacent fix for a different actor type; same function.
- #1330, #1331 (closed-merged, 2026-05-19/20) — refactored `allowed_bots` resolution in `checkHumanActor`. Same function, different concern.

## Reproducibility

The failure is deterministic on the affected event types. Filing this issue with the empirical evidence as observed; happy to provide additional log fragments or a sacrificial repository for upstream reproduction if useful.

Action version: `4481e6d3c7bbb88db2a928ca3444c536f589c7c1`.
Runner: `ubuntu-latest`.
GitHub Enterprise: no (`github.com`).

## Trigger context

This issue arose during maintainer work on a private repository's review-automation workflow that exercises all three trigger paths (`issue_comment.created`, `pull_request.labeled`, `issues.labeled`). The divergence between paths was not visible until the `*.labeled` paths were specifically exercised — sharing here in case it's useful empirical data for the upstream team.

— Filed by a third-party maintainer working with the action; happy to follow up with further evidence.

Contributor guide

Open the contributing guide

Research direction

Start with src/github/token.ts, src/entrypoints/run.ts, src/modes/agent/index.ts, and src/github/validation/actor.ts, then trace the labeled-event path through src/modes/detector.ts. Reproduce the workflow with OIDC federation and compare it with issue_comment.created; done means the affected labeled events complete the intended agent flow without the reported 401, with the chosen behavior verified by the project's checks.

Written by the indexing model from the issue text.

Assessment

Tech stack
github, github-actions, typescript
Domain
authentication, backend-api-design, ci-cd
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.