Skip to content

[Bug]: Claude Messages to Astra cache plateau/reset with growing history; Codex CLI comparison #3765

Closed as not planned

Description

@alexph-dev

Client or integration

Claude Desktop 3P and ocx claude, compared with Codex CLI.

Area

Proxy and routing: Anthropic Messages to native OpenAI Responses.

Summary

With gpt-6-astra, a Claude Desktop main-agent conversation reused essentially the same 27.6k-token prefix across several continuations, despite growing input. Separate synthetic ocx claude sequences show that this route can grow cache reads, but one also went warm → cold within seconds. A Codex CLI control grew its cached prefix on every continuation.

This reports measured symptoms, not a proven OCX root cause or a claim that every continuation must hit cache. Please investigate prefix preservation and effective session/cache identity at the native forward boundary. Existing logs do not distinguish client prefix changes, OCX translation, and upstream cache placement/expiry.

Version

OpenCodex CLI and running proxy: 2.43.0, no version skew at the time of measurement.

Operating system

macOS 26.5.2.

Provider and model

OpenAI / gpt-6-astra, native OpenAI Responses adapter through a local OCX proxy.

Additional environment details

  • Desktop embedded Claude Code: 2.1.260, transcript entrypoint claude-desktop-3p.
  • Standalone Claude Code: 2.1.258.
  • Codex CLI: 0.153.2.
  • Local loopback proxy; native OpenAI account-pool route, openai-responses adapter.
  • All successful rows below: resolved gpt-6-astra, requested effort high, HTTP 200, one attempt, the same anonymized account label A. This does not prove a common upstream cache machine/cohort.
  • Measurements: 2026-09-06, 10:18–10:34 UTC.

Desktop main-agent evidence

OCX's conversation group also contained a child Astra agent and Luna calls. I matched the local Desktop transcript to OCX usage by input/cache counts and timestamps, selected isSidechain=false, and deduplicated assistant content blocks sharing one message ID. Only the six main-agent requests are below:

Request start UTC Total input tokens Cache-read tokens Read share
10:18:45 43,084 0 0.0%
10:20:13 43,201 27,648 64.0%
10:21:49 43,653 27,648 63.3%
10:21:54 43,816 27,648 63.1%
10:22:08 45,592 27,648 60.6%
10:24:06 47,427 28,928 61.0%

Total: 139,520 / 266,773 = 52.3% cache reads; excluding the initial cold request, 62.4%. Requests 2–5 remain at exactly 27,648 cached tokens. The first two responses are text; request 3 returns a Skill call; thinking blocks first appear in response 4. Thus dropping prior thinking blocks cannot explain the earliest plateau by itself. No compaction event appears in this main transcript.

The five child-Astra rows and four Luna rows are excluded from this table. Treating the whole log conversation group as the main agent would give a misleading result.

Synthetic CLI comparisons

Each sequence used a fresh session, the same generated user-text growth pattern, four sequential turns with --resume/exec resume, high effort, and the response OK. No tools were called. Earlier user records were retained; each turn appended 120 new records after an initial 400. Each session's OCX conversation ID remained nonempty and equal across its successful requests.

Sequence Total input tokens by turn Cache-read tokens by turn
Codex CLI 29,150 → 31,361 → 33,572 → 35,783 0 → 28,928 → 31,232 → 33,408
ocx claude --bare, explicit fixed system prompt 7,388 → 9,599 → 11,810 → 14,021 0 → 0 → 9,472 → 11,648
ocx claude --bare, no custom system prompt 7,412 → 9,623 → 11,834 → 14,045 0 → 7,296 → 9,472 → 0

The last sequence's request starts were 10:33:46, 10:33:49, 10:33:54, and 10:33:58 UTC. Its final successful turn lost all reported cache reads about five seconds after a warm request.

These are minimal controls, not identical wire payloads or a client performance ranking: the clients have different system/tool envelopes, Desktop used real tools, and the standalone/embedded Claude versions differ. Two additional startup rows, one per Claude sequence, returned 499 without reported usage; they are excluded rather than counted as zero-cache successes.

Reproduction

The intermittent miss is not established as deterministic. To repeat the minimal comparison:

  1. Use an already running local OCX proxy with the same Astra route. Use a disposable working directory and isolated CLAUDE_CONFIG_DIR for Claude.

  2. Generate records using:

    records = '\n'.join(
        f'Record {i:04d}: amber cedar river stone meadow lantern willow garden copper island violet orchard.'
        for i in range(start, end)
    )

    Ranges: [0,400), [400,520), [520,640), [640,760). Each turn asks to retain the reference records, reply only OK, and take no external action.

  3. Claude invocation: ocx claude --bare -p --output-format json --model gpt-6-astra --effort high --tools '' --strict-mcp-config --mcp-config '{"mcpServers":{}}' --setting-sources '' --max-turns 1, then --resume <returned-session-id> for later turns. Supply authentication appropriate to the local proxy. For this loopback-only sample, an invocation-scoped apiKeyHelper returned OCX's documented opencodex-proxy marker; no upstream credential or global auth setting was changed. The first Claude sequence additionally supplied a fixed --system-prompt asking only for OK and no tools. The second omitted that override.

  4. Codex invocation: codex exec --ignore-user-config --skip-git-repo-check --json -m gpt-6-astra -c 'openai_base_url="http://127.0.0.1:10100/v1"' -c 'model_reasoning_effort="high"' -c 'project_doc_max_bytes=0' -c 'features.shell_tool=false' -c 'features.apply_patch_freeform=false' -c 'sandbox_mode="read-only"' -c 'approval_policy="never"' -. Continue the returned ID with codex exec resume, retaining the same overrides. Prompts go through stdin.

  5. Correlate successful requests in usage.jsonl. OCX/OpenAI inputTokens includes cached input; Claude's client input_tokens excludes it. Add Claude input + cache-read + cache-creation before comparing. Client totals and OCX totals agreed in these samples.

