Repository navigation
fix(web): switching a cold Claude thread to Codex no longer compacts first - #25986
juliusmarminge wants to merge 3 commits into
Conversation
…25943) These tests reproduce #25943. On a started, idle Claude thread whose 250k-token context is past the prompt-cache window, switching the model picker to Codex and sending dispatches `/compact` on Codex before the message. ChatView resolves the resume-compaction provider with the un-relaxed lockedProvider, so it stays on Claude while the composer sends with Codex. The fake environment gains opt-in providers, per-thread model selections and provider threads, and records acknowledged dispatchCommand calls. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…first ChatView offered Claude's resume compaction from the current conversation's context even when the composer's pick hands the thread off to another provider, so the send ran /compact on Codex before the message. The offer now requires that the send resumes the current conversation (no pending handoff), using the same rule as the handoff footer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a narrowly scoped web bug fix that prevents an automatic compaction from being sent to a newly selected handoff provider while preserving same-conversation compaction. It includes browser coverage for both handoff directions, and the remaining changes are test-harness-only. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Provider-aware fake environment apps/web/test/fakeEnvironment.ts |
The fake environment supports provider-specific configuration and thread data. It also records dispatched orchestration commands for browser tests. |
Handoff compaction behavior and browser coverage apps/web/src/components/ChatView.tsx, apps/web/src/components/ChatView.browser.test.tsx |
Resume compaction is unavailable while a handoff is pending. Browser tests verify that switching providers removes the compact-and-send option and dispatches one follow-up message to the selected provider. |
Priority:
Estimated code review effort: 2 (Simple) | ~10 minutes
Change: Bug fix · Severity of issue fixed: Medium
Possibly related issues
- Issue 23086 concerns evaluating compaction for a different saved Claude conversation. This change scopes the source compaction offer to the non-handoff case, but does not add cold-destination evaluation.
- Issue 22636 concerns recovery when the target provider’s separate conversation is full. This change does not implement full-target recovery.
Possibly related PRs
- pingdotgg/t3code#24334: Both changes affect Claude resume-compaction presentation; this change prevents the offer during a provider handoff.
- pingdotgg/t3code#21597: Both changes address Claude compaction lifecycle behavior.
- pingdotgg/t3code#24008: Both changes affect the pre-send compaction decision.
Suggested reviewers: t3dotgg, macroscopeapp
Merge Risk: 
Minimal · up to 9f65f
-
Switching providers no longer offers compaction for the conversation being left. No actionable merge-blocking risk is identified.
Pre-merge checks: 
4
-
Full details: Title check
-
Explanation
The title names the primary change: a cold Claude thread switched to Codex no longer compacts first. The changeset gates the resume-compaction offer on pendingHandoff being null in ChatView.tsx, and adds a browser test for that behavior. The title follows the conventional commit format from the template.
Full details: Description check
-
Explanation
The description follows the template. It has Problem, Change, Scope and approval, and Verification sections. The Problem section gives the reproduction and expected behavior. The Change section explains the pendingHandoff gate and the fake environment updates. Scope links the issue
#25943and names the out-of-scope case#23086. Verification lists the focused browser tests, the full browser project run, the check:changed result, and before/after screenshots. The author also states what was not checked. The Scope section links the triaged issue through an automated flow rather than an explicit maintainer approval comment, so the approval evidence is weaker than the template asks for. That gap is minor and does not make the description incomplete.
Full details: Linked Issues check
-
Explanation
Directly linked issue
#25943has one active coding requirement that this PR addresses: a Claude-to-Codex handoff must not send an automatic /compact on the destination. ChatView.tsx now sets resumeCompactionTokens to null when pendingHandoff is non-null, so the destination never receives the source conversation's resume-compaction offer. The new browser test 'sends a GPT-5.4 follow-up on a cold Claude Opus 4.6 thread without compacting first' covers this case. It fails on main with 'Compact and send' present and passes with the change. A Claude instance that resumes the same native conversation keeps the offer, which matches the issue's expectation that intended same-Claude compaction remains available. Manual /compact is not touched by this change. The issue also says returning to a saved, long, cold Claude conversation should offer compaction when eligible. This PR does not add that behavior for a different saved conversation. It excludes that case and cites#23086, and the issue itself describes#23086as a related, separate concern. Main does not offer compaction there either, so this is a pre-existing gap and not a regression. The issue's own 'Fix applied' section (parent/composer provider alignment, pre-dispatch revalidation after uploads) is not part of this PR. Those items describe the reporter's local fix. They are not stated as acceptance criteria in the Expected section.
Full details: Out of Scope Changes check
-
Explanation
All changed files connect to
#25943. (1) apps/web/src/components/ChatView.tsx (+2/-0) gates the resume-compaction offer on pendingHandoff, which is the direct fix. (2) apps/web/src/components/ChatView.browser.test.tsx (+151/-0) adds the regression test for the stale offer and a guard test for the reverse direction (Claude follow-up on a cold GPT-5.4 thread). (3) apps/web/test/fakeEnvironment.ts (+66/-30) adds provider lists, per-thread modelSelection and providerThreads, recorded dispatchCommand acknowledgement, and serverResolvedCommandContext. These changes are test infrastructure needed by the new browser test. They do not change production behavior. The PR description's media and triage notes are non-code and do not affect scope. No unrelated production logic was found in the reviewed summary.
-
Finishing Touches
-
📝 Generate docstrings
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
Problem
On a long, idle Claude thread with on-return idle compaction, picking Codex in the model picker kept the "Compact 250k" chip on the composer. Sending an ordinary message then dispatched an automatic
/compacton Codex (idleCompaction: "onReturn") and queued the message behind it. Reproduced on main (e923627) with the browser test below: after picking GPT-5.4 on a cold 250k-token Claude thread, "Compact and send" stays in the composer and the dispatches are/compactthenkeep going, both on Codex.Change
ChatView derived the resume-compaction offer from the current conversation's context window while the composer's pick handed the thread off to another provider. The offer now also requires
pendingHandoff === null, meaning the send resumes the current conversation.pendingHandoffis the value ChatView already computes for the "Context handoff pending" footer, and it uses the same instance, driver and continuation-group rule as the server. A Claude instance that resumes the same native conversation keeps the offer. A handoff destination never inherits it.The browser test runs the real AppRoot against the in-process fake environment. To support that,
apps/web/test/fakeEnvironment.tsnow accepts a provider list, records and acknowledgesdispatchCommand, advertisesserverResolvedCommandContextas the real server does, and lets a fixture thread carry its ownmodelSelectionandproviderThreads.Scope and approval
Fixes #25943
Triaged via the automated issue flow.
Out of scope: offering compaction when the destination is a different saved conversation that is itself cold (#23086). Main does not offer it there either.
Verification
apps/web/src/components/ChatView.browser.test.tsx:ChatView.tsx(expected document not to contain element, found <button aria-label="Compact and send">) and passes with the fix.browserproject: 103 files, 427 tests passed, including the new file. The first case takes about 3.4 s because the test file preloads the code-split ChatView module.vp run check:changed: format, lint, knip, typecheck and tests all pass.Evidence: harness captures. A real-app capture needs a real 250k-token Claude conversation past its prompt-cache TTL, which cannot be seeded. Each capture is the committed browser test (AppRoot with the fake environment, Chromium, 1400×900, DPR 1, light) with a
page.screenshotadded before and after the pick. Before = e923627'sChatView.tsx; after = 6ed7750. The setup is otherwise identical.What else changed in the after-pick row: the Compact chip is gone (intended). The other differing pixels come from animation timing, not the PR: the send button's stage art, a one-pixel edge of the fading model picker, and one mark on the thread rail. With 10% fuzz, 524 px differ, mostly the chip.
Capture driver: the committed test with
await page.screenshot({ path: "capture-25943/<side>-claudeAgent-<1-before-pick|2-after-pick>.png" })inserted before the model-picker click and after the GPT-5.4 button appears.Not checked: the real app against live providers (needs a cold, long Claude conversation); mobile, which has no on-return compaction offer.
Claude Opus 5.5 (lead, red test, implementation), GPT-6.1 Sol (reviews) in T3 Code via Claude Code.
🤖 Generated with Claude Code