RewardKit can't grade on a Claude subscription end to end. Three independent bugs force a subscriber onto the paid Console API key — or crash the verifier outright.
Background
CLAUDE_CODE_OAUTH_TOKEN (sk-ant-oat01…) authenticates on a Claude subscription (Pro/Max) — free.
ANTHROPIC_API_KEY (sk-ant-api03…) authenticates on the Console account — metered/paid.
These are different Anthropic orgs. The OAuth token only works as an OAuth bearer (litellm auto-detects the sk-ant-oat prefix and routes it correctly); it 401s if sent as x-api-key.
Environment: harbor-rewardkit==0.1.4, litellm 1.86.2.
What main already supports (and where it stops half-way)
Subscription grading is already partly wired in main — each bug is the point where that support stops short, not a net-new feature:
Already in main |
Where it stops short (the bug) |
The claude-code agent judge already grades on the subscription — it shells out to the claude CLI, which inherits CLAUDE_CODE_OAUTH_TOKEN. This is the working free path today. |
A stray container ANTHROPIC_API_KEY silently shadows that OAuth (CLI auth precedence), breaking the working path — Console bill, or 401 crash on an invalid key. → Bug 3 |
litellm itself already authenticates a subscription via ANTHROPIC_AUTH_TOKEN (OAuth bearer). |
The litellm LLM judge never passes the OAuth token to litellm — it calls acompletion() with no api_key, so the subscription is unreachable from that judge. → Bug 1 |
REWARDKIT_JUDGE already globalizes judge selection (runner.py:79). |
There is no parallel REWARDKIT_MODEL, so a global REWARDKIT_JUDGE=claude-code silently drops the rubric's pinned model. → Bug 2 |
So this is finishing half-built support, not adding a new capability.
Bug 1 — litellm LLM judge ignores the subscription token
Expected: with only CLAUDE_CODE_OAUTH_TOKEN set, an anthropic/* LLM judge grades on the subscription.
Actual: it fails — judges._arun_llm_call calls litellm.acompletion(model=…) with no api_key, so litellm reads ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN from the env and never the OAuth token.
unset ANTHROPIC_API_KEY
export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-... # a real subscription token
# rubric: [judge] judge = "anthropic/claude-sonnet-4-6"
uvx --from 'harbor-rewardkit==0.1.4' rewardkit tests/
litellm.exceptions.AuthenticationError: AnthropicException - Missing Anthropic API Key -
A call is being made to anthropic but no key is set ... Please set `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN`
Bug 2 — REWARDKIT_JUDGE=claude-code silently drops the pinned model
Expected: a rubric that pins its model survives a global judge override.
Actual: REWARDKIT_JUDGE overrides the whole [judge].judge field. When a rubric encodes its model there (judge = "anthropic/claude-opus-4-8"), overriding judge to the agent name claude-code leaves the agent's model = None, so the claude CLI runs its built-in default model — the pinned model vanishes with no error. There is no global REWARDKIT_MODEL to set the model once.
# rubric: [judge] judge = "anthropic/claude-opus-4-8"
export REWARDKIT_JUDGE=claude-code
uvx --from 'harbor-rewardkit==0.1.4' rewardkit tests/
# → agent judge runs the claude CLI's DEFAULT model, not opus-4-8 (silent)
Bug 3 — stray ANTHROPIC_API_KEY shadows OAuth in the agent judge
Expected: the claude-code agent judge grades on the subscription via CLAUDE_CODE_OAUTH_TOKEN.
Actual: the CLI's auth precedence puts ANTHROPIC_API_KEY above CLAUDE_CODE_OAUTH_TOKEN, so a key present in the verifier container ([verifier.env]) wins: a valid key silently grades on Console (paid); an invalid key 401s and the judge crashes with no reward written.
export CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...
export ANTHROPIC_API_KEY=sk-ant-api03-invalid # stray / invalid key in the container
# rubric: [judge] judge = "claude-code"
uvx --from 'harbor-rewardkit==0.1.4' rewardkit tests/
ValueError: Agent CLI 'claude' exited with code 1:
{"is_error":true,"api_error_status":401, ... "result":"Invalid API key · Fix external API key"}
→ RewardFileNotFoundError (no reward.json written)
Note: this only bites when ANTHROPIC_API_KEY reaches the verifier container ([verifier.env] is an explicit allowlist). A host-only .env value is harmless; an empty value is ignored by the CLI; only a non-empty invalid key crashes, and a valid one silently bills Console.
Fix
Proposed in #1770 — resolves each path free-first behind one opt-out flag (REWARDKIT_FORCE_API_KEY=1 keeps the paid API key for bugs 1 & 3), and adds REWARDKIT_MODEL to pin the model globally. Companion to #1766 (same auth for harbor check / harbor analyze).
RewardKit can't grade on a Claude subscription end to end. Three independent bugs force a subscriber onto the paid Console API key — or crash the verifier outright.
Background
CLAUDE_CODE_OAUTH_TOKEN(sk-ant-oat01…) authenticates on a Claude subscription (Pro/Max) — free.ANTHROPIC_API_KEY(sk-ant-api03…) authenticates on the Console account — metered/paid.These are different Anthropic orgs. The OAuth token only works as an OAuth bearer (litellm auto-detects the
sk-ant-oatprefix and routes it correctly); it 401s if sent asx-api-key.Environment:
harbor-rewardkit==0.1.4,litellm 1.86.2.What main already supports (and where it stops half-way)
Subscription grading is already partly wired in
main— each bug is the point where that support stops short, not a net-new feature:mainclaude-codeagent judge already grades on the subscription — it shells out to theclaudeCLI, which inheritsCLAUDE_CODE_OAUTH_TOKEN. This is the working free path today.ANTHROPIC_API_KEYsilently shadows that OAuth (CLI auth precedence), breaking the working path — Console bill, or 401 crash on an invalid key. → Bug 3ANTHROPIC_AUTH_TOKEN(OAuth bearer).acompletion()with noapi_key, so the subscription is unreachable from that judge. → Bug 1REWARDKIT_JUDGEalready globalizes judge selection (runner.py:79).REWARDKIT_MODEL, so a globalREWARDKIT_JUDGE=claude-codesilently drops the rubric's pinned model. → Bug 2So this is finishing half-built support, not adding a new capability.
Bug 1 — litellm LLM judge ignores the subscription token
Expected: with only
CLAUDE_CODE_OAUTH_TOKENset, ananthropic/*LLM judge grades on the subscription.Actual: it fails —
judges._arun_llm_callcallslitellm.acompletion(model=…)with noapi_key, so litellm readsANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKENfrom the env and never the OAuth token.Bug 2 —
REWARDKIT_JUDGE=claude-codesilently drops the pinned modelExpected: a rubric that pins its model survives a global judge override.
Actual:
REWARDKIT_JUDGEoverrides the whole[judge].judgefield. When a rubric encodes its model there (judge = "anthropic/claude-opus-4-8"), overridingjudgeto the agent nameclaude-codeleaves the agent'smodel = None, so theclaudeCLI runs its built-in default model — the pinned model vanishes with no error. There is no globalREWARDKIT_MODELto set the model once.Bug 3 — stray
ANTHROPIC_API_KEYshadows OAuth in the agent judgeExpected: the
claude-codeagent judge grades on the subscription viaCLAUDE_CODE_OAUTH_TOKEN.Actual: the CLI's auth precedence puts
ANTHROPIC_API_KEYaboveCLAUDE_CODE_OAUTH_TOKEN, so a key present in the verifier container ([verifier.env]) wins: a valid key silently grades on Console (paid); an invalid key 401s and the judge crashes with no reward written.Note: this only bites when
ANTHROPIC_API_KEYreaches the verifier container ([verifier.env]is an explicit allowlist). A host-only.envvalue is harmless; an empty value is ignored by the CLI; only a non-empty invalid key crashes, and a valid one silently bills Console.Fix
Proposed in #1770 — resolves each path free-first behind one opt-out flag (
REWARDKIT_FORCE_API_KEY=1keeps the paid API key for bugs 1 & 3), and addsREWARDKIT_MODELto pin the model globally. Companion to #1766 (same auth forharbor check/harbor analyze).