fix(web): avoid double compaction with compact instructions#23172

closed5
main
fix/issue-20222
opened19d agobyJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 3d ago

Problem

With the Compact chip enabled on an idle Claude thread, sending /compact Keep the interface decisions, drop the debugging output prepends a bare /compact before the instructed command. The first compaction ignores the instructions and the second operates on an already summarized conversation.

Change

Recognize lowercase /compact followed by whitespace when deciding whether to compact before sending; preserve existing case-insensitive recognition of a bare command. Mixed-case commands with instructions remain ordinary prompts and retain automatic compaction. The instructed command is sent once, preserving its text.

Scope and approval

Fixes #20222 GitCafe: https://git.cafe/pingdotgg/t3code/issues/20222 Triaged bug via the automated issue flow. Reproduction and scope. Web composer logic is shared with desktop; native mobile has no compact-before-send path.

Verification

Final code head: 1723431e82, with main b5a226e49c merged. Captures are explicitly from 70e5e384da → fafd55cbfa, before the final case correction.

  • Red: lowercase instruction/newline cases fail on main because compactBeforeSend is true and dispatch is forced to queue.
  • Green: 49/49 tests in ChatView.logic.test.ts pass before merging main; both changed files pass lint/format. A mixed-case instructed-command regression was reproduced red and fixed while preserving bare /COMPACT. Post-merge: 49/49 focused tests pass; lint on both changed files exits 0; web-package vp run typecheck exits 0 with existing Effect suggestions only.
  • Runtime: persisted-message transcript shows two sends before and one instructed send after. The Compact chip was exposed with a synthetic expired 250K usage snapshot over a real Claude session; native history was not enlarged.

Draft: proof audit is INSUFFICIENT. Evidence below is from an earlier implementation revision; final case-boundary correction is not captured. No affected matrix row is complete; A1/A2 are partial. Native fixture creation failed during VM overload/MCP registration. Remaining recordings and committed-fixture rebuild block readiness. Revised matrix · Evidence index · Native transcript.

Environment: Linux Chrome 154, DPR 2, zoom 1, 16px CSS font, 1440×900 and 390×900; real app with synthetic stale 250k eligibility over real 83,918-token Claude history.

Final round 3: both isolated reviewers found no code defects and returned FIX_FIRST for proof. The lowercase runtime behavior is unchanged from captured head; the mixed-case correction restores main behavior. No capture is claimed at final head. Revised A2 now requires lowercase keyboard input; previous uppercase A2 footage is a control only.

Evidence comparison table

These completed-state stills supplement the recordings; they do not replace missing interactions.

Main pair: exact script and UTC milestones (above the media)

For each revision: create a fresh real conversation, send the identical OK prompt, observe OK and terminal run, seed client usage as above, remount through Settings/history.back, verify chip, paste the exact instructed command, start recording, click Compact and send once. Observe compact activity/queue and result; stop recorder. No synthetic provider responses or intercepted dispatches were used.

AFTER recording start 22:53:50.626Z. Original persisted 22:53:55.719Z (+5.093s relative to start). Native compaction started 22:53:57.038Z, completed 22:54:08.724Z; terminal provider-turn event 22:54:09.836Z. Video duration 36.913s.

BEFORE recording start 22:57:27.942Z. Bare persisted 22:57:30.110Z (+2.168s), original persisted 22:57:30.523Z (+2.581s, queued). Bare compaction completed 22:57:44.650Z. Instructed native failure 22:57:53.938Z; terminal provider-turn event 22:57:54.428Z. Video duration 32.010s. Encoded UI frames lagged the event stream: final frame still shows Working/Thinking. Terminal failure is visible in supplemental completed-state stills, not certified inside this original video. UTC event offsets must not be treated as frame-accurate UI timings.

Pre-send stills were taken immediately before recording. Original post stills followed observed terminal text; BEFORE still retains transient Working/spinner UI and a failure separator. The later completed-state stills show the failure text. Wall-clock/message times and sidebar history are not identical between runs, so these are behavior comparisons, not a pixel-identical controlled experiment.

Before interaction recording (terminal UI incomplete): A1-1440-light-before.mp4

After interaction recording (native instructed success): A1-1440-light-after.mp4

Pre-send pair:

Before

Before — A1-1440-light-before-pre.png

After

After — A1-1440-light-after-pre.png

Zero threshold, amplified 8×

Absolute difference, zero threshold amplified 8×

Threshold >16/255

Threshold difference >16/255

Original post pair (BEFORE transient Working UI):

Before

Before — A1-1440-light-before-post.png

After

After — A1-1440-light-after-post.png

Zero threshold, amplified 8×

Absolute difference, zero threshold amplified 8×

Threshold >16/255

Threshold difference >16/255

Supplemental completed-state views — stills only

