Repository navigation
Claude Desktop 3P Auto Mode classifier retries after 64-token Anthropic OAuth outputs #545
Description
환경
- opencodex 2.7.41 (launchd 상주, port 10100)
- Claude Desktop 3P 모드 (gateway key 방식) + Claude Code 2.1.218~219
- anthropic provider = Claude OAuth (Pro/Max), Auto Mode classifier 모델 =
claude-sonnet-5 - macOS, Auto Mode(권한 자동판정) 켠 상태
증상
Auto Mode 세션에서 도구 호출마다 Claude Code가 Sonnet 권한 분류 요청을 보내는데, 이 요청이 정확히 output 64토큰에서 max_tokens로 잘리고, 같은 입력으로 최대 5회 연속 반복됩니다. 대시보드에는 502(upstream_server_error)로 쌓이고, 도구 하나 승인에 12~22초씩 걸립니다. 여러 세션/병렬 도구 사용 시 체감 속도가 크게 저하됩니다.
~/.opencodex/usage.jsonl 집계 (한 세션 며칠 누적):
status:502, terminalStatus:incomplete, upstreamError:"Upstream incomplete: max_output_tokens", outputTokens:64→ 1,084건- 64 미만에서 정상 종료(200) → 143건
- 동일 입력토큰 연속 클러스터 232개 (길이 5가 112개 — 재시도 상한 도달로 추정)
10:55:28 502 out=64 → 10:55:31 502 → 10:55:34 502 → 10:55:36 502 → 10:55:39 502
10:54:36 502 out=64 → 10:54:38 502 → 10:54:42 200 out=63 ← 태그가 64토큰 안에 닫힌 경우
원인 분석 (Claude Code 바이너리 + opencodex 소스 대조)
Claude Code의 Auto 분류기는 max_tokens 64, stop_sequences: ["</block>"](또는 </severity>), skipSystemPromptPrefix로 짧은 XML 판정을 받는 설계입니다. 64토큰 안에 태그가 안 닫히면 파싱 실패로 같은 요청을 재시도합니다.
Desktop 3P는 gateway key(ocx)라 wantsNativePassthrough()(src/server/claude-messages.ts:91)가 false → Messages→Responses→Messages 이중 번역 경로를 타는데, 이 과정에서 원 요청과 달라지는 점:
- identity 블록 강제 삽입 — anthropic OAuth 경로에서
CLAUDE_CODE_SYSTEM_INSTRUCTION을 system 첫 블록에 prepend (src/adapters/anthropic.ts). 원 요청은skipSystemPromptPrefix를 명시한 분류기 요청이라 1P 대비 프롬프트가 달라짐 - thinking/effort 관련 필드가 왕복 변환에서 소실 (실패 1,084건 전부
requestedEffort부재) - 이중 번역 자체로 인한 프롬프트 형태의 미세 변화
프롬프트가 1P와 달라지니 응답 길이 분포가 밀려 64토큰 상한에 걸리는 빈도가 급증하는 것으로 추정합니다. (1P 쪽 분류 요청은 관측 불가라 실패율 직접 비교는 못 했습니다 — 가설 부분은 검증 환영)
제안
- A (핵심): anthropic provider로 라우팅되는
claude-*모델은 gateway key여도 Anthropic body를 왕복 번역 없이 그대로 전달(auth/system 첫 블록 요건만 처리)하는 direct bridge 검토 - B (완화): inbound system에 이미 Claude Code identity가 있으면 중복 prepend 생략, thinking/effort 필드 보존
- C (별건): request log가
incomplete/max_output_tokens를 502upstream_server_error로 표기 (src/server/request-log.tshttpStatusForTerminalStatus) — 실제 HTTP는 200이고 서버 오류가 아니어서 진단에 혼선. 별도 상태/라벨 제안
재현: Desktop 3P + anthropic OAuth 라우팅 + Auto Mode 세션에서 도구 몇 개 실행 → usage.jsonl에서 sonnet/out=64/502 클러스터 확인. 로그 필요하면 제공 가능합니다.
Translated Message
Original language: Korean
Environment
- opencodex 2.7.41 (running on launchd, port 10100)
- Claude Desktop 3P mode (using gateway key) + Claude Code 2.1.218~219
- anthropic provider = Claude OAuth (Pro/Max), Auto Mode classifier model =
claude-sonnet-5 - macOS, Auto Mode (automatic permission determination) enabled
Symptoms
In the Auto Mode session, Claude Code sends a Sonnet permission classification request for each tool call, and this request is cut off exactly at 64 tokens by max_tokens, and is repeated up to 5 times consecutively with the same input. It accumulates 502 (upstream_server_error) on the dashboard, and it takes 12 to 22 seconds to approve a single tool. The perceived speed significantly decreases when using multiple sessions/parallel tools.
~/.opencodex/usage.jsonl aggregation (compiled over several days for one session):
status:502, terminalStatus:incomplete, upstreamError:"Upstream incomplete: max_output_tokens", outputTokens:64→ 1,084 occurrences- Normal termination (200) below 64 → 143 occurrences
- Continuous clusters of the same input tokens 232 (length 5 is 112 — estimated due to reaching retry limit)
10:55:28 502 out=64 → 10:55:31 502 → 10:55:34 502 → 10:55:36 502 → 10:55:39 502
10:54:36 502 out=64 → 10:54:38 502 → 10:54:42 200 out=63 ← case where tags close within 64 tokens
Cause Analysis (Comparing Claude Code binary + opencodex source)
The Auto classifier of Claude Code is designed to receive a short XML determination with max_tokens of 64, stop_sequences: ["</block>"] (or </severity>), and skipSystemPromptPrefix. If tags do not close within 64 tokens, it retries the same request due to parsing failure.
Desktop 3P is gateway key (ocx) so wantsNativePassthrough() (src/server/claude-messages.ts:91) returns false → Messages→Responses→Messages double translation path is taken, resulting in differences from the original request:
- Forced insertion of identity block — prepends
CLAUDE_CODE_SYSTEM_INSTRUCTIONto the first block of the system in the anthropic OAuth path (src/adapters/anthropic.ts). The original request is a classifier request explicitly statingskipSystemPromptPrefix, resulting in a different prompt compared to 1P. - Fields related to thinking/effort are lost during round-trip conversion (all 1,084 failed requests lack
requestedEffort) - Subtle changes in prompt form due to double translation itself
Due to the difference in prompt from 1P, it is presumed that the frequency of hitting the upper bound of 64 tokens increases drastically. (It is not possible to directly compare failure rates as the classifier requests on the 1P side are not observable — verification of this hypothesis is welcome)
Suggestions
- A (Core): Consider implementing a direct bridge that transmits
claude-*models routed via the anthropic provider without round-trip translation, preserving only the first block requirements of the auth/system. - B (Mitigation): If there is already a Claude Code identity in the inbound system, omit duplicate prepend and preserve thinking/effort fields.
- C (Other): The request log notes
incomplete/max_output_tokensas 502upstream_server_error(src/server/request-log.tshttpStatusForTerminalStatus) — the actual HTTP is 200 and not a server error, leading to confusion in diagnosis. A separate status/label suggestion.
Reproduction: Run a few tools in the session with Desktop 3P + anthropic OAuth routing + Auto Mode → check for sonnet/out=64/502 clusters in usage.jsonl. Logs can be provided if needed.
Activity
[-]Desktop 3P + Auto Mode: Sonnet 권한 분류기 요청이 64토큰 잘림 후 최대 5회 반복 (이중 번역 경로 영향 추정)[/-][+]Desktop 3P + Auto Mode: Request for Sonnet Permission Classifier Cuts Tokens at 64 and Repeats Up to 5 Times (Estimating Impact of Double Translation Path)[/+]github-actions commentedon Jul 27, 2026
Automated translation bookkeeping — detected language: unknown.
Ingwannu commentedon Jul 27, 2026
I checked this against the current dev path. Part C is confirmed: a routed Responses turn that ends with response.incomplete / max_output_tokens is translated back to Anthropic stop_reason=max_tokens and the HTTP stream remains successful, but the request logger currently maps every incomplete terminal to 502/upstream_server_error. I will fix that first by retaining the structured incomplete reason, recording max_output_tokens as status 200 with terminalStatus=incomplete, keeping real incomplete failures such as adapter_eof and upstream_stall_timeout at 502, and adding a focused regression test.
I am not going to implement A or B from the hypothesis alone: the gateway-key direct bridge changes authentication/routing ownership, and the report does not yet include a 1P control or a redacted inbound system/body sample proving duplicate identity or effort loss for the classifier request. After the logging fix lands, please provide one redacted failing request shape plus its corresponding terminal frames if the five-attempt delay remains; that will let us fix the classifier path without guessing.
Ingwannu commentedon Jul 27, 2026
The confirmed logging bug is fixed on dev in 7fcaa91. response.incomplete with the structured reason max_output_tokens now records status 200, terminalStatus=incomplete, no upstream_server_error code, and the diagnostic label Output reached the requested token limit. Stall, adapter EOF, content-filter, and unknown incomplete terminals still fail closed. Regression coverage was added in tests/request-log.test.ts. Validation: focused request-log suite 41/41, full pre-push 5,047/5,047, typecheck, GUI lint, privacy gate, and the 131-page docs build all passed.\n\nI am leaving the issue open for the repeated 64-token classifier behavior itself. The direct-bridge and identity/effort hypotheses still need one redacted failing inbound request shape and its terminal frames or a 1P control before we change the auth/routing path.
PBJ-2 commentedon Jul 27, 2026
I captured one complete Auto Mode failure sequence on OpenCodex 2.7.41 using a temporary privacy-first structural logger. The attached JSON contains no prompt/system/message text, tool names/descriptions/schemas/arguments/results, credentials, user identifiers, or filesystem paths.
The capture correlates each inbound request with its terminal result using OpenCodex's
requestId.Reproduction result
Claude Desktop 3P Auto Mode sent the same classifier-shaped request five times:
model:claude-sonnet-5max_tokens:64stop_sequences:["</block>"]thinking.type:disabledEach request terminated as:
response.incompleteresponse.status:incompleteincomplete_details.reason:max_output_tokensOpenCodex 2.7.41 logged each terminal as status 502 /
upstream_server_error, although this is the already-identified logger representation issue fixed by7fcaa91; the important part for the remaining Auto Mode issue is that the classifier output hit the 64-token ceiling five times and the client repeated the same request shape.The five attempts took 31.312 seconds from the first request start through the fifth terminal event. The first attempt wrote part of the prompt cache; attempts 2–5 reused the same cached input, which further supports that these were retries of the same classifier input rather than unrelated Sonnet calls.
Complete redacted evidence JSON
{ "schema": "opencodex-issue-545-evidence-v2", "capture": { "opencodexVersion": "2.7.41", "surface": "claude-desktop-3p", "privacy": "Allowlisted structural metadata only. No prompt/system/message text, tool names/descriptions/schemas/arguments/results, credentials, user identifiers, or filesystem paths.", "correlation": "Each inbound request and terminal summary is joined by requestId." }, "finding": { "attemptCount": 5, "sameInboundShape": true, "elapsedMsFromFirstStartToLastTerminal": 31312, "model": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "terminalEventType": "response.incomplete", "terminalReason": "max_output_tokens", "outputTokensEach": 64 }, "attempts": [ { "attempt": 1, "requestId": "ocx-ms34xvwg-g", "startOffsetMs": 0, "inbound": { "model": "claude-sonnet-5", "resolvedModel": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "thinkingType": "disabled", "metadataKeys": [ "user_id" ], "hasMetadataUserId": true, "hasSystem": true, "anthropicBeta": "claude-code-20250219,interleaved-thinking-2025-05-14,mid-conversation-system-2026-04-07", "systemEqualityTag": "4606ae41", "systemShape": { "form": "array", "blockCount": 3, "blockTypes": { "text": 3 }, "totalChars": 108391 }, "messagesShape": { "count": 2, "roleCounts": { "user": 2 }, "contentForms": { "array": 2 }, "contentBlockTypes": { "text": 67 } }, "toolsShape": { "count": 0, "typeCounts": {} } }, "terminal": { "eventType": "response.incomplete", "loggedStatus": 502, "terminalStatus": "incomplete", "reason": "max_output_tokens", "durationMs": 6703, "closeReason": "terminal", "usage": { "inputTokens": 71698, "outputTokens": 64, "cachedInputTokens": 36539, "cacheReadInputTokens": 36539, "cacheCreationInputTokens": 35157 } } }, { "attempt": 2, "requestId": "ocx-ms34y12r-h", "startOffsetMs": 6707, "inbound": { "model": "claude-sonnet-5", "resolvedModel": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "thinkingType": "disabled", "metadataKeys": [ "user_id" ], "hasMetadataUserId": true, "hasSystem": true, "anthropicBeta": "claude-code-20250219,interleaved-thinking-2025-05-14,mid-conversation-system-2026-04-07", "systemEqualityTag": "4606ae41", "systemShape": { "form": "array", "blockCount": 3, "blockTypes": { "text": 3 }, "totalChars": 108391 }, "messagesShape": { "count": 2, "roleCounts": { "user": 2 }, "contentForms": { "array": 2 }, "contentBlockTypes": { "text": 67 } }, "toolsShape": { "count": 0, "typeCounts": {} } }, "terminal": { "eventType": "response.incomplete", "loggedStatus": 502, "terminalStatus": "incomplete", "reason": "max_output_tokens", "durationMs": 4443, "closeReason": "terminal", "usage": { "inputTokens": 71698, "outputTokens": 64, "cachedInputTokens": 71696, "cacheReadInputTokens": 71696, "cacheCreationInputTokens": 0 } } }, { "attempt": 3, "requestId": "ocx-ms34y4ia-i", "startOffsetMs": 11154, "inbound": { "model": "claude-sonnet-5", "resolvedModel": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "thinkingType": "disabled", "metadataKeys": [ "user_id" ], "hasMetadataUserId": true, "hasSystem": true, "anthropicBeta": "claude-code-20250219,interleaved-thinking-2025-05-14,mid-conversation-system-2026-04-07", "systemEqualityTag": "4606ae41", "systemShape": { "form": "array", "blockCount": 3, "blockTypes": { "text": 3 }, "totalChars": 108391 }, "messagesShape": { "count": 2, "roleCounts": { "user": 2 }, "contentForms": { "array": 2 }, "contentBlockTypes": { "text": 67 } }, "toolsShape": { "count": 0, "typeCounts": {} } }, "terminal": { "eventType": "response.incomplete", "loggedStatus": 502, "terminalStatus": "incomplete", "reason": "max_output_tokens", "durationMs": 7899, "closeReason": "terminal", "usage": { "inputTokens": 71698, "outputTokens": 64, "cachedInputTokens": 71696, "cacheReadInputTokens": 71696, "cacheCreationInputTokens": 0 } } }, { "attempt": 4, "requestId": "ocx-ms34yalt-j", "startOffsetMs": 19057, "inbound": { "model": "claude-sonnet-5", "resolvedModel": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "thinkingType": "disabled", "metadataKeys": [ "user_id" ], "hasMetadataUserId": true, "hasSystem": true, "anthropicBeta": "claude-code-20250219,interleaved-thinking-2025-05-14,mid-conversation-system-2026-04-07", "systemEqualityTag": "4606ae41", "systemShape": { "form": "array", "blockCount": 3, "blockTypes": { "text": 3 }, "totalChars": 108391 }, "messagesShape": { "count": 2, "roleCounts": { "user": 2 }, "contentForms": { "array": 2 }, "contentBlockTypes": { "text": 67 } }, "toolsShape": { "count": 0, "typeCounts": {} } }, "terminal": { "eventType": "response.incomplete", "loggedStatus": 502, "terminalStatus": "incomplete", "reason": "max_output_tokens", "durationMs": 6960, "closeReason": "terminal", "usage": { "inputTokens": 71698, "outputTokens": 64, "cachedInputTokens": 71696, "cacheReadInputTokens": 71696, "cacheCreationInputTokens": 0 } } }, { "attempt": 5, "requestId": "ocx-ms34yfz8-k", "startOffsetMs": 26020, "inbound": { "model": "claude-sonnet-5", "resolvedModel": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "thinkingType": "disabled", "metadataKeys": [ "user_id" ], "hasMetadataUserId": true, "hasSystem": true, "anthropicBeta": "claude-code-20250219,interleaved-thinking-2025-05-14,mid-conversation-system-2026-04-07", "systemEqualityTag": "4606ae41", "systemShape": { "form": "array", "blockCount": 3, "blockTypes": { "text": 3 }, "totalChars": 108391 }, "messagesShape": { "count": 2, "roleCounts": { "user": 2 }, "contentForms": { "array": 2 }, "contentBlockTypes": { "text": 67 } }, "toolsShape": { "count": 0, "typeCounts": {} } }, "terminal": { "eventType": "response.incomplete", "loggedStatus": 502, "terminalStatus": "incomplete", "reason": "max_output_tokens", "durationMs": 5292, "closeReason": "terminal", "usage": { "inputTokens": 71698, "outputTokens": 64, "cachedInputTokens": 71696, "cacheReadInputTokens": 71696, "cacheCreationInputTokens": 0 } } } ] }Ingwannu commentedon Jul 27, 2026
Thanks — this confirms the repeated behavior itself: five structurally identical 64-token classifier requests, each ending in
max_output_tokens, with the logging-only misclassification already fixed by7fcaa911.It does not yet establish that OpenCodex created the 108k system payload or lost the stop sequence: your capture is the inbound Anthropic body, where the large system and
</block>stop sequence are already present. Currentdevmaps bothmax_tokens -> max_output_tokensandstop_sequences -> stopon the internal request, then maps the stop sequence back for an Anthropic provider. Raising the caller's explicit 64-token cap would also change client semantics and is not safe as a generic fix.The next useful capture is therefore the redacted outbound provider request for one failed attempt:
max_tokens,stop_sequences, system block count/total chars, and whether the short Claude OAuth identity block was prepended. No text contents are needed. That will distinguish a translation loss from the model simply failing to emit</block>within the caller's limit. I am leaving this open underneeds-infoand will not change the auth/routing path on hypothesis alone.docs(devlog): correct the decision ledger against an independent audit
PBJ-2 commentedon Jul 27, 2026
I captured the requested Anthropic-provider outbound structure on OpenCodex 2.7.41. The temporary hook ran immediately before
JSON.stringify(body)in the Anthropic adapter and stored allowlisted structural scalars only—no prompt/message/system text, tool data, headers, credentials, user identifiers, or filesystem paths.Outbound result
The same classifier sequence produced five outbound Anthropic requests. All five had:
model:claude-sonnet-5max_tokens:64stop_sequences:["</block>"]All five corresponding terminal records ended as
incomplete/max_output_tokenswith exactly 64 output tokens. The first request created the prompt cache; attempts 2–5 read the same 68,090 cached input tokens. Each structural capture occurred 15–26 ms after its matching OpenCodex request start, in the same order, making the correlation unambiguous.What this establishes—and what it does not
This rules out loss of the caller's explicit token cap or stop sequence in the Anthropic provider translation: both survive into the final outbound provider body.
It does not establish parity with Claude Desktop's native 1P path, and I do not think the remaining behavior can yet be assigned to Claude Desktop or the model alone. In this 3P/OpenCodex path, the inbound capture had 3 system text blocks totaling 108,391 characters, while the final outbound body had 2 system text blocks totaling 108,457 characters. That exact 66-character increase is consistent with OpenCodex collapsing the three inbound blocks into one with two
\n\nseparators (+4 characters) and prepending the 62-character Claude OAuth identity block as a separate block.The repeated truncation is not observed when I use Claude Desktop without OpenCodex/3P. The remaining issue therefore appears specific to the Claude Desktop 3P/OpenCodex compatibility path. Plausible remaining distinctions are:
</block>within 64 tokens.The current capture cannot distinguish those two possibilities. It only shows that
max_tokensandstop_sequencesare preserved correctly, while the system block structure is not byte/structure-identical to the inbound request.OpenCodex 2.7.41 still records these incomplete terminals as status 502; as noted earlier, that is the logging-only representation fixed by
7fcaa91and is separate from the repeated classifier behavior.Privacy-reviewed redacted outbound evidence (5 attempts)
{ "capture": "OpenCodex 2.7.41 Anthropic provider outbound structural evidence", "privacy": { "containsPromptOrMessageText": false, "containsSystemText": false, "containsToolNamesDescriptionsSchemasArgumentsOrResults": false, "containsHeadersCredentialsUserIdentifiersOrFilesystemPaths": false, "note": "Only allowlisted structural scalars were captured in memory immediately before JSON serialization for the Anthropic provider." }, "interpretation": { "attemptCount": 5, "allPreservedCallerMaxTokens64": true, "allPreservedStopSequence": true, "allSystemBlockCounts": [ 2 ], "allSystemTotalChars": [ 108457 ], "allOAuthIdentityPrepended": true, "allTerminalMaxOutputTokensAt64": true, "translationLossObserved": false, "loggerNote": "OpenCodex 2.7.41 records these incomplete terminals as 502; that logging-only representation is fixed by 7fcaa91." }, "attempts": [ { "attempt": 1, "requestId": "ocx-ms39ch40-49", "outboundCaptureId": 106, "outboundAt": 1785158612660, "requestStartedAt": 1785158612640, "captureOffsetMs": 20, "provider": "anthropic", "isOAuth": true, "model": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "systemShape": { "form": "array", "blockCount": 2, "blockTypes": { "text": 2 }, "totalChars": 108457 }, "oauthIdentityPrepended": true, "terminal": { "status": "incomplete", "reason": "max_output_tokens", "loggerStatus": 502, "durationMs": 2997, "usage": { "inputTokens": 68092, "outputTokens": 64, "cachedInputTokens": 0, "cacheReadInputTokens": 0, "cacheCreationInputTokens": 68090 } } }, { "attempt": 2, "requestId": "ocx-ms39cjfe-4a", "outboundCaptureId": 107, "outboundAt": 1785158615658, "requestStartedAt": 1785158615642, "captureOffsetMs": 16, "provider": "anthropic", "isOAuth": true, "model": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "systemShape": { "form": "array", "blockCount": 2, "blockTypes": { "text": 2 }, "totalChars": 108457 }, "oauthIdentityPrepended": true, "terminal": { "status": "incomplete", "reason": "max_output_tokens", "loggerStatus": 502, "durationMs": 2469, "usage": { "inputTokens": 68092, "outputTokens": 64, "cachedInputTokens": 68090, "cacheReadInputTokens": 68090, "cacheCreationInputTokens": 0 } } }, { "attempt": 3, "requestId": "ocx-ms39clc3-4b", "outboundCaptureId": 108, "outboundAt": 1785158618131, "requestStartedAt": 1785158618115, "captureOffsetMs": 16, "provider": "anthropic", "isOAuth": true, "model": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "systemShape": { "form": "array", "blockCount": 2, "blockTypes": { "text": 2 }, "totalChars": 108457 }, "oauthIdentityPrepended": true, "terminal": { "status": "incomplete", "reason": "max_output_tokens", "loggerStatus": 502, "durationMs": 2575, "usage": { "inputTokens": 68092, "outputTokens": 64, "cachedInputTokens": 68090, "cacheReadInputTokens": 68090, "cacheCreationInputTokens": 0 } } }, { "attempt": 4, "requestId": "ocx-ms39cnbs-4c", "outboundCaptureId": 109, "outboundAt": 1785158620711, "requestStartedAt": 1785158620696, "captureOffsetMs": 15, "provider": "anthropic", "isOAuth": true, "model": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "systemShape": { "form": "array", "blockCount": 2, "blockTypes": { "text": 2 }, "totalChars": 108457 }, "oauthIdentityPrepended": true, "terminal": { "status": "incomplete", "reason": "max_output_tokens", "loggerStatus": 502, "durationMs": 1944, "usage": { "inputTokens": 68092, "outputTokens": 64, "cachedInputTokens": 68090, "cacheReadInputTokens": 68090, "cacheCreationInputTokens": 0 } } }, { "attempt": 5, "requestId": "ocx-ms39coty-4d", "outboundCaptureId": 110, "outboundAt": 1785158622672, "requestStartedAt": 1785158622646, "captureOffsetMs": 26, "provider": "anthropic", "isOAuth": true, "model": "claude-sonnet-5", "maxTokens": 64, "stopSequences": [ "</block>" ], "systemShape": { "form": "array", "blockCount": 2, "blockTypes": { "text": 2 }, "totalChars": 108457 }, "oauthIdentityPrepended": true, "terminal": { "status": "incomplete", "reason": "max_output_tokens", "loggerStatus": 502, "durationMs": 2243, "usage": { "inputTokens": 68092, "outputTokens": 64, "cachedInputTokens": 68090, "cacheReadInputTokens": 68090, "cacheCreationInputTokens": 0 } } } ] }[-]Desktop 3P + Auto Mode: Request for Sonnet Permission Classifier Cuts Tokens at 64 and Repeats Up to 5 Times (Estimating Impact of Double Translation Path)[/-][+]Claude Desktop 3P Auto Mode classifier retries after 64-token Anthropic OAuth outputs[/+]Ingwannu commentedon Jul 27, 2026
Thanks — this is sufficient to close the evidence gap.
The capture confirms that OpenCodex preserves both caller controls all the way to Anthropic:
max_tokens: 64andstop_sequences: ["</block>"]are present on every outbound request. The original translation-loss hypothesis is therefore disproven. The 66-character system difference is also accounted for by the required Claude OAuth identity block plus block joining; it is not an unexplained truncation or dropped field.What remains is a narrower compatibility issue: Claude Desktop 3P's Auto Mode classifier, routed through Anthropic OAuth, sometimes does not emit its closing tag within the client's explicit 64-token budget and Desktop repeats the classifier request five times. We cannot safely fix that by raising the caller's explicit budget, removing the OAuth identity instruction, or pretending an incomplete response completed.
I have narrowed the title, removed
needs-info, and addedprovider-compatibility. Leaving it open for a deliberately scoped Desktop-3P/Anthropic classifier solution or an upstream behavior change; it is no longer tracked as a generic Messages↔Responses field-loss bug. The separate 502 logging defect remains fixed ondevby7fcaa911.20 remaining items
fix(claude): find the claude- segment instead of assuming its position (
fix(anthropic): reject a longer number, not any dot, in the family tail
fix(anthropic): reject a longer number, not any dot, in the family tail
fix(anthropic): classify capitalized/dotted Claude ids as adaptive th…
Merge from upstream (#8)
fix(anthropic): classify capitalized/dotted Claude ids as adaptive th…
fix(ci): soft-pass detailed issues after maintainer retitle
docs(devlog): roadmap lock for the bug-label resolution unit
docs(devlog): fold the A-gate audit into the bug-bundle roadmap
docs(triage): lidge-jun#545 is a thinking round-trip loss, not an ide…
fix(claude): keep an explicit thinking disable through translation (l…
fix(claude): match the thinking gate on prefixed model ids too (lidge…
fix(claude): find the claude- segment instead of assuming its position (
Merge pull request lidge-jun#954 from lidge-jun/codex/545-classifier-…
fix(anthropic): classify capitalized/dotted Claude ids as adaptive th…