Repository navigation
[Bug]: OCG DeepSeek Chat re-hoists chronological reminders after inbound fix #4161 #4439
Description
Client or integration
Claude Code (Claude Desktop Code surface)
Area
Provider adapter
Summary
On OpenCode Go's deepseek-v4.1-flash Chat route, appending a chronological Claude system reminder still rewrites the leading outbound system message. The generic Chat serializer collects text-only developer messages into that leading message, undoing the chronological representation introduced by #4161. This changes the reusable prompt prefix even when the existing conversation is unchanged.
This is the Chat-outbound follow-up explicitly left out of scope in #4161, which closed #4148. Proposed narrow fix: #4438.
Reproduction
-
Use the canonical OpenCode Go destination (
https://opencode.ai/zen/go/v1), Chat adapter, and modeldeepseek-v4.1-flash. -
Translate a synthetic Claude request through
anthropicToResponsesBody→parseRequest→createOpenAIChatAdapter(...).buildRequest(...):{ "model": "deepseek-v4.1-flash", "system": "Stable project instructions.", "max_tokens": 100, "messages": [ {"role": "user", "content": "Inspect the synthetic project."}, {"role": "assistant", "content": "First result."}, {"role": "system", "content": "Synthetic reminder A."} ] } -
Append an assistant turn, a user continuation, and another system reminder B; build the second outbound request.
-
Compare the old-length prefix of the second outbound
messagesarray with the first array. Before the fix, reminder B joins the leading system content and the comparison fails. Expected on this compatible route: the prior serialized messages remain unchanged and the new reminder stays in its chronological position, while pending tool calls/results remain adjacent.
The offline regression in #4438 exercises the actual translation/parser/adapter chain with stabilizePromptCache both false and true. It requires no credentials or model requests. The new suite produced 5 failures on the unmodified base and 15 passes with the patch, including destination/model boundaries and pending-tool behavior.
Version
Observed locally on OpenCodex 2.52.0, Bun 1.4.2. PR base: dcd13b4358befaae0fdca845a8219103943faca0; proposed head: e7bfb08b2823647b09d501692f759193a19d2299.
Operating system
Windows 11, build 26200.
Provider and model
OpenCode Go / deepseek-v4.1-flash, High effort. Live observation includes input growing to 409,130 tokens; it does not validate the full advertised 1M window.
Observed cache behavior
Snapshot through 2026-09-12 22:07:56 UTC, about 2h23m after the local chronological patch became active:
- Same long-session cohort: 9.17% before (16 measured later requests), 99.05% after (194).
- Both patched cohorts combined: 99.06% over 202 measured later requests; latest 50: 99.88%; latest 20: 99.92%.
- Four measured requests were below 90%, retained in those totals. One reset coincided with input shrinking from 214,155 to 36,948 tokens; another followed about 33 minutes without a successful request in that cohort and recovered on the next success. Compaction/cache expiry are possible explanations, not established causes.
Ratios are sum(cached input)/sum(input) from reported upstream usage; input already includes cached tokens. The first successful request per phase/session/effort is excluded as a cold-start approximation, and eligible requests require at least 1,024 input tokens. Failed requests and missing/estimated usage are not counted as cache measurements. The whole patch window contains 218 requests: 204 HTTP 200, 2 client cancellations (499), 12 connection failures (502), and no 400/422. Connection failures are disclosed separately; this issue does not propose a 502 fix.
This is an uncontrolled within-session observation, not a general cache guarantee. The live local endpoint predicate differs from the PR's registry-based predicate, whose boundary is tested offline. The opt-in trailing-notice stabilization setting stayed enabled during the live patch window; the new offline tests cover both setting values.
Related work / duplicate check
- [Bug]: Mid-conversation role: system messages in Claude inbound are aggregated into top-level instructions, destroying prompt cache prefix #4148 / fix(claude): keep mid-conversation system messages in the timeline #4161: inbound chronology; fix(claude): keep mid-conversation system messages in the timeline #4161 explicitly identifies Chat developer-message re-hoisting as a separate unresolved compatibility tradeoff.
- fix(claude): stabilize Responses instructions for Muse/Go prompt cache #4052 / fix(claude): opt in to harness prompt-cache stabilization (carry #4052) #4347: opt-in handling of supported trailing harness notices; does not change this Chat serializer behavior. In one matched cohort that setting alone yielded 7.13%, versus 9.03% before and 99.59% after the chronological patch.
- fix(openai-chat): keep system messages first #397: existing generic system-first compatibility behavior. fix(chat): preserve OCG DeepSeek timeline system instructions #4438 preserves it for other destinations/models.
- Open Experimental: preserve parent prompt-cache prefixes for Desktop side chats #4222 and feat: Change Astra reasoning mid-conversation without breaking cache #4225 concern Codex Responses side chats and Astra effort changes, respectively; neither implements this OCG Chat exception.
Searched open/closed issues and PRs for cache, DeepSeek, system, hoist and chronological terminology. No other PR implementing this specific residual fix was found; #4438 already exists and is reused. It remains draft because broader local test:changed validation is unresolved, as detailed in its body.
Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
lidge-jun commentedon Sep 12, 2026
리뷰 · 우선순위 68 / 80
설명
이 이슈는 Claude Code가 대화 중간에 넣는 시스템 리마인더가, OpenCode Go의 Chat 경로(
deepseek-v4.1-flash)로 나갈 때 다시 맨 앞 system 메시지에 합쳐지는 잔여 버그를 가리킨다. 인바운드 쪽은 이미 머지된 #4161이 고쳤다. Claude가messages안에 넣는 mid-conversationsystem을 타임라인developer로 남기고,instructions/맨 앞 프롬프트로 끌어올리지 않게 했다. 그 PR은 비-OpenAI Chat 직렬화가 developer를 다시 앞쪽으로 접는 문제는 의도적으로 범위 밖으로 남겼고, 이 이슈가 바로 그 다음 단계다.지금
devHEAD(dcd13b435, package2.53.0)의src/adapters/openai-chat.tsmessagesToChatFormat를 보면 재현이 코드와 맞다. 네이티브 OpenAI(api.openai.com)가 아니면 텍스트-only developer를developerSystemParts로 모아 앞 system에 붙이고(대략 739–754행), 타임라인 루프에서는if (!nativeOpenAI) break로 그 자리를 비운다(764–765행). 그래서base → user → assistant → reminder A다음에 reminder B가 붙으면 앞 system이base+A에서base+A+B로 바뀌고, 이전 요청과 같은 길이의 메시지 접두사가 깨진다. 업스트림 KV/프롬프트 캐시가 앞부분을 맞추는 경로에서는 캐시 hit이 무너진다. 보고자가 적은 재현(Anthropic →anthropicToResponsesBody→parseRequest→ ChatbuildRequest)과 “두 번째 요청의 앞쪽 직렬화가 첫 요청과 같아야 한다”는 기대는 이 코드 경로와 정확히 대응한다.관련 설정·이슈와도 구분된다.
claudeCode.stabilizePromptCache(#4052/#4347)는 지원되는 trailing harness notice만 다루고, 이 Chat serializer의 developer 수집과는 별개다. 보고자 관측에서도 그 설정만으로는 캐시 비율이 거의 안 올랐고, 시간순 패치 뒤에야 크게 올랐다(비통제 세션 관측이므로 제품 보장으로 읽으면 안 된다). #397의 일반 system-first 호환, 열린 #4222(Codex Responses side chat), #4225(Astra effort)와도 겹치지 않는다. OpenCode Go 레지스트리(src/providers/registry.tsidopencode-go, baseUrlhttps://opencode.ai/zen/go/v1)와 게이트웨이 thinking 모델deepseek-v4.1-flash는 현재 HEAD에 그대로 있다.고치는 쪽은 이미 드래프트 PR #4438이 있다. OCG 목적지 + exact
deepseek-v4.1-flash에서만 타임라인 지시를 제자리system으로 남기고, 다른 라우트의 기존 접기는 유지한다. 이슈 본문이 그 PR을 좁은 픽스로 가리키고, PR도 Closes #4439를 건다. types.ts/config.ts 대분할에 잡아먹힐 종류의 변경이 아니고, 중복 이슈/PR 검색도 작성자가 해 두었다. 현재dev방향(캐시 접두사 안정화, OCG/DeepSeek Chat 호환, 좁은 destination/model 게이트)과도 맞다. 다만 #4438이 아직 readiness/드래프트라서, 이 이슈만 보고 “지금 바로 닫아도 된다”고 단정하면 안 된다.라인 739-754 -
messagesToChatFormat가 비-OpenAI에서 텍스트-only developer를 앞 system에 합친다. #4161 이후에도 Chat 출력이 접두사를 다시 쓴다.라인 764-765 - 같은 함수가 타임라인 자리의 텍스트-only developer를
break로 건너뛴다. 앞쪽 hoist와 한 쌍이다.경로
tests/adapters/openai/openai-chat-system-order.test.ts- HEAD 테스트는 여전히 “interleaved developer를 하나로 접는다”를 기본 기대로 둔다. OCG DeepSeek 예외 회귀는 #4438이 추가한다.경로
src/providers/registry.tsopencode-go/DEEPSEEK_GATEWAY_THINKING_MODELS- 이슈가 가리키는 목적지·모델은 현재 카탈로그와 일치한다. first-partydeepseek-flash나 다른 Go DeepSeek id로 예외를 넓힐지는 별도 판단이다.심볼
claudeCode.stabilizePromptCache- 이 설정만으로는 이 serializer 동작을 바꾸지 못한다. 이슈/픽스를 설정 토글로 대체하면 안 된다.메인테이너의 판단이 필요한 지점
deepseek-v4.1-flash만 예외로 둘지, Go DeepSeek thinking 집합·first-party flash까지 같은 접두사 민감도로 볼지.test:changed공백이 있는 동안 이슈를 열린 버그로만 둘지, 픽스 PR readiness와 묶을지.너의 추천
열린 버그로 두고
bug/provider/tools를 유지한다. 픽스는 드래프트 #4438을 쓰면 되고, 새 PR을 또 열 필요는 없다. #4438이 readiness와 포커스 회귀를 정리해 ready가 되면 그 PR로 이 이슈를 닫는다. types/config 대분할에 무효화될 변경이 아니므로 이슈를 닫거나 “리베이스 하라”고 할 대상이 아니다. 중복도 아니다. 지금은 #4438 랜딩을 기다리는 추적 이슈로 두는 것이 맞다.이 댓글은 grok-bot이 작성했습니다
lidge-jun commentedon Sep 13, 2026
Fixed on dev via #4438, carried and merged inside lane C's tip #4487 (merge commit 55bb9f3, verified as an ancestor of origin/dev). The landed commit credits @Yongzhaooo with a Co-authored-by trailer.
Cross-platform CI run 34744712476 concluded success on the exact merged head. Closing manually because this merges into dev rather than the default branch.