Script: reopen the same already-completed native proof threads; do not send again. Set viewport to 1440×900 or 390×900 CSS px, emulate light/dark, retain start-of-thread scroll (all observed nonzero scrollTop lists empty), save still. Before views were collected after the infrastructure failure and retain timeout/reconnect banners. After views were recaptured after dismissing an unrelated provider-update notification and expanding/focusing the narrow composer. No native turn ran for these stills. Their capture window is later than the original recordings; exact wall clock was not frozen. These do not certify any missing send/timing/toggle/expansion recording.

1440×900 CSS px — light

Script: same completed proof threads, no send; resize, set appearance, observe final state; save. Later captures, not matched wall clock.

Before

Before — A1-completed-before-1440-light.png

After

After — A1-completed-after-1440-light.png

Zero threshold, amplified 8×

Absolute difference, zero threshold amplified 8×

Threshold >16/255

Threshold difference >16/255

1440×900 CSS px — dark

Script: same completed proof threads, no send; resize, set appearance, observe final state; save. Later captures, not matched wall clock.

Before

Before — A1-completed-before-1440-dark.png

After

After — A1-completed-after-1440-dark.png

Zero threshold, amplified 8×

Absolute difference, zero threshold amplified 8×

Threshold >16/255

Threshold difference >16/255

390×900 CSS px — light

Script: same completed proof threads, no send; resize, set appearance, observe final state; save. Later captures, not matched wall clock.

Before

Before — A1-completed-before-390-light.png

After

After — A1-completed-after-390-light.png

Zero threshold, amplified 8×

Absolute difference, zero threshold amplified 8×

Threshold >16/255

Threshold difference >16/255

390×900 CSS px — dark

Script: same completed proof threads, no send; resize, set appearance, observe final state; save. Later captures, not matched wall clock.

Before

Before — A1-completed-before-390-dark.png

After

After — A1-completed-after-390-dark.png

Zero threshold, amplified 8×

Absolute difference, zero threshold amplified 8×

Threshold >16/255

Threshold difference >16/255

Every visible difference and comparison limits

Original pre-send pair: the composer text, chip, model/options, controls and layout match. The thread title/breadcrumb differs (Reply OK before, Confirm Instructions after); sidebar has additional proof threads, different titles, branch badges, relative ages and provider glyph states. Initial message times differ (10:56/10:57 versus 10:53). These differences predate the tested dispatch and are fixture/timing differences.

During/post-send: BEFORE adds a separate bare command bubble, queues the original (visible Queued 1 strip), then shows both bubbles, first compaction separator and subsequent failure activity/error. AFTER shows the original bubble alone, compacts directly and settles to a success separator. Summaries are 1.61K versus 1.59K. Message positions and vertical gaps change with that extra content. Working elapsed counters, thinking/compaction labels, sidebar spinner/relative ages, queue strip, submit/stop button color/icon, and emptied composer change at different times. BEFORE original post still retains Working and red stop while AFTER is idle with blue send; this is a capture/paint timing mismatch, not an additional code change.

Supplemental completed pairs: BEFORE has Claude provider status timeout banner, Context compaction failed, Not enough messages to compact., and reconnect warning; AFTER has one successful instructed command and no timeout/reconnect banner. The extra messages/banners shift all downstream content and alter wrapping at 390px. Both themes apply correctly. Wide AFTER details include a Lineage entry for the fork created during fixture exploration; BEFORE details lack it. Sidebar proof-thread counts/ages/order differ with capture time. Narrow width hides the desktop sidebar/details and abbreviates controls/model; both final narrow composers were expanded/focused, with placeholder/caret and small layout changes governed by available width. A provider update toast initially obscured the after narrow view; it was dismissed and the final uploaded view replaced it. There are no masks, erased banners, composited app states, or fake terminal frames.

Differences above include unrelated infrastructure/state changes. Do not attribute every nonzero diff pixel to this regex fix. The diff images are supplementary measurements of these actual frames, not a no-regression verdict.

Diff method and rendering environment

Every supplied A1 still pair has an absolute RGB zero-threshold difference, amplified 8× and clipped at 255; its companion threshold image marks pixels magenta when maximum absolute RGB channel delta is >16/255, black otherwise. No masks, registration, crop, or excluded pixels. Counts/bounding boxes are in diff-metrics.json. Original pre pair: 5,249 pixels differ at zero threshold / 4,203 above 16. Original post pair: 45,885 / 12,616. Large supplemental differences include the banners, history, lineage, and layout changes listed above.

