Skip to content

Harden Claude subscription path + restore coding quality through the proxy - #6

Merged
0xPuncker merged 2 commits into
mainfrom
feat/run-podman-containers
Jun 24, 2026
Merged

Harden Claude subscription path + restore coding quality through the proxy#6
0xPuncker merged 2 commits into
mainfrom
feat/run-podman-containers

Conversation

@0xPuncker

Copy link
Copy Markdown
Owner

Summary

Three intertwined fixes to the Claude subscription (OAuth) provider path, plus a Docker build fix. Coding requests routed through the proxy were noticeably worse than talking to Claude directly — the root cause was the proxy silently stripping the request settings that drive coding intelligence, plus a couple of correctness bugs on the subscription path.

Background

Live log analysis (podman logs cc-proxy) showed this on every request:

stripped fields: context_management, output_config
stripped N historical thinking block(s) for subscription

Changes

1. Coding-quality passthrough (src/index.ts — the headline fix)

cleanBody was a hardcoded allow-list that omitted output_config, so every request lost it. Claude Code puts effort (it sends effort: \"xhigh\" for Opus 4.7/4.8 — the setting Anthropic calls out as best for coding/agentic work), task_budget, and format inside output_config. Stripped → the API silently fell back to high, reducing reasoning/agentic depth on every coding request.

  • cleanBody is now provider-aware: for subscription/anthropic it passes the body through untouched (only model-remap + message-clean); the strict allow-list is kept only for zai/openrouter, which genuinely need the translated shape. Future-proofs against new client fields.
  • cleanMessages no longer strips all historical thinking blocks on the subscription path. It now keeps signed thinking blocks + all redacted_thinking (preserving cross-turn reasoning continuity — what direct Claude does), dropping only unsigned (contaminated) blocks that would 400. Gated by STRIP_SUBSCRIPTION_THINKING (unsigned default | all legacy | none).
  • Fixed mapModel OpenRouter mapping: claude-opus-4-8/4-7/4-6 + claude-opus now map to ~anthropic/claude-opus-latest instead of being silently downgraded to Sonnet on OpenRouter fallback.

2. Tool-use ID sanitization (src/index.ts)

A conversation whose earlier turns were served by a fallback provider (GLM/Z.AI) accumulates tool-use IDs that fail Anthropic's strict ^srvtoolu_ / ^toolu_ validation, 400-ing every replay (messages.N.content.M.server_tool_use.id: String should match pattern ...). Added sanitizeToolUseIds: rewrites non-conforming IDs and remaps every paired tool_use_id reference in the same pass (subscription + anthropic paths).

3. OAuth auto-refresh (src/index.ts, src/types.ts)

The proxy now redeems the refresh token itself when the access token is near expiry or on a 401, persisting rotated credentials atomically (temp file + rename) under a single-flight mutex (Anthropic rotates the refresh token on every use). Removes dependence on the external credential-sync job. Configurable via CLAUDE_OAUTH_REFRESH_URL / CLAUDE_OAUTH_CLIENT_ID / CLAUDE_OAUTH_REFRESH_SKEW_MS.

4. Docker build fix (Dockerfile)

The production image re-ran npm ci --only=production, which dropped the pg runtime dependency (and the generated Prisma client). Reuse the builder's node_modules instead and fail the build loudly if pg is missing.

Verification

  • tsc --noEmit + eslint clean
  • 102/102 tests pass (93 prior + 9 new: passthrough for subscription/anthropic, still-strip for zai/openrouter, all three thinking modes, Opus→Opus mapping, sanitization pairing, refresh behavior)
  • Built and deployed; live logs confirm output_config is no longer stripped, thinking-strip drops only contaminated blocks, zero new subscription 400s after keeping signed thinking.

Operational notes

  • The proxy must mount the credentials file read-write so rotated tokens can be persisted (compose already has :rw).
  • To revert the thinking-strip change instantly if a 400 appears: set STRIP_SUBSCRIPTION_THINKING=all and recreate the container (no rebuild).

The final image re-ran 'npm ci --only=production', which dropped the 'pg'
runtime dependency (and the generated Prisma client), breaking the proxy
at startup. Reuse the builder's already-installed node_modules instead,
and fail the build loudly if pg is missing.
Three intertwined fixes to the Claude subscription (OAuth) provider:

1. OAuth auto-refresh: the proxy now redeems the refresh token itself when
   the access token is near expiry or on a 401, persisting the rotated
   credentials atomically with a single-flight mutex. Removes dependence on
   the external (and here, broken) credential-sync job.

2. Tool-use ID sanitization: a conversation whose earlier turns were served
   by a fallback provider (GLM/Z.AI) accumulates tool-use IDs that fail
   Anthropic's strict ^srvtoolu_ / ^toolu_ validation, 400-ing every replay.
   Rewrite non-conforming IDs, remapping paired tool_use_id references in
   the same pass (subscription + anthropic paths).

3. Coding quality passthrough: cleanBody was an allow-list that stripped
   output_config (effort: xhigh, task_budget) and context_management on
   every request, and cleanMessages stripped ALL historical thinking blocks.
   For the Anthropic-compatible paths, pass top-level fields through
   untouched and keep signed thinking blocks (drop only unsigned/contaminated
   ones). Also fix the OpenRouter Opus->Sonnet downgrade.

102 tests pass (93 prior + 9 new).
@0xPuncker
0xPuncker merged commit 0d9b15f into main Jun 24, 2026
3 checks passed
@0xPuncker
0xPuncker deleted the feat/run-podman-containers branch June 24, 2026 14:22
@0xPuncker
0xPuncker restored the feat/run-podman-containers branch June 24, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant