Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions devlog/_plan/261003_release_lane_a/001_evidence.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,13 @@
- Transport B: cherry-picked6508 with -x (0076dc5b0b), original author retained; added UTF-8 exact threshold, destination-away and AbortError executor tests. Focused initial green exit0. Removing byte conversion made the boundary tests fail (transport-mutation-red.log); source restored before broader affected transport checks. No retries or credential policy altered.

- Transport C/D: 199 passed,0 failed,1 older-runtime skip; mutation6fail. Typecheck/privacy/structure exit0; docs561 pages/77932links pass. Pinned Bun postinstall was completed after ignore-scripts prevented bun-run commands; no lockfile change. Independent code/security reviewer01a10221-1acd-7f90-92c9-ae96f95229f1 returned PASS, no blockers, four extra synthetic boundary probes passed. Next: publish transport and continue Messages.

- Messages P revalidation: transport D concluded reviewed upload bytes are ready; continue020 per roadmap. All three Messages runtime files remain identical to original architect/reflection/audit source. Same D1-D3/D5 decisions apply with accepted reachability clarifications; no credentials/compaction-policy edits. Transport published as PR6513, and Messages is a separate child branch pending parent integration.

- Messages B: new real HTTP Messages regressions first failed8 cases (502 collection/JSON, missing stream code, retry hint leak), then passed. Restoring all three pre-fix source files with current regressions caused13 failures; restored fixes afterward. A canonical native final-send fixture also asserts actual Messages SSE/JSON and exactly1 upstream send. Explicit Direct stored-main variant encountered the separately owned auth fence (requires caller bearer); used the existing canonical-forward fixture pattern without changing credential owners.
- Messages affected command from020 passed211 tests/0 failures across5 files. Defensive upstream JSON remained JSON into the ingress: stream true also returns HTTP400 as asserted. Existing endpoint negatives preserve unrelated failures.
- Composed real-client probe: isolated Claude Code2.1.288 -> proxy /v1/messages -> canonical native Responses executor redirected to synthetic upstream response.failed/context_length_exceeded. Exited1 in0.765s,3 bounded upstream requests, displays API Error400 and synthetic input-limit message, no timeout. Client uses known Claude model name mapped in isolated config to native GPT destination. It proves composed surfacing/termination, not zero retries, real account behavior or automatic compaction. First unrecognized fixture alias was rejected by client before upstream (0hits) and is not counted as passing evidence. Scratch script/log retained; no private conversation or external provider used.

- Test placement amendment: registering a new Messages test hit NEW_OVERSIZED at2000lines in test-layout-expected.json. Move the new cases into the existing claude-outbound.test.ts under a nested fixture scope (remains below2000), remove only our new registry row/file; preserve ratchet, no cap increase or formatting workaround. Runtime/interface/flow decisions unchanged.

- Messages C/D: after relocating fixtures,238 tests passed/0fail across7files including layout/ratchet; typecheck/privacy/structure exit0,docs561pages/77932links pass. Independent implementation/security reviewer01a10230-ce6c-75c3-b677-9edafdd2b12a PASS, blockers0; additional partial-output/cancellation/exact-code-negative probes passed. Next publish Messages child and complete exact-head hosted evidence; no claims of native account/automatic-compaction success.
10 changes: 5 additions & 5 deletions devlog/_plan/261003_release_lane_a/020_messages_context.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,17 @@ No change to core-auth.ts/core-options.ts/core-combo.ts or Responses retry/compa

## Test delta and activation

NEW tests/claude-integration/claude-messages-context-errors.test.ts, registered in both scripts/test-layout/layout.json explicit and tests/fixtures/test-layout-expected.json. Reuse isolatedCodexHome and per-test OPENCODEX_HOME helpers from adjacent endpoint tests; fixtures contain synthetic input and credentials only. Public handleClaudeMessages with configured openai-responses local upstream activates the actual error translation. Include Retry-After in the non-2xx context fixture to prove suppression; for failed JSON assert upstream content type and record whether earlier normalization consumes it. Use HTTP400 for the single-send non-2xx fixture; refusal/maintenance precedence stays covered separately by existing endpoint tests. Failed JSON must use a route/config that does not reframe it as SSE; if no public route reaches it, report the coverage limit instead of claiming activation. Parameterize stream true/false and terminal SSE/provider 413/non-2xx context error/failed JSON where constructible. Assert exact invalid_request_error + context_length_exceeded, HTTP400 for nonstream/JSON, no retry header, one upstream send, one terminal stream error and no message_stop. 413 fixture embeds a private-marker string in upstream body; final error must contain only proxy-safe copy. Add unknown and transient negative cases preserving existing classification. Cancel the response consumer and assert upstream cleanup/no extra dispatch where the harness can observe it.
MODIFY tests/claude-integration/claude-outbound.test.ts with a nested Messages-ingress describe and locally scoped home/server fixtures. Reuse this existing registered file: a new roster row would push test-layout-expected.json to the prohibited 2,000-line threshold. No ratchet increase or compressed manifest rows. Reuse isolatedCodexHome and per-test OPENCODEX_HOME helpers from adjacent endpoint tests; fixtures contain synthetic input and credentials only. Public handleClaudeMessages with configured openai-responses local upstream activates the actual error translation. Include Retry-After in the non-2xx context fixture to prove suppression; for failed JSON assert upstream content type and record whether earlier normalization consumes it. Use HTTP 400 for the single-send non-2xx fixture; refusal/maintenance precedence stays covered separately by existing endpoint tests. Failed JSON must use a route/config that does not reframe it as SSE; if no public route reaches it, report the coverage limit instead of claiming activation. Parameterize stream true/false and terminal SSE/provider 413/non-2xx context error/failed JSON where constructible. Assert exact invalid_request_error + context_length_exceeded, HTTP 400 for nonstream/JSON, no retry header, one upstream send, one terminal stream error and no message_stop. 413 fixture embeds a private-marker string in upstream body; final error must contain only proxy-safe copy. Add unknown and transient negative cases preserving existing classification. Cancel the response consumer and assert upstream cleanup/no extra dispatch where the harness can observe it.

MODIFY tests/claude-integration/claude-outbound.test.ts: update the existing classified invalid_request_error exact assertion to include context_length_exceeded; synthetic response.failed with status413 + context code and status-absent context code both produce invalid_request_error/context_length_exceeded without message_start or successful terminal. Explicit expectations, not parity.
MODIFY tests/claude-integration/claude-outbound.test.ts: update the existing classified invalid_request_error exact assertion to include context_length_exceeded; synthetic response.failed with status 413 + context code and status-absent context code both produce invalid_request_error/context_length_exceeded without message_start or successful terminal. Explicit expectations, not parity.

MODIFY tests/responses/protocol-direct-encoders-messages.test.ts: direct AdapterEvent error with code context_length_exceeded reaches streaming frame and folded HTTP400; keep explicit oracle assertions. Update the test's legacyFold fixture narrow status rule to mirror the new ingress, without deriving expectations from production helpers. Keep unknown failures and translator limits in existing cases.
MODIFY tests/responses/protocol-direct-encoders-messages.test.ts: direct AdapterEvent error with code context_length_exceeded reaches streaming frame and folded HTTP 400; keep explicit oracle assertions. Update the test's legacyFold fixture narrow status rule to mirror the new ingress, without deriving expectations from production helpers. Keep unknown failures and translator limits in existing cases.

Run explicit test files plus tests/responses/responses-context-overflow.test.ts and existing claude-messages-endpoint.test.ts. Direct Arguments observe each target; baseline three-file command passed143 tests (001); new-file and endpoint coverage will run after implementation. Exact implementation command: `bun test tests/claude-integration/claude-messages-context-errors.test.ts tests/claude-integration/claude-outbound.test.ts tests/claude-integration/claude-messages-endpoint.test.ts tests/responses/protocol-direct-encoders-messages.test.ts tests/responses/responses-context-overflow.test.ts`. Add source-oracle test layout/file-size checks if a new test file is added. No source cap is to be increased.
Run explicit test files plus tests/responses/responses-context-overflow.test.ts and existing claude-messages-endpoint.test.ts. Direct Arguments observe each target; baseline three-file command passed 143 tests (001); new-file and endpoint coverage will run after implementation. Exact implementation command: `bun test tests/claude-integration/claude-outbound.test.ts tests/claude-integration/claude-messages-endpoint.test.ts tests/responses/protocol-direct-encoders-messages.test.ts tests/responses/responses-context-overflow.test.ts`. Add source-oracle test layout/file-size checks if a new test file is added. No source cap is to be increased.

## Exact documentation additions

MODIFY structure/data-planes/inbound-compat.md: add a short Messages error projection paragraph naming src/claude/outbound.ts, src/protocols/encoders/messages.ts and src/server/claude-messages.ts: classified context_length_exceeded remains invalid_request_error through SSE and HTTP400 through collection/failed JSON; unknown transient/transport and local translator limits retain their distinct handling. No implicit recovery send.
MODIFY structure/data-planes/inbound-compat.md: add a short Messages error projection paragraph naming src/claude/outbound.ts, src/protocols/encoders/messages.ts and src/server/claude-messages.ts: classified context_length_exceeded remains invalid_request_error through SSE and HTTP 400 through collection/failed JSON; unknown transient/transport and local translator limits retain their distinct handling. No implicit recovery send.

