Repository navigation
feat(claude): restore configurable OAuth billing entrypoint - #7
Conversation
…RYPOINT
Anthropic meters `cli`-labelled third-party OAuth traffic against the account's extra-usage balance ("You're out of extra usage" 400s), while the Agent SDK entrypoint (`sdk-cli`) counts as plan usage. This affects even the official Claude Code (anthropics/claude-code#45203).
Add an opt-in `CLAUDE_CC_ENTRYPOINT` env var (`cli`|`sdk-cli`, default `cli` — no behavior change), routed through one helper that sets both the `cc_entrypoint` field of `x-anthropic-billing-header` and the matching `(external, <entrypoint>)` claude-cli User-Agent suffix together, across all native Claude OAuth sites (executor, identity bootstrap, oauth provider). Same wire image the CC-Compatible provider already uses.
Only the native Claude OAuth path is affected; API-key requests are unchanged. Adds unit tests (default / sdk-cli / explicit-cli / whitespace / invalid-fallback).
(cherry picked from commit fe2eb72)
(cherry picked from commit 7cc2066)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 076acd9c83
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return getClaudeCodeUserAgent(getClaudeEntrypoint()); | ||
| } | ||
|
|
||
| export const CLAUDE_CLI_USER_AGENT = claudeCliUserAgent(); |
There was a problem hiding this comment.
Keep static Claude headers on the CLI entrypoint
When CLAUDE_CC_ENTRYPOINT=sdk-cli, this module-level constant also changes getClaudeCliHeaders() in open-sse/config/providers/shared.ts, which the Claude registry snapshots for every connection. Consequently, a native Claude API-key request that does not enter the OAuth/Claude-Code cloak still sends the sdk-cli User-Agent, despite the new setting being documented as OAuth-only and API-key requests being explicitly described as unaffected. Keep this static registry identity on cli and use the dynamic helper only in the OAuth-specific request paths.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Triage complete against current main. The version-parameter concern is fixed; two OAuth header-scope/override defects remain and need a focused follow-up. — automated pr-review-sweep
There was a problem hiding this comment.
Valid catch. Fixed in PR #9: CLAUDE_CLI_USER_AGENT is now a hardcoded literal claude-cli/${CLAUDE_CLI_VERSION} (external, cli) — it no longer calls claudeCliUserAgent() at module load, so CLAUDE_CC_ENTRYPOINT=sdk-cli cannot leak into API-key connection headers via getClaudeCliHeaders(). The dynamic helper is only called at native OAuth call sites in base.ts.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e91eb8d. Configure here.
… t06 gate (diegosouzapw#9779) The release-green verdict (diegosouzapw#9737) lists check:route-validation:t06 as a HARD failure and it is STILL red on the current tip: four routes call request.json() and hand-roll `typeof x === "string"` checks instead of using Zod, which Hard Rule #7 requires and the gate enforces (it scans source and has no allowlist). - src/app/api/plugins/marketplace/install (diegosouzapw#9445): InstallBodySchema; the 400 'Missing or invalid name field' response is preserved verbatim. - src/app/api/services/dario/admin/accounts (diegosouzapw#8523): DeleteAccountBodySchema for the optional { alias } DELETE body; query-param path untouched. - src/app/api/services/dario/admin/login-start (diegosouzapw#8523): LoginStartBodySchema; trimming now happens in the schema, so the forward body is unchanged. - src/app/api/services/dario/admin/import-from-omniroute (diegosouzapw#8523): ImportBodySchema for connectionId/alias; invalid shapes fall back to the same 'connectionId is required' 400 as before. All four keep their exact status codes and messages — this is a validation mechanism swap, not a contract change (plugins route suite still 33/33). Adds tests/unit/route-body-validation-t06.test.ts, which runs the gate's own rule inside the unit suite so the next such route fails on ITS OWN PR instead of surfacing weeks later in a base-red sweep. Guard verified by mutation: renaming .safeParse( in one route makes it fail (1 fail), restored from a pre-probe copy. Gates: route-validation:t06, file-size, test-discovery, mutation-test-coverage, dead-code exit 0; typecheck:core clean; eslint clean. Refs diegosouzapw#9737 Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
…e — every handler 500'd (diegosouzapw#9737) (diegosouzapw#9785) * fix(api): validate request bodies with Zod in 4 routes — restores the t06 gate The release-green verdict (diegosouzapw#9737) lists check:route-validation:t06 as a HARD failure and it is STILL red on the current tip: four routes call request.json() and hand-roll `typeof x === "string"` checks instead of using Zod, which Hard Rule #7 requires and the gate enforces (it scans source and has no allowlist). - src/app/api/plugins/marketplace/install (diegosouzapw#9445): InstallBodySchema; the 400 'Missing or invalid name field' response is preserved verbatim. - src/app/api/services/dario/admin/accounts (diegosouzapw#8523): DeleteAccountBodySchema for the optional { alias } DELETE body; query-param path untouched. - src/app/api/services/dario/admin/login-start (diegosouzapw#8523): LoginStartBodySchema; trimming now happens in the schema, so the forward body is unchanged. - src/app/api/services/dario/admin/import-from-omniroute (diegosouzapw#8523): ImportBodySchema for connectionId/alias; invalid shapes fall back to the same 'connectionId is required' 400 as before. All four keep their exact status codes and messages — this is a validation mechanism swap, not a contract change (plugins route suite still 33/33). Adds tests/unit/route-body-validation-t06.test.ts, which runs the gate's own rule inside the unit suite so the next such route fails on ITS OWN PR instead of surfacing weeks later in a base-red sweep. Guard verified by mutation: renaming .safeParse( in one route makes it fail (1 fail), restored from a pre-probe copy. Gates: route-validation:t06, file-size, test-discovery, mutation-test-coverage, dead-code exit 0; typecheck:core clean; eslint clean. Refs diegosouzapw#9737 * fix(memory): register the sqlite backend on the /api/memory/[id] route — every handler 500'd GET/PUT/DELETE /api/memory/[id] threw `Primary backend "sqlite" not registered` and returned 500. diegosouzapw#8752 (MemoryBackend provider pattern) wired the route to `@/lib/memory/manager` directly, but the registry is populated by an import-time side effect in the module INDEX (src/lib/memory/index.ts:23, `memoryManager.register(sqliteBackend)`). Importing the bare manager gives an empty registry. In production the failure is order-dependent, which is why it went unnoticed: if /api/memory (which imports the index) is hit first in the same process, the singleton is already populated and [id] works. Reached first — the common case for a client that edits a known memory id — every request 500s. The sibling route is the only other consumer and already imports the index; this was the lone direct-manager import in src/. - Fix: import from `@/lib/memory` (index) with a comment stating WHY the indirection matters, so the next refactor does not simplify it back. - Guard: tests/integration/memory-route-put.test.ts already covered this and was failing 2/5 on the base (it only surfaced now because the integration suite runs on the release-PR CI, not per-PR). Now 5/5. Also fixes a test-isolation defect in the same run: tests/integration/combo-matrix/context-relay-codex.test.ts reused one combo name across both tests, and the control failed with `UNIQUE constraint failed: combos.name` — resetStorage() unlinks the DB file but the previous better-sqlite3 handle keeps writing to the same inode. Gave the control its own combo name and parameterized the request builder; the assertion is unchanged (it never depended on the name). 2/2. Integration suite on this tip: 936 tests, 32m19s — under the 40min ceiling the old verdict reported as exceeded (diegosouzapw#9737 item 6), which the migration-135 collision was causing. Refs diegosouzapw#9737 --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>

Restores the fork's core CLAUDE_CC_ENTRYPOINT patch on the current release/v3.8.50 line.\n\n- keeps cc_entrypoint and Claude CLI User-Agent suffix aligned\n- supports cli and sdk-cli, defaulting safely to cli\n- covers executor, identity bootstrap, and OAuth provider paths\n- restores the existing unit test and env documentation\n\nProduction currently sets CLAUDE_CC_ENTRYPOINT=sdk-cli, but its deployed standalone bundle does not contain this patch, so that setting is currently inert. This PR restores the code required for the setting to work in the next verified deployment.