Linux VM fs-21, Chrome headless 154 through T3 native collaborative preview, own tab only. CSS viewports 1440×900 and 390×900, DPR 2, zoom 1, body font size 16px, font stack -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; actual resolved font and OS font scaling were not independently measured. Wide screenshots are 1280×800 downscaled by preview from a 1440×900 CSS viewport; narrow screenshots are 780×1800. Main videos encode 2880×1800. Native recorder uses sparse variable-timing screen frames with holds; it is not continuous high-frame-rate motion proof. No motion preference override was applied. All three MP4s fully decoded successfully using full ffmpeg; the bundled preview ffmpeg only supports WebM demuxing and initially rejected valid MP4s. video-decode-validation.json contains actual decode reports. All three public MP4 downloads were fetched with curl and matched local bytes and SHA-256 exactly (upload-video-verification.json). A Python urllib fetch was rejected with HTTP 403; curl succeeded with HTTP 200.

Coverage ledger: every matrix row

RowCapturedMissing / reason
A11440/light before and after send recordings; pre/post stills, all four completed-state width/theme still pairs and paired diffs; persisted counts/queue/native successThree required interaction pairs (1440/dark, 390/light, 390/dark); original before video lacks visually settled terminal error; same-clock/same-sidebar matching not achieved. Runtime failure prevented fresh fixtures.
A21440/light BEFORE keyboard send in long partial recording, paired pre/post stills for that revision only, persisted case/double-space text and two sendsAFTER and other three viewport/theme pairs missing. Native uppercase /Compact is not an installed command: it became provider prose, rather than successful native compaction. Preserved text in DB is authoritative. Runtime failed on next fresh fixture.
A3Failed fresh OK fixture and infrastructure blocker retained; multiline input was never sentAll eight interaction recordings and equivalent seeded fixtures unavailable after MCP-registration failure/overload.
A4NoneAll four before/after interaction pairs missing, including 390px expand-only/zero-send assertion. Completed narrow stills are not an expansion test. Same unavailable native runtime.
A5NoneBoth outer-whitespace and actual-tab inputs missing at all four viewport/theme combinations (eight pairs). Same unavailable native runtime.
C1NoneBare exact and uppercase exact control pairs unverified; same unavailable native runtime.
C2NoneAll three negative-boundary control pairs unverified; same unavailable native runtime.
C3NoneOrdinary Reply OK with Compact enabled control pair unverified. Initial fixture OK prompts were not this control.
C4NoneOpt-out and reverse toggle interaction pairs unverified; no toggle evidence substituted.
C5NoneFresh-time, 99999, 199999 and 200000 eligibility control pairs unverified. Only prescribed synthetic 250k stale seed was exercised.
C6NoneEmpty Resume/running/held-queue gate interaction pairs unverified. The stalled fixture is infrastructure evidence, not a valid intentional running/held-queue gate test.
U1NoneDesktop Electron shell unavailable/unexercised in this headless Linux preview workflow. Web evidence does not certify shell/IPC/OS keybindings.
U2N/A per matrixNative mobile has no compact-before-send feature. 390px evidence is responsive web, not native mobile.

Remote/relay/tunnel execution, actual stale 250k native history, native mobile behavior, and all additional pure-function boundary checks remain unverified by this capture task. No tests substitute for missing captures.

Infrastructure failure and stopping condition

After the successful A1 and before-only A2 runs, fresh fixture thread 510d0e3b-0075-4bb0-891c-854da97f54f5 submitted the OK prompt at 23:01:23.918Z and never produced OK. App reported Claude Agent CLI is installed but failed to run. Timed out while running command. and VM freeze/CPU load 24 on 4 cores. At 23:04:51.630Z, server logged ProviderAdapterTurnStartError, caused by ClaudeAgentSdkQueryRunnerError, caused by Claude Code did not answer the MCP server registration. At 23:06:07.511Z it logged an event-loop stall of 32,823 ms. Subsequent source-control reads also timed out in GitCafeCredentials.get after 10000ms; no forge operation was initiated by this tester. The UI remained Working/reconnecting until reload exposed the provider error. The lead independently confirmed overload and timer oversleep and instructed ending this evidence step rather than retrying unavailable fixtures.

The A2/blocked-fixture recording includes substantial waiting; its duration is about 16m09.90s. A2 persisted bare/original at 22:59:48.646Z / 22:59:49.550Z. Keyboard action started 22:59:47.740Z; terminal-case inspection finished 23:00:58.841Z. Original recorder start was not retained, so exact A2 video offsets are not certified. The later fixture submission and failure are separately timestamped above. No retry loop or paid attempt was started after this failure.

Own unused before dev server/browser frontend on 13994/5954 and final before-only frontend on 5955 were stopped using verified port-owner PIDs/cwds (205247, 205170, 207783). Primary 15470/7430 remained listening (187960 / 166813); neither was stopped. Detached before checkout is /home/ubuntu/wt/issue-20222-before; existing head checkout is unchanged and clean. No fixture commit: a committed seeder could not repair the unavailable native prerequisite; the exact working client seed is documented above and in the supplied matrix. No live T3 database was written or started against; isolated .t3 only. Preview itself owns its screenshot/attachment artifact files beneath the host's T3 artifact directories.