MODIFY docs-site/src/content/docs/guides/claude-code.md after Auto context warning: explain that a model's advertised window is not a guarantee that a tool-heavy serialized request fits. A classified input-limit error is terminal; reduce current input or compact earlier. If manual compact also exceeds input limits, preserve original history and compact a fork with fewer enabled tool/MCP schemas if the client supports it. Do not promise that changing client accounting or a [1m] marker raises upstream limits; recommend inspecting actual model metadata rather than hardcoding reporter measurements as supported thresholds.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ Depends on verified transport and Messages changes. No runtime edits are pre-pla
6. NEW/UPDATE .tmp/release-stabilization/report.md with exact local/PR head, URL, source heads/credit/dispositions, test outputs, review evidence and residual native limitations. Do not expose private data. DONE only after all required applicable exact-head checks actually succeed and blocking reviews resolve.

Publication changes only Git history and PR metadata. Checklist claims are evidence-based; security review is technical, not a substitute for any maintainer decision.

## Prepared handoff

Transport PR6513 has independent code/security PASS and all applicable local checks. Its CodeRabbit threshold wording finding is fixed atdb094c49c7 and the thread resolved; corrected-head hosted CI remains the integration gate. Messages has independent code/security PASS and238 local tests plus typecheck/privacy/structure/docs success. Its child branch includes the parent threshold wording fix without runtime drift. Report and live exact-head CI/review snapshots are retained in ignored .tmp/release-stabilization; no merge or release is performed by this lane. This plan stays open for coordinator integration, rather than claiming the changes shipped.
9 changes: 9 additions & 0 deletions docs-site/src/content/docs/guides/claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,15 @@ Three config states:
The compaction value is adjustable on the Claude page. **Warning:** raising it past a model's real
window breaks that model — the chat errors out before the summary can fire.

A model's advertised context window does not guarantee that a tool-heavy request fits the
upstream input limit. A classified input-limit rejection reaches Claude Code as
`invalid_request_error` with `context_length_exceeded`; a non-streaming response uses HTTP 400
instead of a retryable 502. Reduce the current input or compact earlier. If `/compact` also
exceeds the limit, preserve the original history and try compacting a fork with fewer enabled
tool or MCP schemas, if your client supports that workflow. Recovery still depends on the
reduced request fitting the upstream limit. A `[1m]` marker or larger client accounting setting
does not raise that limit, and OpenCodex does not silently remove history or tools to make it fit.

