Skip to content

NAS-142189 / 27.0.0-BETA.1 / Accept a Claude Code OAuth token as the auth secret - #9

Merged
william-gr merged 3 commits into
masterfrom
feat/oauth-token-auth
Aug 15, 2026
Merged

NAS-142189 / 27.0.0-BETA.1 / Accept a Claude Code OAuth token as the auth secret#9
william-gr merged 3 commits into
masterfrom
feat/oauth-token-auth

Conversation

@william-gr

Copy link
Copy Markdown
Contributor

Summary

anthropics/claude-code-action@v1.0.187 accepts claude_code_oauth_token (the token claude setup-token generates from a subscription login) as a first-class alternative to anthropic_api_key — verified in the pinned version's action.yml and base-action/src/validate-env.ts, which requires at least one of the two and exports both to the CLI as env vars.

  • New optional claude-code-oauth-token secret, passed through to the action; anthropic-api-key becomes optional.
  • A guard step fails the job before the checkout when neither secret is set, instead of erroring deep inside the review step.
  • Usage comment and README updated: set exactly one of the two.

No caller changes required — existing anthropic-api-key callers are untouched. A subscription-billed repo switches by mapping claude-code-oauth-token instead.

claude-code-action v1.0.187 takes claude_code_oauth_token as a
first-class alternative to anthropic_api_key, so subscription-billed
repos need no API key — and no self-hosted runner or custom action.
Either secret satisfies the workflow; a job with neither fails
before the checkout instead of deep inside the review step.
Comment thread .github/workflows/claude-review.yml Outdated

# Fail before spending a runner minute on a checkout: with neither
# secret the review step errors anyway, only later and less clearly.
- name: Check an auth secret is present

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — This step moves the "no auth" failure earlier in the job, which makes a comment in claude-review-self.yml untrue. claude-review-self.yml:40-41 says of a fork PR:

pull_request gives a fork run no secrets, so anthropic-api-key arrives empty and the review step fails — it cannot spend tokens.

With this step in place the review step never runs on that path: Check an auth secret is present exits 1 before the checkout, so a writer opening a PR from a fork now fails here, not at Automatic PR Review. The conclusion the comment draws (degrades, spends no tokens) still holds; the mechanism it names does not, and that file is otherwise precise about which step does what ("the step dies on MODULE_NOT_FOUND", "the review job's own actions/checkout"), so the specificity reads as load-bearing.

Fix in claude-review-self.yml:41 — e.g. "…arrives empty and the job fails its auth-secret check before the review runs".

.github/workflows/claude-review-self.yml:41

Comment thread .github/workflows/claude-review.yml Outdated
uses: anthropics/claude-code-action@v1.0.187
with:
# Both pass through as env vars; the action treats an empty one as
# absent and requires at least one. Callers should set exactly one.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — "Callers should set exactly one" is advice with nothing behind it, and both usage snippets invite the opposite. The check above enforces at least one; the header block (claude-review.yml:42-44) and README.md:181-183 show the two lines adjacently with only a # comment between them, so a caller who copies the block verbatim and has both org secrets defined passes both. Nothing then says which credential wins, and the two bill different accounts — a repo that meant to stay on API billing could silently start drawing down a subscription (or the reverse) with no signal in the log.

Cheap to close either way: extend the check to warn (or fail) when both are non-empty, or state in the comment which one the action prefers so the outcome is at least predictable.

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown

Two findings, both LOW. Nothing here blocks the gate.

The shape of the change is right for this repo. anthropic-api-key going required: false costs existing callers nothing — truenas/webui, api-client-ts, truenas-connect/ui and truenas-ui-components all still map it and still pass the new guard on the first branch, so the @master blast radius of this is a no-op for them. The either/or is enforced in one place rather than left to the action, the guard sits before the checkout so it cannot spend tokens, and it fails as a step in the review job rather than as a condition that would skip it — which matters, because a skipped job satisfies a required status check. The both-set branch is the right call too: two credentials billing two accounts with an undocumented winner is worse than a red job.

I also checked the paths where a guard like this usually leaks. An empty-but-mapped secret is treated as absent, so no caller is newly rejected. A non-empty but expired OAuth token passes the guard and dies in the review step, where check-review-threshold.mjs fails on unparseable output — still closed. And this PR's own review is running the proposed workflow via the relative uses: in claude-review-self.yml, so the one-secret branch is exercised by the fact that you are reading this.