Unverified: desktop/Electron shell; remote/relay/tunnel modes; a genuinely stale native 250K session.

Models: Claude Opus 5.5 and GPT-6.1-Sol, through the Claude Code and Codex harnesses in T3 Code.

Takeover

Evidence-only follow-up, 2026-10-22. Original implementation and description by @juliusmarminge are preserved above. No code or test changes, claim comment, merge, or watch.

Evidence

This dated appendix supersedes the earlier statement that no final-head capture exists. Proof remains INSUFFICIENT: six complete scoped pairs of fourteen.

Source-derived matrix · All new media, scripts, timings and diffs · Reproducible proof package · Final independent audit.

Before = b5a226e49c; after = 1723431e82. Historical captures retain their original SHAs. Recordings show fresh real Claude Sonnet 5.5 OK conversations, then synthetic stale 250k client eligibility. Native counts were 91,680 final / 91,720 baseline; this does not certify actual stale 250k native history. Each row uses the exact matrix input and action; driver/timing records accompany the media. Original final recording timelines remain incomplete; P3 recovery and suffix replacement also have missing step boundaries. No missing offsets are inferred.

Linux Chrome 154 through T3 preview, DPR 2, zoom 1. Wide rows: 1440×900 CSS, light; P3: 390×900 CSS, dark. Native video: 2880×1800 / 780×1800; wide stills: 1280×800. Each diff below is the post-send full-frame RGB comparison: zero threshold amplified 8×, and max-channel delta >16/255. Pre-send equivalents and exact metrics are in the index. Partial rows are not terminal-equivalent comparisons.

What else changed: titles, sidebar order/counts/ages, clocks, source-control counts, native token/summary totals, response prose/wrapping, elapsed labels, scroll/message placement, and a provider-update toast differ. Raw diffs include those fixture differences and do not isolate predicate pixels. The complete observed difference list is in the reports. No claim of pixel equality or “nothing else changed.”

Verification log
  • Independent code review found no blocking code defect; evidence kept its overall FIX_FIRST verdict. Review.
  • Tests unchanged: 49 → 49 in ChatView.logic.test.ts; focused test run 49/49 and lint on both changed files exited 0. No implementation, test pruning, or new commit in this evidence-only follow-up.
  • Final observed head has 31 successful checks, 0 pending, 0 failing. Draft remains. No merge or watch requested or performed.
  • Final independent audit verified all 234 manifest entries, all 28 comparisons / 56 diff images, and an independent affected P1 rebuild. Pixel deltas versus delivered P1 are measured nonzero: pre 11,913 / post 13,017 pixels at zero threshold, with fixture differences disclosed. Rebuild recording visibly settles and preserves one instructed send.
  • Remaining: five baseline terminal endings (P2, K1, K3-upper, K5-full, K5-restore); three final terminal endings (K4-suffix, K4-prose, K5-full); baseline P3 collapsed expansion; incomplete recording timelines. Completed persisted events and later stills do not replace missing frames.
  • Unverified: required web matrix remains partial as listed. Desktop shell is shared-source-only; native mobile has no compact-before-send path. Remote/relay/tunnel and other providers were not exercised; no transport or adapter change is claimed.
  • Friction: host event-loop stalls up to 71 seconds; stale recorder endings despite terminal snapshots; repository-grouped sidebar hid the expected project control; narrow and duplicate-label locators needed adaptation. Correct project-owned navigation resolved the wide locator failure.
  • Models/harnesses: gpt-6.1-sol / Codex for evidence and independent proof audits; claude-opus-5-5 / Claude Agent for independent code review, through T3 Code.

Checks

21 successful · 13 skipped
All checks passed
ad4dfd1

Added 5 commits

  • Merge main into fix/issue-202221723431
  • fix(web): mixed-case /Compact with instructions still compacts a stale session first8812169
  • fix(web): /compact with instructions no longer compacts a stale session twicefafd55c
  • test(web): reproduce issue 20222 /compact with instructions double-compactsfc2cc54
  • Merge origin/main into fix/issue-20222ad4dfd1

Merged main at fbc6a08159 into this branch as ad4dfd145f and resolved the planner conflict. Main now includes the lowercase instructed-command fix via #24008. The remaining implementation difference preserves automatic precompaction for mixed-case instructed /Compact messages, which the native reproduction treated as prose. Bare commands remain case-insensitive. Focused logic tests: 49/49 passed; changed-file lint and format passed. Existing captures predate this merge; proof gaps remain open and this PR remains a draft.

Note

🤖 Claude Opus 5.5 responding on behalf of Theo

Closing: this is covered by #24008, which landed. Reopen if a needed part is still missing.