Sub-1M native Anthropic models are never auto-marked. Values you export yourself always win (the
proxy uses YOUR value to decide which models are safe to mark). Invalid hand-edited config values
fall back to 829,800.
Expand Down
2 changes: 2 additions & 0 deletions src/claude/outbound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ export function anthropicIncompleteOutcome(
*/
export function anthropicFailedStatus(error: Rec, message: string): number {
const code = typeof error.code === "string" ? error.code : undefined;
if (code === "context_length_exceeded") return 400;
return code === "translation_buffer_limit"
? 413
: typeof error.status === "number"
Expand Down Expand Up @@ -726,6 +727,7 @@ export function responsesSseToAnthropicSse(
status,
message,
true,
code === "context_length_exceeded" ? code : undefined,
);
break;
}
Expand Down
7 changes: 4 additions & 3 deletions src/protocols/encoders/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,8 @@ function createAnthropicMessageWriter(
return;
}
const message = typeof error.message === "string" ? error.message : "upstream request failed";
fail(anthropicFailedStatus(error, message), message, true);
fail(anthropicFailedStatus(error, message), message, true,
error.code === "context_length_exceeded" ? error.code : undefined);
},
overflow() {
fail(413, "upstream translation buffer exceeded the safe limit", false, "translation_buffer_limit");
Expand All @@ -413,7 +414,7 @@ export function encodeAnthropicMessageSse(
/**
* Fold an encoded Messages stream into the non-streaming client response, with the status
* mapping the Messages ingress applied to its collected message: a translator overflow is 413,
* a stream that ended in an error frame is 502, anything else unexpected a 502 api_error.
* a classified context rejection is 400; other error frames/unexpected failures stay 502.
*/
export async function collectAnthropicMessageResponse(
stream: ReadableStream<Uint8Array>,
Expand Down Expand Up @@ -444,7 +445,7 @@ export async function collectAnthropicMessageResponse(
);
}
return new Response(JSON.stringify(message), {
status: isError ? 502 : 200,
status: isError ? (translatedError?.code === "context_length_exceeded" ? 400 : 502) : 200,
headers: { "Content-Type": "application/json" },
});
}
Expand Down
17 changes: 11 additions & 6 deletions src/server/claude-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1315,10 +1315,12 @@ async function handleClaudeMessagesWithBudget(
const replayRefusal = isReplayRefusalResponse(response);
// Re-shape the OpenAI-style error envelope into the Anthropic one, preserving status.
let message = `upstream error (${response.status})`;
let contextError = false;
try {
const text = await response.text();
try {
const parsed = JSON.parse(text) as { error?: { message?: string; type?: string } | string; message?: string };
const parsed = JSON.parse(text) as { error?: { message?: string; type?: string; code?: unknown } | string; message?: string };
contextError = typeof parsed?.error === "object" && parsed.error?.code === "context_length_exceeded";
const nested = typeof parsed?.error === "object" && parsed.error ? parsed.error.message : undefined;
const flat = typeof parsed?.error === "string" ? parsed.error : parsed?.message;
message = nested || flat || (text ? `upstream error (${response.status}): ${text.slice(0, 400)}` : message);
Expand All @@ -1327,7 +1329,7 @@ async function handleClaudeMessagesWithBudget(
}
} catch { /* keep fallback message */ }
const upstreamRetryAfter = response.headers.get("retry-after");
const retryAfter = replayRefusal
const retryAfter = replayRefusal || contextError
? undefined
: resolveClientRetryAfter({
status: response.status,
Expand All @@ -1347,10 +1349,10 @@ async function handleClaudeMessagesWithBudget(
const nativeMainFence = response.status === 503
&& upstreamRetryAfter?.trim() === "1"
&& message === CODEX_MAIN_PROFILE_MAINTENANCE_MESSAGE;
const transient = !replayRefusal && !nativeMainFence && isTransientUpstreamStatus(response.status);
const transient = !replayRefusal && !nativeMainFence && !contextError && isTransientUpstreamStatus(response.status);
const outStatus = replayRefusal
? REPLAY_REFUSED_STATUS
: nativeMainFence ? 503 : transient ? 529 : response.status;
: nativeMainFence ? 503 : contextError ? 400 : transient ? 529 : response.status;
const outHeaders = new Headers({ "Content-Type": "application/json" });
if (retryAfter) outHeaders.set("Retry-After", retryAfter);
else if (transient) outHeaders.set("Retry-After", "2");
Expand All @@ -1359,7 +1361,7 @@ async function handleClaudeMessagesWithBudget(
outStatus,
message,
undefined,
replayRefusal ? UPSTREAM_RESET_REPLAY_REFUSED_CODE : undefined,
replayRefusal ? UPSTREAM_RESET_REPLAY_REFUSED_CODE : contextError ? "context_length_exceeded" : undefined,
)), {
status: outStatus,
headers: outHeaders,
Expand Down Expand Up @@ -1410,7 +1412,7 @@ async function handleClaudeMessagesWithBudget(
);
}
return new Response(JSON.stringify(message), {
status: isError ? 502 : 200,
status: isError ? (translatedError?.code === "context_length_exceeded" ? 400 : 502) : 200,
headers: { "Content-Type": "application/json" },
});
}
Expand All @@ -1433,6 +1435,9 @@ async function handleClaudeMessagesWithBudget(
"translation_buffer_limit",
);
}
if (error?.code === "context_length_exceeded") {
return anthropicErrorResponse(400, error.message ?? "upstream context limit exceeded", "invalid_request_error", error.code);
}
return anthropicErrorResponse(502, error?.message ?? "upstream request failed", "api_error");
}
let message: Rec;
Expand Down
9 changes: 9 additions & 0 deletions structure/data-planes/inbound-compat.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,15 @@ its defaults and exclusions are owned by [Responses transport](../transports/res

The provider summary default applies at Responses ingress; native Chat and Anthropic inbound preferences keep their existing handling. Raw content is never renamed to a summary. See [bridge contract](../providers/chat-compat.md).

## Claude context rejection

Claude Messages preserves the classified `context_length_exceeded` error through
`src/claude/outbound.ts`, `src/protocols/encoders/messages.ts`, and
`src/server/claude-messages.ts`. Streaming output carries one `invalid_request_error`
terminal with that code; collected and failed-JSON responses return HTTP 400 without
a retry hint. This mapping adds no recovery send or context pruning. Unknown upstream
failures, replay refusal, and local translation-buffer limits retain their distinct handling.

## Claude affinity at final Go dispatch

`src/server/claude-messages.ts` carries validated conversation affinity privately through
Expand Down
Loading
Loading