Conversation
…essions truncate_context_for_display_keep short-circuited on `len(ctx) <= len(msgs)` and returned ctx[:keep], slicing a trimmed (shorter) model context at the DISPLAY index. For large/compacted sessions this misaligned the forked context from the visible transcript and left a dangling assistant tool_use at the tail, producing broken/disjointed forks. Small sessions were unaffected (equal-length arrays), which masked the bug. Narrow the naive-slice guard to `len(ctx) == len(msgs)` so both divergent directions fall through to the signature matcher and the cut lands on a real turn boundary. Add an ambiguous-match-aware shorter-context fallback, gated to `len(ctx) < len(msgs)` so the context-longer (nesquena#5096 summary-prefix) path is unchanged. Upholds the model-context invariant in docs/rfcs/webui-run-state-consistency-contract.md (context must not contradict what the user can see). Live case b88a443d2f2b (618 display / 600 context) now forks to context[:425] (completed tool result) instead of context[:440] (dangling call_0366, ~15 turns out of sync). Tests: correct the compact-summary-fallback assertion (it encoded the contract-violating drop of a kept user turn's assistant reply) and add shorter-context alignment, ambiguous-boundary, and zero-match regressions.
🔬 Gate certification — GREEN ✅ (fork context alignment for compacted sessions)Certified head: What I ran (rebased worktree
|
| Gate | Result |
|---|---|
| Rebase onto current master | ✅ git apply clean |
| Codex (reproduce) | SAFE TO SHIP — 0 findings |
| Full pytest suite | ✅ 11988 passed, 0 failed |
| PR's own test | ✅ 11/11 (test_issue_branch_context_at_fork.py) |
Findings
✅ Correct alignment fix: truncate_context_for_display_keep — the old len(ctx) <= len(msgs) guard short-circuited to ctx[:keep], slicing the SHORTER model-context at the DISPLAY index (landing mid-turn, e.g. on an assistant tool_call whose result was past the cut) → forked compacted sessions got misaligned context. Fix narrows the fast-path to len(ctx) == len(msgs) (perfectly parallel) and falls through to the signature matcher for BOTH divergent directions (context longer = injected summary/prefix; context shorter = large-session trim dropped turns), so the cut lands on a real turn boundary. Ambiguous/weak-match fallback for large sessions where context rows lost id/timestamp errs toward UNDER-keeping (not a raw-display-index slice), preserving #5096 behavior for unreliable alignment. Any residual dangling tool_use is made wire-safe on the send path (_sanitize_messages_for_api strips unanswered tool_calls; gateway forwards none) — Codex confirmed no dangling tool_use escapes to the API. 11 branch-context tests + full suite green.
Recommendation to the next agent
Ready to merge — use branch gate-rebase/5563-fork-context-align (sha:57fc513a), NOT the PR's stale head d5b357cd. A surgical, well-documented data-correctness fix (forked compacted sessions get context aligned to display on a real turn boundary, no mid-turn/dangling-tool slice), Codex SAFE + 11 targeted tests + full suite green (0 failures). Backend/logic — no visible surface. concept 4/5 (real fork-alignment correctness for large sessions). Author @b3nw (T2). crit=3, data-correctness.
Gate-certifier layer (warm-up → gate → release). I do not merge/tag/deploy. Rebased onto current master; verified the fast-path narrowed to len(ctx)==len(msgs) with both divergent cases routed to the signature matcher (real turn-boundary cut), ambiguous-fallback errs under-keep (#5096 preserved), dangling tool_use wire-safe on send path (Codex), Codex SAFE + 11/11 + full suite green (0 failed). Cert valid for sha:57fc513a.
release #5563: align forked context to display for large/compacted sessions
|
Shipped in v0.51.861 — thanks @b3nw! 🎉 Your fix for fork context alignment on large/compacted sessions is live. Forking now aligns the cut to a real turn boundary in the same display coordinate space Gate summary before release:
Merged via release PR #5576 with your authorship preserved. Appreciate the clean, well-tested fix. |
…ted) sessions
Fork ("Fork from here") on a large/compacted session could cut context off a
real turn boundary — slicing mid-turn or leaving a dangling tool call, feeding
a malformed context to the model. Align the fork cut to a resolved turn boundary
via a signature matcher handling both compacted and non-compacted display
coordinate spaces; errs toward under-keeping with send-time sanitization backstop.
Co-authored-by: b3nw <b3nw@users.noreply.github.com>
…acted sessions + CHANGELOG
…s align WebUI keeps two parallel arrays per session: messages (display transcript) and context_messages (what's sent to the model). In large/compacted sessions they diverge. Forking / in-place truncation copies a prefix of both and relies on truncate_context_for_display_keep() to translate a display keep-index into the matching context index — an aligner that prefers a stable per-row `id`. But the model-context rows carried neither id nor timestamp (a 989-session live scan found 0 with any id), so alignment fell back to fragile content-signature matching that goes ambiguous on repeated tool calls / empty assistant turns. This closes the DATA gap (the alignment logic itself already prefers `id` and handles the compacted case via the matcher, shipped in nesquena#5563): mint a monotonic, session-unique integer `id` on the per-turn result rows AFTER the context restore and BEFORE both arrays are built, so the display and model-context copies of a logical row share the same id. - api/streaming.py: new _assign_stable_message_ids(); _restore_reasoning_metadata carries `id` forward across turns (as it already does timestamp); wired into all three streaming commit sites (main turn, retry, self-heal). - api/routes.py: same mint on the runs/MoA _handle_chat_sync commit path. - api/gateway_chat.py: mints ids on the two new rows of a gateway turn. - The `id` is stripped before the provider API call (not in _API_SAFE_MSG_KEYS), same treatment as timestamp — nothing new reaches the provider. session_ops.py alignment was already delivered by nesquena#5563 (id-preferring matcher + compacted-case fall-through), so no change is needed there now; tests updated to assert the post-nesquena#5563 behavior (id-bearing = exact cut; id-less = errs toward under-keeping, never the old raw-index mis-cut). Co-authored-by: b3nw <b3nw@users.noreply.github.com>
Thinking Path
messagestranscript and thecontext_messagesthe model actually receives. In large sessions the latter is trimmed/compacted, so it is shorter than and structurally divergent from the display.truncate_context_for_display_keepto cut the model context at the matching point so the two stay aligned.context_messageswas shorter thanmessages(every trimmed large session), the function short-circuited onlen(ctx) <= len(msgs)and returnedctx[:keep]— slicing the shorter context at the display index. The matcher that exists precisely to align divergent arrays never ran.tool_use. This is the "broken / disjointed fork."docs/rfcs/webui-run-state-consistency-contract.mdthat the model context must not "contradict what the user can see."What Changed
api/session_ops.py—truncate_context_for_display_keep:len(ctx) <= len(msgs)tolen(ctx) == len(msgs). Only perfectly-parallel arrays may be sliced at the raw display index; both divergent directions now fall through to the signature matcher.len(ctx) < len(msgs)so the context-longer (fix(session): rewind/fork leaves stale model context (branch, truncate, edit index) #5096 summary-prefix) path is unchanged.len(msgs) == 0guard above the length check (no behavior change).tests/test_issue_branch_context_at_fork.py:test_..._prefers_compact_summary_fallback: the old assertion[compact, u1]droppeda1(the assistant reply to a kept user turn) — see Contract Routing. New assertion[compact, u1, a1]matches the siblingpreserves_leading_compaction_rowcase.test_truncate_context_shorter_than_display_aligns_to_turn_boundary,test_shorter_context_ambiguous_boundary_keeps_forked_turn_via_weak_match, andtest_shorter_context_zero_match_falls_back_to_best_effort_prefix.No new dependencies; no build-step/bundler/framework changes; server logic stays in
api/.Why It Matters
Forking a large (trimmed/compacted) session produced a child whose model context was misaligned with its visible transcript and frequently malformed (dangling
tool_use). Users saw the fork "continue from the wrong place" or behave erratically on the first message. Small sessions were unaffected (arrays equal length), which masked the bug. The measured live case (618 display / 600 context) now forks to the correctcontext[:425](ending on a completed tool result) instead of the brokencontext[:440](ending on a danglingtool_use, ~15 turns out of sync).Verification
./scripts/test.shfull suite: 11,781 passed, 133 skipped, 2 xfailed, 1 xpassed. The two failures are pre-existing / unrelated (confirmed):test_gateway_lifecycle_controls…profile_scoped_agent_cli…also fails on the base commit (sandbox resolves the agent CLI totruenotecho), andtest_issue4536_service_tier…roundtrip…passes in isolation on this branch (order-dependent provider-config-state flake). This diff touches no gateway/provider/config code.master).truncate_context_for_display_keepagainst the real parent session returnscontext[:425], ends on a completedtoolresult, zero dangling tool_calls (wascontext[:440]with a danglingcall_0366).No browser/UI surface changed, so no before/after images apply.
Contract Routing
api/session_ops.pytruncate_context_for_display_keep/truncate_session_at_keep; product-semantics tests intests/test_issue_branch_context_at_fork.pyAGENTS.md,CONTRIBUTING.md,docs/CONTRACTS.md,docs/rfcs/webui-run-state-consistency-contract.md(Runtime/durability/state family — model-context reconstruction),docs/rfcs/stable-assistant-turn-anchors.md(root-cause context)Risks / Follow-ups
ctx[:keep]; alignment is genuinely impossible there. Any residual danglingtool_useis made wire-safe at send (_sanitize_messages_for_apion the streaming path; the gateway path forwards notool_calls/toolrows). Pinned by a regression test.id/timestamp, forcing fuzzy signature matching thatstable-assistant-turn-anchors.mdexplicitly warns against. Restoring stable per-message identity would make alignment exact. Tracked separately (144 affected sessions observed in a live store)._drop_trailing_unsatisfied_tool_callstrim; review showed it was redundant with the send-path sanitizer, missed Anthropic-nativetool_usecontent blocks and partial multi-tool tails, and could empty the context (making the send path fall back to the untrimmed display messages). Removed.Release note
Model Used / AI Usage Disclosure
claude-opus-4-8(Claude Opus 4.8)