Source checks and remaining evidence gap

The following installed files were byte-identical to main at commit 06ec553630fa2ee51a96b5cbf694089021249194:

  • inbound.ts: metadata-backed cache key generation; also contains the assistant thinking-drop policy.
  • claude-messages.ts: native metadata-backed requests synthesize an underscore session_id header from that key.
  • outbound.ts: cache counters are mapped into Anthropic usage.
  • src/adapters/openai-responses.ts: includes session_id in forward headers and reads upstream cached-token usage.

The populated conversation groups are consistent with metadata-backed identity, but there was no inbound/outbound wire capture. Stable logged identity/account labels do not establish byte-identical prefixes or the effective forwarded headers. Claude inbound debug was disabled. The next useful capture would record only field presence/equality and prefix-change locations across a warm/cold pair, including tools/instructions/history, cache key, underscore/hyphenated session identifiers, and selected account. No private request content is needed publicly.

Related: #3433 tracks Hermes Chat Completions cache misses with missing identity fields; this report adds the Claude Messages path with populated conversation correlation and a Desktop main-agent plateau. #3719 explicitly concerns intentional Anthropic upstream thinking replay; this report's upstream is OpenAI Astra. No duplicate claiming this exact comparison was found.

Checks

  • Searched existing issues and current source.
  • Removed personal paths, original prompts, session IDs, account identifiers, credentials, and private tool output.
  • No proxy/provider configuration change, restart, or local source patch was made for this investigation.

Activity

  1. github-actions commented on Sep 6, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  2. lidge-jun commented on Sep 6, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 56 / 80

    이 이슈는 Claude Messages → 네이티브 OpenAI Responses(gpt-6-astra) 경로에서, 대화가 이어져도 캐시 읽기 토큰이 한 구간에 고정되거나(Desktop 메인 에이전트 27,648 정체), 짧게 따뜻했다가 다시 0으로 떨어지는(합성 ocx claude 한 시퀀스) 증상을 측정한 보고서입니다. Codex CLI 대조군은 이어질수록 cached prefix가 커졌습니다. 리포터도 말했듯 OCX 루트 원인 확정이 아니라, 클라이언트 prefix 변화 / OCX 번역 / 업스트림 캐시 배치·만료를 로그만으로는 구분할 수 없다는 증거 공백이 핵심입니다.

    지금 dev HEAD a349b521b(2.44.0, #3766 이후)의 Claude→Responses 캐시 신원은 리포트가 가리킨 파일과 맞습니다. src/claude/inbound.ts는 metadata.user_id가 있으면 sha256으로 prompt_cache_key를 만들고(cacheKeySource: "metadata"), 없으면 system+tools 코호트 해시("system")로 폴백합니다. assistant thinking/redacted_thinking은 리플레이 시 드롭하는 v1 정책입니다(대략 L237–L238). src/server/claude-messages.ts 네이티브 라우트는 cacheKeySource === "metadata"일 때만 session_id 헤더를 캐시 키에서 uuid 형태로 합성하고, system 폴백에는 헤더를 안 붙입니다(대략 L827–L835, audit 133 R2#3). src/claude/outbound.ts는 업스트림 cached 토큰을 Anthropic cache_read_input_tokens 등으로 옮기고, src/adapters/openai-responses.ts는 session_id를 포워드 헤더에 두고 cached usage를 읽습니다.

    Desktop 표는 요청 2–5가 입력은 늘어도 cache-read가 정확히 27,648에 머뭅니다. 스킬 호출·thinking 등장 전에 이미 정체라, thinking-drop만으로 초기 평탄을 설명하기 어렵다는 리포터 관찰과 맞습니다. 합성 CLI는 같은 성장 패턴인데 Codex는 캐시가 자라고, Claude bare는 늦게 따뜻해지거나 수 초 만에 식습니다. 와이어 캡처가 없어 “OCX가 prefix를 깨뜨렸다”와 “업스트림이 같은 키/계정에서도 배치를 바꿨다”를 갈라낼 수 없습니다. 관련 #3433(Hermes Chat Completions, 신원 필드 공백)과 겹치는 주제지만 여기는 Claude Messages + 채워진 conversation 상관입니다. #3719는 Anthropic 업스트림 thinking 리플레이 설계라 업스트림이 Astra인 이 이슈와는 다릅니다.

    release-244는 대시보드·문서 마감 직후이고, 이 이슈는 즉시 머지할 패치보다 관측 설계가 먼저입니다. types/config 분할에 무효화될 PR도 아닙니다.

    src/claude/inbound.ts - metadata/system prompt_cache_key 분기와 thinking-drop 정책이 HEAD에 그대로 있음. 평탄 원인을 여기만으로 단정할 수 없음
    src/server/claude-messages.ts - metadata일 때만 session_id 합성. system 코호트는 body key만. Desktop이 어느 source인지 이슈만으로는 확정 안 됨
    src/claude/outbound.ts / src/adapters/openai-responses.ts - 캐시 카운터·헤더 포워딩은 존재. “왜 읽기가 안 늘었는지”를 설명하는 진단 필드는 부족
    증거 공백 - 따뜻한/차가운 한 쌍에 대해 tools/instructions/history prefix 변화 위치, cache key, underscore/hyphen session id, 선택 계정만 빨간 캡처가 없음(리포터도 다음 단계로 제안)
    #3433 / #3719 - 인접하지만 경로·업스트림이 다름. 한 PR로 뭉개면 안 됨

    메인테이너의 판단이 필요한 지점

    • 다음을 버그 픽스 큐로 둘지, needs-repro/needs-design 관측 이슈로 둘지
    • 빨간 와이어 캡처(필드 존재/동등·prefix 변화 위치만)를 리포터에게 추가 요청할지, OCX 쪽에 디버그 토글을 넣을지
    • Desktop 메인 에이전트의 cacheKeySource가 metadata인지 system인지 먼저 확인할지
    • Astra/OpenAI 쪽 캐시 TTL·코호트 동작을 OCX 버그로 취급하기 전에 업스트림 이슈로 분리할지
    • #3433과 공동 “cache identity” 스펙으로 묶을지, Claude Messages만 따로 둘지

    너의 추천
    열린 채로 두되 구현 PR은 아직 받지 마세요. 다음 한 장은 (1) Desktop/합성 한 쌍의 빨간 캡처 체크리스트(캐시 키 source, session_id 유무·형태, tools/system/history prefix 동등 여부, 계정 라벨), (2) usage.jsonl에 cacheKeySource·session_id 존재 여부 같은 비공개 메타를 남길지 여부입니다. thinking-drop을 바로 끄거나 session_id를 system 키에도 붙이는 PR은 audit 이유를 깨기 쉬우니 관측 전에는 거절하세요. release-244 기차와는 독립입니다.

    이 댓글은 grok-bot이 작성했습니다

  3. added
    priority: P2Medium: provider/client-specific bug with a workaround, bounded enhancement tied to a tracked issue,
    on Sep 24, 2026
  4. devin-ai-integration commented on Sep 24, 2026

    @devin-ai-integration
    Contributor

    Triage: priority: P2 — Claude Messages cache plateau/reset with growing history.

    Criteria (P2): Medium: provider/client-specific bug with a workaround, bounded enhancement tied to a tracked issue, perf, or CI reliability.

    Matched pull requests:

  5. lidge-jun commented on Sep 27, 2026

    @lidge-jun
    Owner

    Release-train 4 disposition: keeping this open, with no proxy change in this release. The Claude Messages → Responses path derives prompt_cache_key from metadata.user_id when present and otherwise from a system+tools hash (src/claude/inbound.ts:465-502), so the two symptoms here (a 27,648-token plateau, and a short warm run that drops back to 0) can come from different places: the client changing an early part of the prefix, our translation changing it, or upstream cache placement. Cache counters alone can't separate those.

    Current dev has a diagnostic for exactly this: run with OPENCODEX_CACHE_DEBUG=1 and it records, per request, process-keyed tags for the inbound and outbound prefix sections alongside the cache counters (src/usage/cache-diagnostic.ts). The useful data is a comparison of two consecutive turns from a plateaued session: which prefix sections kept the same tag and which changed, the prompt_cache_key source (metadata or system), and cached_tokens for each turn. Please describe which sections matched or changed rather than pasting the tags, request IDs or any prompt text. If an early section changes between turns, the inbound and outbound tags show whether the client or our translation changed it, which narrows where to look. If everything matches and cached_tokens still drops, that points away from our translation, though it doesn't prove where the cache miss happened.

  6. lidge-jun commented on Oct 7, 2026

    @lidge-jun
    Owner

    Thank you for the detailed cache measurements. Cache counts alone do not establish that OpenCodex changed the prefix or cache identity, and the requested diagnostic comparison remains missing. Please reopen with current versions, sanitized ocx doctor output, and a minimal warm/cold pair describing inbound versus outbound prefix equality and cache-key source without sharing prompts or identifiers.

    Closing as not reproducible with the current evidence; this is not a fix claim.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: P2Medium: provider/client-specific bug with a workaround, bounded enhancement tied to a tracked issue,toolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions