Repository navigation
[PR] fix: classify FORK_PREFIX_CONFLICT rejections instead of failing blind (#3740) - #3743
Conversation
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.4421-pr.3743.fb0cf31This exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr3743.tgz
npm install -g packageInstall instructions are refreshed on each push. |
#3740) The public-fork first-request gate rejected mismatched replays with a bare 409 and zero server-side evidence of WHY the child history drifted from its inherited prefix — field reports like #3740 were impossible to triage from either side. publicForkInputMatches becomes publicForkInputCheck returning a classified verdict: cell-mismatch@N (id-drift / hash-drift-under-same-id / ref-broken, with truncated id+hash previews), prefix-longer-than-incoming, incoming-unmappable, receipt-missing, snapshot-missing, projection-error. Accept/reject semantics are unchanged — fail-closed on any error, same per-cell gates incl. the #2620 BLOCKED sentinel. On rejection the 409 body gains an ADDITIVE detail field with the classification and the proxy logs a warn line (session id, protocol, reason). detail carries truncated ids/hashes only, never message content. Also drops the now-unused publicForkInputMatches import in server.ts and refreshes four stale name references in comments (fork-adopt.ts, two parity test files). tests/plugin-fork.test.ts pins every rejection class end-to-end through a real proxy, including same-id hash drift (thinkingTokens flag flipped under unchanged bytes) asserting it classifies separately from id drift. Behavior change (additive): 409 response body gains 'detail'; new warn log line on rejection. No config surface, wire-shape, or persistence change. Refs #3740
The verdict type is only consumed inside plugin.ts (as the return type of publicForkInputCheck); the repo's unused-exports gate requires such types to stay module-private.
Review of #3743: the classifier could label a cell whose ONLY failing check was the ref mapping (id+hash intact, e.g. a mapping pruned at a native-compaction boundary) as hash-drift-under-same-id while printing two identical hash previews — sending triage after a phantom flag flip. Each evidence part is now emitted only when its own check failed. Also pins the ref-only class end-to-end in tests/plugin-fork.test.ts (break the child's byRaw mapping for cell 0, restore in finally; assert the detail says ref-broken without inventing hash-drift/id-drift), and points the stale #3740 refs in src/plugin.ts, src/server/handle.ts and the test file at #3741 — the field report (#3740 on GitHub is an unrelated acp_delegate_wait wording issue).
5453518 to
02c4fb5
Compare
(ai: vllm-qwen/qwen3.8-27b) 查重:不重复。 按 标签: bug / scope/kernel / p2 —— fork 子会话被 409 永久卡死是真实缺陷家族,机制在会话核心的 fork prefix gate;能用但有明显问题 → p2。 审核结论(八问)
审核中发现的问题与处置(均当场修复,独立标注 commit)
另外把分支 rebase 到了当前 master( 判定:审核侧无拦点,代码可合入,等新 head |
(ai: vllm-qwen/qwen3.8-27b) CI 确认:新 head PR 状态:open、mergeable,无审核拦点——可以合并了(合并按惯例由人来)。 |
|
(ai: vllm-qwen/qwen3.8-27b) 补一条记录修正:此前 18:45Z 的「全绿」结论对 main CI 而言为时尚早——head 现已核实:
|
(ai: vllm-qwen/qwen3.8-27b) 确认并采纳这条修正:我此前「主 CI 全量矩阵已绿」的说法为时过早——当时存活的 pull_request run 的 plan 按设计被跳过(labeled 事件且无 当前状态我已独立复核:
结论不变:审核侧无拦点,可人工合并。 |
📦 Merged Build PublishedThis merge to master was published as npm install -g billion-context@0.0.579-master.6dca5e4 # this exact build
npm install -g billion-context@master # always the newest merged build
|
#3757 review) Inverting the 409 sub-cases removed the only coverage of the hash-drift-under-same-id and ref-broken verdict branches (including #3743's fix that a pruned ref reports ref-broken alone, never hash-drift). Pin both directly against publicForkInputCheck with negative assertions so a future classifier regression cannot ship silently behind the advisory warn.
…a classified warn (ranxianglei#3741) The ordered-prefix 409 was a permanent dead-end: the rejected first request never incremented stats.requests, so every retry re-hit the same gate with no clear or TTL path (field report: dsh fork child stuck after an in-place proxy upgrade re-keyed message ids, ranxianglei#3642 canonical re-key generation). Routing is the session-id binding (recordPluginSession -> resolveConversation); the byte-exact pre-gate added no correctness fold reconcile (ranxianglei#3641 order-over-hash) doesn't already enforce — it only produced unrecoverable 409s. Now: mismatched first request is adopted, logs one classified warn (ranxianglei#3743 reason taxonomy), publicForkPrefix stays false so the side-lane demotion heuristics keep their normal say. Cost: a divergent child's first request goes out unfolded — a one-time token spike, never corruption. Gate stays one-shot (stats.requests > 0 bypasses it).
Converge onto the resident ci-fixes baseline (master dc10c2d + guard-lane fixes) so this branch is current with master. Clean union-of-intent merge: the #4266 thinking-gate work (src/plugin.ts, src/agent/fork-adopt.ts) is untouched by ci-fixes; tests/plugin-fork.test.ts auto-merges both this PR's #3743 identity-field pin and master's #5140 cold-tail 409-body diagnostic.
Background
#3741 field report: a fork child session is stuck on
409 FORK_PREFIX_CONFLICT("first child request does not match its inherited ordered prefix") and nobody can tell WHY — the client sees a bare 409 and the proxy logs nothing. A deterministic mismatch is a permanent brick for that child (the gate re-runs on every request), so the first thing missing from this failure mode is diagnosability. This PR adds classification to that gate; root-cause triage continues in #3741 with user evidence.Changes
src/plugin.ts:publicForkInputMatches()becomespublicForkInputCheck(), returning a classified verdict instead of a boolean:cell-mismatch@N—id-drift/hash-drift-under-same-id/ref-broken, with truncated id + hash previews only; each evidence part appears only when its own check failed;prefix-longer-than-incoming (expected=N incoming=M);incoming-unmappable (protocol=X);receipt-missing/snapshot-missing/projection-error (…).Accept/reject semantics are UNCHANGED: fail-closed on any error, same per-cell gates incl. the [Bug][plugin] protectedTools 的 BLOCKED 映射导致纯文本 snapshot 返回 409 #2620 BLOCKED sentinel.
src/server/handle.ts: on rejection, logswarn [fork-prefix] <sessionId>: first child request rejected — <reason> (protocol=<p>)and the 409 body gains an additivedetailfield carrying the classification. Existingok/code/errorfields and the error sentence are unchanged.src/server.ts; refreshed four stale name references in comments (src/agent/fork-adopt.ts, two parity test files).tests/plugin-fork.test.ts: pins every rejection class end-to-end through a real proxy, including same-id hash drift (athinkingTokensflag flipped under unchanged bytes) asserting it classifies separately from id drift, plus a ref-only mismatch pinningref-brokenalone.Behavior change (disclosure)
{ok, code, error}onlydetail: <classification>fieldNo config surface change, no wire-shape change beyond the additive JSON field, no persistence change, no kernel change.
Verification
npm run typecheckclean, unused-exports gate clean,npm run buildOKtests/plugin-fork.test.ts48/48 (full suite = remote CI)Review fixes (2026-10-19)
Review of this PR found three issues, fixed as labeled commits on top:
src/plugin.ts): when a cell's ONLY failing check was the ref mapping (id+hash intact — e.g. a mapping pruned at a native-compaction boundary), theelsebranch still emittedhash-drift-under-same-idwith two identical hash previews, sending triage after a phantom flag flip. Each evidence part is now emitted only when its own check failed; pinned end-to-end intests/plugin-fork.test.ts(break the child'sbyRawmapping for cell 0, restore infinally, assertref-brokenalone).PublicForkPrefixVerdictwas exported but consumed only insidesrc/plugin.ts— un-exported (separate commit).#3740pointed at an unrelated GitHub issue (anacp_delegate_waitwording report); the field report is This run failed with 409: "first child request does not match its inherited ordered prefix" | 本轮运行失败409 "first child request does not match its inherited ordered prefix" #3741 — comments and this body corrected. The title suffix is system-managed and left as-is.The branch was also rebased onto current master (
6f2b113bd).Refs #3741