Skip to content

NAS-142245 / 27.0.0-BETA.1 / ci: prefer the Claude Code OAuth token over the API key - #11

Merged
AlexKarpov98 merged 3 commits into
masterfrom
claude-review-prefer-oauth-token
Aug 18, 2026
Merged

NAS-142245 / 27.0.0-BETA.1 / ci: prefer the Claude Code OAuth token over the API key#11
AlexKarpov98 merged 3 commits into
masterfrom
claude-review-prefer-oauth-token

Conversation

@AlexKarpov98

Copy link
Copy Markdown
Contributor

The shared review workflow accepted either credential but required exactly one, failing the job when both were mapped. That forces the secret grant and the caller change to land in lockstep in every repo.

Prefer the token instead: fail only when both are empty, and pass the API key to the action only when the token is empty, so the CLI never sees two credentials and the billing account is decided here rather than by whichever it happens to read first.

UX_CLAUDE_CODE_OAUTH_TOKEN is an org secret granted per repository, and one that has not been granted resolves to the empty string rather than failing. With both mapped, a repo runs on the subscription where the grant exists and on API billing where it does not, and flips over on its own when the grant lands -- no second pull request, and no window where review is broken because the secret and the workflow arrived in the wrong order.

The shared review workflow accepted either credential but required
exactly one, failing the job when both were mapped. That forces the
secret grant and the caller change to land in lockstep in every repo.

Prefer the token instead: fail only when both are empty, and pass the
API key to the action only when the token is empty, so the CLI never
sees two credentials and the billing account is decided here rather
than by whichever it happens to read first.

UX_CLAUDE_CODE_OAUTH_TOKEN is an org secret granted per repository, and
one that has not been granted resolves to the empty string rather than
failing. With both mapped, a repo runs on the subscription where the
grant exists and on API billing where it does not, and flips over on
its own when the grant lands -- no second pull request, and no window
where review is broken because the secret and the workflow arrived in
the wrong order.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018z1AokioSumsXkmVAGo9pq
@AlexKarpov98 AlexKarpov98 self-assigned this Aug 18, 2026
@bugclerk bugclerk changed the title ci: prefer the Claude Code OAuth token over the API key NAS-142245 / 27.0.0-BETA.1 / ci: prefer the Claude Code OAuth token over the API key Aug 18, 2026
@bugclerk

Copy link
Copy Markdown
Contributor

Comment thread README.md Outdated
Comment thread README.md Outdated
@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown

Automatic review

One finding: one LOW. Nothing at or above MEDIUM, so the review gate passes.

The credential pick itself checks out end to end:

  • The expression is the working direction, not the no-op. ${{ secrets.claude-code-oauth-token == '' && secrets.anthropic-api-key || '' }} parses as ((token == '') && key) || '', and since a && b yields b only when b is itself truthy, the key has to be in the true arm. Token empty → true && key → key. Token set → false && keyfalse || ''''. The comment above it spells out why the inverted form collapses to key unconditionally, which is the trap here, and it is right.
  • An unchanged caller keeps working. A repo mapping only anthropic-api-key leaves secrets.claude-code-oauth-token undeclared, which compares equal to '', so it still gets the key — and the guard, which used to fail on both-set, now only fails on neither-set, so nothing that passed before starts failing. A repo mapping only the token is likewise unaffected. The behaviour change is confined to the both-mapped case, which previously exited 1.
  • The guard still fails closed and still fails before the checkout, as the README claims — it is the third step of the review job, ahead of actions/checkout.
  • check-review-threshold.mjs does what the README now says it does. terminalApiError() reads EXECUTION_FILE, finds the last result message, and reports terminal_reason === 'api_error' or api_error_status; with no structured output every path exits 1. So an exhausted or revoked token surfaces as a named API error, not as a clean review. EXECUTION_FILE is wired from steps.review.outputs.execution_file at line 482.
  • No stale copy of the old rule survives. The "set exactly one; mapping both fails the job" wording is gone from the secret descriptions, the header usage example, the guard's comment and the README together — grep for it finds nothing left.

Worth noting for the record, not as a finding: the pre-existing fork paragraph in claude-review-self.yml says "both auth secrets arrive empty" on a fork run, which was loose when only one secret was mapped and is now literally true.

The one LOW is on README.md:233 and is about where a paragraph break went, not about behaviour.

The README said mapping both secrets means an expired OAuth token
degrades to API billing. It does not. The pick is made on emptiness and
an expired token is a non-empty string, so it still wins and the API
key is still blanked -- every repo holding the grant fails auth at
once. The pair covers a missing grant, not a bad credential.

Also names the three orgs the secret is set on, since a reader outside
them would find "check the grant" a dead end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018z1AokioSumsXkmVAGo9pq
Comment thread README.md Outdated
The paragraph named expiry as the case the mapped pair does not cover,
but the property is about validity in general: a revoked token, or one
that has hit the subscription's usage limit, is equally non-empty, so
it still wins and the API key is still blanked. Same failure, same
blast radius.

Also points at what does report it -- check-review-threshold.mjs reads
the execution log when there is no structured output, so the gate names
the API error instead of passing a review that never ran.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018z1AokioSumsXkmVAGo9pq
Comment thread README.md
@AlexKarpov98
AlexKarpov98 marked this pull request as ready for review August 18, 2026 16:22
@AlexKarpov98
AlexKarpov98 merged commit bdb46f7 into master Aug 18, 2026
7 checks passed
@AlexKarpov98
AlexKarpov98 deleted the claude-review-prefer-oauth-token branch August 18, 2026 16:27
@bugclerk

Copy link
Copy Markdown
Contributor

This PR has been merged and conversations have been locked.
If you would like to discuss more about this issue please use our forums or raise a Jira ticket.

@iXsystems iXsystems locked as resolved and limited conversation to collaborators Aug 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants