fix(web): switching a cold Claude thread to Codex no longer compacts first#25986
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 /compact on Codex (idleCompaction: "onReturn") and queued the message behind it. Reproduced on main (e9236274a3) 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 /compact then keep 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. pendingHandoff is 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.ts now accepts a provider list, records and acknowledges dispatchCommand, advertises serverResolvedCommandContext as the real server does, and lets a fixture thread carry its own modelSelection and providerThreads.
Scope and approval
Fixes pingdotgg/t3code#25943
Triaged via the automated issue flow.
Out of scope: offering compaction when the destination is a different saved conversation that is itself cold (pingdotgg/t3code#23086). Main does not offer it there either.
Verification
apps/web/src/components/ChatView.browser.test.tsx:- "sends a GPT-5.4 follow-up on a cold Claude Opus 4.6 thread without compacting first" fails with main's
ChatView.tsx(expected document not to contain element, found <button aria-label="Compact and send">) and passes with the fix. - "sends a Claude Opus 4.6 follow-up on a cold GPT-5.4 thread without compacting first" passes on both. It guards against an offer leaking onto a handoff destination.
- "sends a GPT-5.4 follow-up on a cold Claude Opus 4.6 thread without compacting first" fails with main's
- Whole web
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.screenshot added before and after the pick. Before = e9236274a3's ChatView.tsx; after = 6ed77503dd. The setup is otherwise identical.
| Row | Before (e9236274a3) | After (6ed77503dd) | Diff0 | Diff (10% fuzz) |
|---|---|---|---|---|
| Cold Claude thread, GPT-5.4 picked, 1400×900 light | ||||
| Cold Claude thread, before any pick, 1400×900 light | 0 px differ | 0 px differ |
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
Checks
Failing1
- Agent context budgetGitHubFailed
Added 3 commits
Approvability
Verdict: Approved at 9f65f8d
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.
External image Recent review info
-
External image Run configuration
- Source mode: Auto
- Repository: pingdotgg/t3code
- Branch: fix/issue-25943
- File: .coderabbit.config.ts
- Commit: 9f65f8d
- Layers: Repository TS config
- Review profile: CHILL
- Plan: Team
- Run ID:
f20b5799-0332-4e39-92fd-759638b4cb21
External image Commits
Reviewing files that changed from the base of the PR and between 36d4d711b79c73eb33131944f73ea4a8c6156484 and 9f65f8dbca6c0f1ddea753f9443fad65433c242a.
External image Files selected for processing: 3
apps/web/src/components/ChatView.browser.test.tsxapps/web/src/components/ChatView.tsxapps/web/test/fakeEnvironment.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.
Walkthrough
The chat view now disables resume compaction while a provider handoff is pending. Browser tests cover follow-up sends in both provider-switch directions, using an expanded fake environment that supports provider-specific threads and records dispatched commands.
Changes: Provider handoff compaction
Layer / File(s) Summary Provider-aware fake environment
apps/web/test/fakeEnvironment.tsThe 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.tsxResume 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: External image Normal
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: External image 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: Passed 4
Check name Status Explanation Title check Passed 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 a… Description check Passed 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 explai… Linked Issues check Passed 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 resumeCom…Out of Scope Changes check Passed 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…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.