Both findings are on the inline threads:

  • LOW.github/workflows/claude-review.yml:44 — the usage snippet (and README.md:183) says "mapping both fails the job", but the guard compares values, not mappings. A mapped-but-nonexistent secret is the same empty string as an unmapped one, so a mistyped secret name falls through to the other credential and bills the account the caller was migrating away from, silently. Not fixable from a workflow; the wording is what can move.
  • LOW.github/workflows/claude-review.yml:210 — failing before the checkout leaves the two if: always() tail steps calling node .claude-review/tooling/review/*.mjs with nothing checked out, so they add two Cannot find module failures after the annotation that explains the real problem. Pre-existing shape, but this change is what puts it on a path callers will routinely hit.

One thing I could not verify offline: that anthropics/claude-code-action@v1.0.187 declares claude_code_oauth_token and treats an empty value as absent. Network access is blocked in this job, so I am taking the PR description's reading of action.yml and base-action/src/validate-env.ts at its word. If the input name were wrong, Actions would warn rather than fail and the OAuth path would quietly do nothing — worth a second pair of eyes on that pin, since the line at 306 is the assertion the whole change rests on for existing callers.

The claude-review-self.yml comment update and the README prose both track the new behaviour; the fork paragraph reads correctly for a writer working from a fork, and the require-write-access sentence already covers the non-writer case where the guard never runs.

Review findings: the fork-PR comment in claude-review-self.yml named
the review step as where an empty secret dies, but the new guard
fails first; and "set exactly one" was advice with nothing behind it
while both usage snippets invite copying both lines. The guard now
fails on both-set too — the two secrets bill different accounts, so
which wins must not be an undocumented pick.
Comment thread README.md Outdated
Comment on lines +182 to +183
# or, on subscription auth instead of API billing:
claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — This example maps both secrets, which is the one configuration the new guard rejects. # or, ... reads as an alternative to a human, but both lines are live YAML: a caller copying this block gets both mappings.

That is harmless only while CLAUDE_CODE_OAUTH_TOKEN does not exist in the repo (it resolves empty). The interesting case is the migration this PR exists for — a repo adding CLAUDE_CODE_OAUTH_TOKEN without deleting CLAUDE_API_KEY, which is the normal way to switch billing. With both secrets defined and this block copied verbatim, Check exactly one auth secret is present exits 1 and every review job fails.

The prose at README.md:204 already says both fails before the checkout, so the example contradicts the paragraph two lines under it. Note also that this repo's own caller (claude-review-self.yml:60) maps one, not both.

Suggested change
# or, on subscription auth instead of API billing:
claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# or, on subscription auth instead of API billing — set exactly one of
# the two; mapping both fails the job before the checkout:
# claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

Comment thread .github/workflows/claude-review.yml Outdated
Comment on lines +43 to +44
# # or, on subscription auth instead of API billing:
# claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MEDIUM — Same contradiction as the README example: the usage block shows both secrets mapped, and the guard added at line 195 fails the job when both are set. This is the copy-paste source for a new consumer, so it hands them the rejected configuration — a repo that adds CLAUDE_CODE_OAUTH_TOKEN while CLAUDE_API_KEY is still defined (the ordinary way to switch billing) gets a red review job on every PR with no change of its own to explain it.

Suggested change
# # or, on subscription auth instead of API billing:
# claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# # or, on subscription auth instead of API billing — set exactly one
# # of the two; mapping both fails the job:
# # claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

Comment thread .github/workflows/claude-review.yml Outdated
Comment on lines +94 to +96
Anthropic API key. Provide this or claude-code-oauth-token; a step
fails the job when both are missing. Mapped by the caller, since the
secret name differs per repo.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — The description documents one half of the guard. It says the job fails "when both are missing" and stops there, so a caller reading the reusable workflow's own interface — the only place they see these secrets described in their editor — learns nothing about the both-set failure. claude-code-oauth-token's description below mentions no guard at all. The README carries the full rule; these two do not.

Suggested change
Anthropic API key. Provide this or claude-code-oauth-token; a step
fails the job when both are missing. Mapped by the caller, since the
secret name differs per repo.
Anthropic API key. Set exactly one of this and
claude-code-oauth-token; a step fails the job when both are missing,
and also when both are set. Mapped by the caller, since the secret
name differs per repo.

Comment thread .github/workflows/claude-review.yml Outdated
Comment on lines +205 to +206
echo "::error::pass only one of anthropic-api-key and claude-code-oauth-token; both are set,"
echo "::error::and they bill different accounts — which one wins would be undocumented."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — Two ::error:: lines for one sentence produce two separate annotations, the second beginning mid-sentence with "and they bill different accounts". Every other multi-line message in this file prefixes only the first line and leaves the continuation as a plain echo (lines 172-175, 184-188, and the neither branch just above), which keeps one annotation and puts the rest in the log.

Suggested change
echo "::error::pass only one of anthropic-api-key and claude-code-oauth-token; both are set,"
echo "::error::and they bill different accounts which one wins would be undocumented."
echo "::error::pass only one of anthropic-api-key and claude-code-oauth-token; both are set."
echo "They bill different accounts, so which one wins would be undocumented."

Review findings: both usage snippets mapped both secrets — live YAML,
not an either/or — which is exactly what the guard now fails on, so a
migrating caller copying the block would red every review. Comment
out the alternative line. Also document the both-set failure in both
secret descriptions, and emit the both-set error as one annotation.
# secrets:
# anthropic-api-key: ${{ secrets.CLAUDE_API_KEY }}
# # or, on subscription auth instead of API billing — set exactly one
# # of the two; mapping both fails the job:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — "mapping both fails the job" describes a mapping-based rule; the guard is value-based. It compares $API_KEY and $OAUTH_TOKEN, and GitHub gives a mapped-but-nonexistent secret the same empty string as an unmapped one, so mapping both lines passes whenever only one of the two repo secrets actually exists. The secret descriptions below get this right ("when both are set"); these two snippets — here and README.md:183 — do not.

The gap it hides is the one this repo's own guidelines put first. A caller mapping claude-code-oauth-token: ${{ secrets.CLAUDE_CODE_OATH_TOKEN }} — one transposed letter — resolves empty, the guard sees a single credential and passes, and the review runs on API billing while the caller believes it is on their subscription. Nothing says so. That is not fixable from here (Actions cannot distinguish "unmapped" from "mapped to nothing"), so the useful move is wording that does not promise a check the guard cannot make: "setting both fails the job" rather than "mapping both".

Not a repeat of the earlier thread on this line — that one was about the snippet shipping both mappings as live YAML, which this push fixed.

echo "::error::pass only one of anthropic-api-key and claude-code-oauth-token; both are set."
echo "They bill different accounts, so which one wins would be undocumented."
exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOW — This guard fails before the checkout, and the two if: always() steps at the end of the job then run against a workspace that has no tooling in it. Report denied tool requests (line 408) and Enforce review threshold (line 428) both invoke node .claude-review/tooling/review/*.mjs, and neither file exists yet, so each dies with Cannot find module. A caller who mapped neither secret, or both, sees three red steps and two module-resolution errors after the annotation that actually explains the problem.

The job still fails closed, so this is legibility only. It is also not new — Assert the triggering event and Check the access gate ran have the same shape above. What this change does is move it onto the common path: a trigger misconfiguration or a check-member infrastructure failure is rare, while "I mapped the wrong number of auth secrets" is the first thing every repo adopting the OAuth token gets wrong, and the step's own comment gives clearer failure as its reason for existing. steps.review.outcome != '', or a tooling-ready marker output, would keep the two tail steps out of a pre-checkout exit.

@bugclerk bugclerk changed the title Accept a Claude Code OAuth token as the auth secret NAS-142189 / 27.0.0-BETA.1 / Accept a Claude Code OAuth token as the auth secret Aug 15, 2026
@bugclerk

Copy link
Copy Markdown
Contributor

@william-gr
william-gr merged commit 24e74bf into master Aug 15, 2026
7 of 8 checks passed
@william-gr
william-gr deleted the feat/oauth-token-auth branch August 15, 2026 19:50
@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 15, 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.

2 participants