fix(pi): Claude Code billing rejection and missing claude-opus-5 - #147
Open
unspecd-dev wants to merge 5 commits into
Open
fix(pi): Claude Code billing rejection and missing claude-opus-5#147unspecd-dev wants to merge 5 commits into
unspecd-dev wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 5 files
Architecture diagram
sequenceDiagram
participant UI as "Pi TUI / User"
participant Ext as "Pi Startup\ncortexKitPiAnthropicAuth"
participant Catalog as "Provider Model Catalog\n(index.ts)"
participant Convert as "Request Converter\n(convert.ts)"
participant Pkg as "@cortexkit/anthropic-auth-core"
participant API as "Anthropic API"
Note over UI,API: PR focus: Claude Code billing request shape\nand claude-opus-5 model availability
rect rgb(240,240,240)
Note over UI,Catalog: A. Model registration (claude-opus-5)
UI->>Ext: /model command
Ext->>Catalog: register provider models
Catalog->>Catalog: include claude-opus-5 (reasoning,\ncost, context window, max tokens)
Catalog-->>UI: model appears in /model list
end
rect rgb(240,240,240)
Note over UI,API: B. Request conversion and billing header flow
UI->>Convert: send user message\n(e.g. "hi")
Convert->>Pkg: fetch Claude Code billing header\n(x-anthropic-billing-header)
Pkg-->>Convert: cc_version, cch
Convert->>Convert: sanitize Pi system prompt\n(no Pi identity fingerprint)
Convert->>Convert: build system[] array
Note over Convert: system[] now contains ONLY:\n[0] billing header (added later)\n[1] Claude Code identity block\nPi prompt relocated out of system[]
Convert->>Convert: find first user message\nin messages[] history
alt First user message is a plain string
Convert->>Convert: prepend prompt to string\n-> "You are an expert coding assistant...\n\nhi"
else First user message is structured content (array)
Convert->>Convert: unshift text block containing prompt
else No user message present
Convert->>Convert: append prompt to system[]\n[NOTE: fallback recreates\nrejected 3-entry shape]
end
Convert->>Convert: addEphemeralCacheControl\nanchors on system.at(-1)\n= Claude Code identity block
Convert->>Convert: buildBillingHeaderValue\ncomputes cch over user message\nBEFORE prompt prepend
Convert->>API: POST /messages\nwith system[] (2 entries)\n+ user message with prompt
alt HTTP 200 OK
API-->>UI: completion (model accepted,\nbilling OK)
else HTTP 400 (only if fallback path\nrecreates 3-entry system[])
API-->>Convert: "You're out of extra usage"
end
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two fixes in
packages/pi, both reproduced against the released v1.19.0 and verified on a clean macOS VM.400 You're out of extra usageon a valid Claude subscription. Pi's system prompt is pushed intosystem[]alongside the Claude Code identity block.claude-opus-5is not selectable in Pi, despite feat: support Claude Opus 5 #143 adding Opus 5 request conversion and documenting/claude-fastsupport for it.1.
fix(pi): relocate system prompt out of system[] for Claude Code billingSymptom
Every Claude request from Pi returns:
Reproduced on
claude-opus-4-8andclaude-sonnet-5. Despite the message, this is not a quota problem: the dumped request is well-formed Claude Code traffic — correctuser-agent, the fullanthropic-betaset, the stainless headers, and a validx-anthropic-billing-headercarryingcc_versionandcch— and the same account works in OpenCode with@cortexkit/opencode-anthropic-auth.Cause
packages/pi/src/convert.tspushes Pi's system prompt as a third entry insystem[]:Empirically, Anthropic rejects OAuth requests carrying Claude Code billing headers when third-party system content appears in
system[]alongside the identity block. Removing that third entry is sufficient to make the request succeed; nothing else about the request changes.The OpenCode package is unaffected because
sanitizeSystemTextstrips content anchored onOPENCODE_IDENTITY_PREFIX. There is no Pi equivalent, so Pi's prompt passes through untouched.Released package (1.19.0) —
status: 400,systemCount: 3,message0Bytes: 30:This branch —
status: 200,systemCount: 2,message0Bytes: 3586:Same subscription (
account_uuidunchanged), same model, same tool set, samemax_tokensand thinking config. The two captures come from different pi sessions and working directories, so the session metadata, thecchvalue and the prompt text itself differ; thesystem[]shape is the variable under test.Fix
Keep only the billing header and identity block in
system[]; prepend the sanitized prompt to the first user message, where it is functionally equivalent. When no user message exists, the prompt is dropped rather than pushed back intosystem[]; that path only occurs whenmessages[]is already empty.Two notes for review
Cache anchor.
addEphemeralCacheControlanchors onbody.system.at(-1), previously Pi's system prompt and now the identity block. Caching still works across turns: a follow-up request in the same session — after a model switch fromclaude-opus-4-8toclaude-opus-5— carried an identicalmessage0Hashandmessage0Bytes. Flagging in case the shorter anchor has implications I have not considered.Billing-header ordering.
buildBillingHeaderValueruns before the relocation, socchis computed over the first user message as it was before the prompt was prepended. Anthropic accepted the request either way in testing, but you may prefer to compute the header after relocation so the hash matches the transmitted body.2.
fix(pi): register claude-opus-5 in the provider model list#143 added Opus 5 request conversion in
packages/pi/src/convert.ts, wiring upisClaudeOpus5ModelandCLAUDE_OPUS_5_ADAPTIVE_THINKINGfrom core, and updatedpackages/pi/README.mdto document/claude-fastsupport forclaude-opus-5. The model was never added to themodelsarray inpackages/pi/src/index.ts— that file was last touched by #118.Without that entry the Opus 5 code path is unreachable from Pi, so the documented model does not appear in
/model. The cost entry follows Anthropic's published rates for Opus 5: $5/MTok input, $25/MTok output, $0.50/MTok cache reads and $6.25/MTok cache writes.A third commit updates
packages/pi/README.md, which enumerates the provider catalog and would otherwise omit the newly registered model.Tests
The relocation changes the shape of
messages[0], whichpackages/pi/src/tests/convert.test.tsasserts on through its sharedbuildMessageshelper. That helper hard-codedsystemPrompt: 'test'for every case, including tests concerned only with message conversion, so the relocation broke eight of them.systemPromptis now an optional parameter: those cases assert raw conversion output as before, and the relocation gets its own coverage —system[]staying at two entries, the string and structured first-user-message paths, the no-user-message path dropping the prompt, and the identity block remaining the lastsystem[]entry that the ephemeral cache anchors on.packages/pi/src/tests/index.test.tsgains an Opus 5 registration case alongside the existing Sonnet 5 one.Two notes on layout. The test commit lands after the first two fix commits, so
fix(pi): relocate system prompt...is red in isolation; the commits are split that way to keep the two bugs independently revertable. And the roottestscript iscd packages/opencode && bun run test, sopackages/pi's own suite is not reached by CI — it runs withbun run --cwd packages/pi test.Verification
Clean macOS VM —
brew install pi-coding-agent0.84.1,pi install npm:@cortexkit/pi-anthropic-auth@1.19.0, authenticated with Pi's/login anthropic. The released package reproduced both bugs. This branch was then cloned, built withbun run build, and itspackages/pi/distinstalled over the released package'sdist; core was left at the installed 1.19.0.bun run typecheck,bun run build,bun run test(1018 pass),bun run --cwd packages/pi test(64 pass),bun run lintandbun run format:checkare all clean.claude-opus-4-8returns 200 on this branch where the released package returns 400, as dumped above.claude-opus-5now appears in/modeland returns 200:confirming the
CLAUDE_OPUS_5_ADAPTIVE_THINKINGpath executes. Anthropic validates model IDs — an unrecognised id returns404 not_found_error— so a successful response confirms the model string was accepted.Prior art: pankajudhas81/pi-claude-auth —
src/transforms.tsdocuments the same Anthropic behaviour independently and applies the same relocation.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Fixes Claude Code OAuth billing rejections by keeping Pi’s prompt out of
system[], and makesclaude-opus-5selectable in Pi’s model list.system[]; prepend the sanitized Pi prompt to the first user message, and drop the prompt when no user message exists. Restores successful requests and keeps cache anchoring on the identity block.claude-opus-5to the provider catalog with reasoning enabled, costs{ input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },contextWindow: 1_000_000,maxTokens: 128_000. Updatedpackages/pi/README.md.Written for commit 9c0ec08. Summary will update on new commits.
Greptile Summary
This PR prevents Claude Code OAuth billing rejection by relocating Pi’s system prompt to the first user message and registers Claude Opus 5 in Pi’s provider catalog.
system[]limited to the billing header and Claude Code identity, including when conversion produces no user message.Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Reviews (2): Last reviewed commit: "fix(pi): drop the system[] fallback for ..." | Re-trigger Greptile
Context used: