Skip to content

[Bug]: React #185 loop in the composer context strip label compaction (BranchToolbar measure -> setOverflows), 0.0.43-nightly.20260921.2044 #12891

Description

@cristip73

Before submitting

  • I searched existing issues and did not find an open duplicate.
  • I included the available details needed to investigate the crash.

Area

apps/web

Steps to reproduce

No deterministic repro. The renderer crashed once to the crash screen while a thread was open on 0.0.43-nightly.20260921.2044. Retry recovered and it has not recurred since.

Because the stack decodes cleanly, the mechanism is reproducible by reasoning even though the trigger is not: it is the composer context strip's label compaction loop in BranchToolbar.tsx, not @legendapp/list as in #11308.

Expected behavior

The context strip settles on one of the two label states and the renderer stays up.

Actual behavior

React error #185 (Maximum update depth exceeded).

I extracted the client assets out of the installed app.asar for this exact build and matched the stack offsets against origin/main:

Minified frame Source
dist-BVnGsFIt.js:26:27485 / :27010 react-dom getRootForUpdatedFiber / enqueueConcurrentHookUpdate (the throw Error(185) site)
dist-BVnGsFIt.js:26:58533 / :58155 react-dom dispatchSetStateInternal / dispatchSetState
_chat-DOCEAHoD.js:8:59203 setOverflows(nextOverflows), the last statement of measure() in BranchToolbar.tsx
_chat-DOCEAHoD.js:8:59884 measure() inside useLayoutEffect(() => { measure(); }), the one with no dependency array
dist-BVnGsFIt.js:26:91668 / :96139 / :104933 / :96697 react-dom commit / work loop

So the loop is: every render runs the dependency-free layout effect, measure() recomputes needed vs available and calls setOverflows, and when the two states disagree about each other the flip-flop is synchronous until React gives up at 50 nested updates. The only guard is CONTEXT_STRIP_COMPACT_EXPAND_HYSTERESIS_PX = 16 in BranchToolbar.logic.ts:81, which cannot absorb a disagreement larger than 16px.

Note that the label width animation is not involved here: this machine has Reduce Motion on, so the [overflows] layout effect returns at the prefers-reduced-motion check before animating anything. The loop is measurement only.

Suspect, stated as a hypothesis rather than a measurement: #12805 (feat(web): truncate branch names and paths in the middle, merged 2026-09-20 23:43 -0300) is in this build. I confirmed it by finding the minified splitForMiddleTruncate in the shipped chunks. That PR changed the branch label that measure() measures, from

<span data-composer-label-motion className="block w-full min-w-0 max-w-[240px] truncate ...">{triggerLabel}</span>

to a MiddleTruncate, which renders two spans (min-w-0 truncate head plus shrink-0 tail) inside an inline-flex overflow-hidden wrapper. But measure() still reserves hidden text as

let textWidth = label.scrollWidth;
for (const inner of label.querySelectorAll<HTMLElement>("*")) {
  textWidth = Math.max(textWidth, inner.scrollWidth);
}
needed += Math.max(0, textWidth - label.getBoundingClientRect().width);

a max over the subtree, which describes one clipped span, not a head/tail split whose real width is closer to head + tail. The shipped bundle also shows the strip in a mixed state: the branch label is already MiddleTruncate, while the "Run on" and environment labels still render the old single truncating span, so the two label kinds are now measured by the same formula with different accuracy. Combined with the max-w-[240px] cap on the expanded label and max-w-0 in compact, the reconstructed needed can differ between the two states by much more than 16px.

I have not reproduced the numeric path, so treat the causal link as dated correlation plus a plausible mechanism, not proof.

Impact

Major degradation or frequent failure

Version or commit

T3 Code (Nightly) 0.0.43-nightly.20260921.2044

Environment

macOS 26.6.2 (arm64), MacBook Pro built-in Liquid Retina XDR plus a 5120x2880 external display, DPR 2. Reduce Motion is enabled system-wide. Window size at crash time was not captured. The thread path from the crash screen is omitted because it contains private identifiers.

Logs or stack traces

Error: Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
    at fi (t3code://app/assets/dist-BVnGsFIt.js:26:27485)
    at li (t3code://app/assets/dist-BVnGsFIt.js:26:27010)
    at Is (t3code://app/assets/dist-BVnGsFIt.js:26:58533)
    at Fs (t3code://app/assets/dist-BVnGsFIt.js:26:58155)
    at t3code://app/assets/_chat-DOCEAHoD.js:8:59203
    at t3code://app/assets/_chat-DOCEAHoD.js:8:59884
    at Uc (t3code://app/assets/dist-BVnGsFIt.js:26:91668)
    at sl (t3code://app/assets/dist-BVnGsFIt.js:26:96139)
    at xl (t3code://app/assets/dist-BVnGsFIt.js:26:104933)
    at sl (t3code://app/assets/dist-BVnGsFIt.js:26:96697)

This is the full stack as shown on the crash screen; it is not truncated.

Workaround

Retry from the crash screen recovered immediately and it has not recurred. Since the decision depends on strip width and on the current branch, model and environment label text, widening the window should move the strip out of the flapping band.

Activity

  1. juliusmarminge commented on Sep 21, 2026

    @juliusmarminge
    Member

    Triage: accepted as a real composer-strip measurement loop. Not a duplicate of #11308.

    The stack decode is correct against current main (unchanged since 0.0.43-nightly.20260921.2044 / 5781b5240):

    • _chat-…: setOverflows(nextOverflows) is the last statement of measure() in useLabelsOverflow (apps/web/src/components/BranchToolbar.tsx)
    • the caller is the dependency-free useLayoutEffect(() => { measure(); })
    • React Add drag-and-drop project reordering to the sidebar #185 is Maximum update depth exceeded

    setOverflows is a boolean, so React bails out when the decision is unchanged. The crash only happens when the compact pass and the expanded pass disagree about needed by more than CONTEXT_STRIP_COMPACT_EXPAND_HYSTERESIS_PX (16) in resolveContextStripLabelsCompact (BranchToolbar.logic.ts). Each layout effect then flips the other until React stops at 50 nested updates. Reduce Motion skipping the [overflows] width animation matches the code; this is measurement-only.

    This is the same family as #9390 / #9393 / #9481 (strip layout effects writing state that changes the next measure), but not the same loop. Those were strip labels vs resting composer controls. #11308 is @legendapp/list in the ProviderModelPicker chunk.

    Leading reconstruction (same caution as the report: mechanism from source, not a captured width trace):

    #12805 is in this nightly and changed the branch trigger that measure() reads from one truncate span to MiddleTruncate (head min-w-0 truncate + shrink-0 tail) in BranchToolbarBranchSelector.tsx. Env / workspace labels are still the old single span.

    measure() still recovers hidden text as max(subtree scrollWidth) - visible width. That describes one clipped span. For a split label that still fits under max-w-[240px]:

    • expanded: offsetWidth is head+tail, reservation is 0 → contributes the full string
    • compact (group-data-[compact]/composer-context:max-w-0): reservation is max(head, tail, wrapper) → typically head only

    The gap is about one tail (default 10 chars, last path segment up to 16). At text-xs that is larger than 16px, so there is a real flapping band. Short names that never split, and names already hard-capped at 240px, should agree and not loop. That also matches a one-shot crash that retry / a slightly different width escaped.

    A leftover #9393 fight with the resting-controls host would also exit through this setOverflows. Less likely as the new trigger; that path already has natural-width reservation. #12841 would remove that host and does not fix compact-vs-expanded label math.

    Fix: make needed independent of the current data-compact DOM (sum of the MiddleTruncate parts, or an unclipped probe — not max over subtree scrollWidth). Do not just raise the 16px hysteresis. Add a regression that compact vs expanded needed widths for a split-but-under-240px branch label agree within hysteresis, then park the strip in that band with Reduce Motion on.

    Window size and the branch / env / workspace strings from the crashed thread would confirm the band; not required to start.

  2. added
    acceptedfeature request accepted
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Sep 21, 2026
  3. cristip73 commented on Sep 21, 2026

    @cristip73
    Author

    Follow-up with facts I did not have when I filed this, after the crash recurred:

    Frequency. It is not a one-off. It now reproduces on ordinary timeline scrolling in many threads, and resizing the window does not move it out of the band. Raising Impact to "Major degradation or frequent failure" accordingly. Retry and "Reload app" both recover, and a reloaded thread crashes again on the next scroll.

    Build contents. 0.0.43-nightly.20260921.2044 is 5781b5240bd5. Two commits from the same night both touch this strip and are both ancestors of the build (confirmed with git merge-base --is-ancestor, and by finding their minified fingerprints in the shipped chunks):

    • feat(web): truncate branch names and paths in the middle #12805 (f7efb5354a) - branch label becomes MiddleTruncate, discussed above.
    • fix(web): keep composer controls visible while they fit #12837 (6cc7f7006e, "keep composer controls visible while they fit") - rewrites resolveRestingComposerControlsLayout into a stepped fit (icon-only labels first, then overflow) and changes measureRestingComposerControls to derive natural / iconOnly block widths from one tree. The comment that PR removed is a description of this exact failure: "The composer re-measures on every render, so without that margin a host sitting exactly on a threshold flips a block in and out until React gives up with 'Maximum update depth exceeded'." The new code keeps RESTING_CONTROLS_SLACK_PX = 1 only on promotions (step < previousStep).

    Why scroll. shouldUseRestingComposerLayout rests the composer only on a timeline scroll gesture (isScrollCollapsed). Resting relocates the controls into [data-chat-resting-composer-controls-host] inside the context strip, and from that moment two dependency-free layout effects read the same DOM and write state that changes each other's inputs: BranchToolbar measure() reserves resolveRestingComposerControlsNaturalWidth(hostedMeasurement) and toggles overflows, which changes the labels' max-w and therefore the host's flex-1 width; ChatComposer then re-fits its controls to the new hostWidth, which changes the DOM measure() reads next. The thrower in the stack is whichever setState lands on the 50th nested update, so setOverflows being on top does not by itself say which side is inconsistent.

    The thread that crashed was on branch feat/quiz-images-migrate (24 chars, so splitForMiddleTruncate does split it: head feat/quiz-imag, tail es-migrate), in a git project with the environment indicator shown. Nothing was streaming in that thread at the time; other threads on the same server were running turns.

    No commit on main after 5781b5240bd5 touches BranchToolbar*, ChatComposer.tsx, composerFooterLayout.ts or restingComposerControlsMeasurement.ts as of this comment, so the next nightly will carry the same code.

  4. 0xKa commented on Sep 21, 2026

    @0xKa

    Same build and identical stack offsets on Fedora 44/KDE Wayland. In my case, selecting one persisted thread reproduces the crash every time, no scrolling or streaming required, and retry/reload does not recover. The provider turn completed normally and the session remains ready with no error.

    Image
    T3 Code (Nightly) 0.0.43-nightly.20260921.2044
    Path: /e4f522e2-b083-4e05-a6fe-bc1ccff59006/95bbe62a-d389-4caf-8167-587804a234ed
    Time: 2026-09-21T10:44:21.985Z
    
    Error: Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
        at fi (t3code://app/assets/dist-BVnGsFIt.js:26:27485)
        at li (t3code://app/assets/dist-BVnGsFIt.js:26:27010)
        at Is (t3code://app/assets/dist-BVnGsFIt.js:26:58533)
        at Fs (t3code://app/assets/dist-BVnGsFIt.js:26:58155)
        at t3code://app/assets/_chat-DOCEAHoD.js:8:59203
        at t3code://app/assets/_chat-DOCEAHoD.js:8:59884
        at Uc (t3code://app/assets/dist-BVnGsFIt.js:26:91668)
        at sl (t3code://app/assets/dist-BVnGsFIt.js:26:96139)
        at xl (t3code://app/assets/dist-BVnGsFIt.js:26:104933)
        at sl (t3code://app/assets/dist-BVnGsFIt.js:26:96697)
    
  5. pascalandy commented on Sep 21, 2026

    @pascalandy

    I hit the same issue on T3 Code (Nightly) 0.0.43-nightly.20260921.2044 with the identical React #185 stack.

    Time: 2026-09-21T18:29:35.129Z
    Path: /50a3196d-6590-4f7c-a1b6-4b089c3cd081/a7ac8843-5d15-4c14-b4c1-b6d567c9b756

  6. ranvier2d2 commented on Sep 21, 2026

    @ranvier2d2
    Contributor

    Hit this twice today on MacOS desktop nightly, on two different threads in the same environment. First on 0.0.43-nightly.20260921.2058 at 2026-09-21T18:27:09.545Z, while scrolling a thread. The renderer died once a certain point in the timeline was on screen. Updated, then hit it again on 0.0.43-nightly.20260921.2071 at 2026-09-21T21:04:13.182Z.

    Short 15s video of the scroll that reproduces it: https://www.loom.com/share/b9e69a4b8c324fc9a97bac7b5e27668e

    Sorry about the music. I did not realize the recording was picking up audio.

    The asset hashes changed on each nightly (2044 was dist-BVnGsFIt.js / _chat-DOCEAHoD.js, 2058 was dist-Dd0QIH1a.js / _chat-DA5ugVFK.js, 2071 is dist-7UkdRZDe.js / _chat-f-Exmpka.js), but the offsets did not. Same React #185 frames, and the same two chat frames: _chat-….js:8:59203 and :8:59884. Both builds still die in setOverflows inside the dependency-free measure() layout effect. No pull request is linked on this issue.

    2058:

    Error: Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
        at fi (t3code://app/assets/dist-Dd0QIH1a.js:26:27485)
        at li (t3code://app/assets/dist-Dd0QIH1a.js:26:27010)
        at Is (t3code://app/assets/dist-Dd0QIH1a.js:26:58533)
        at Fs (t3code://app/assets/dist-Dd0QIH1a.js:26:58155)
        at t3code://app/assets/_chat-DA5ugVFK.js:8:59203
        at t3code://app/assets/_chat-DA5ugVFK.js:8:59884
        at Uc (t3code://app/assets/dist-Dd0QIH1a.js:26:91668)
        at sl (t3code://app/assets/dist-Dd0QIH1a.js:26:96139)
        at xl (t3code://app/assets/dist-Dd0QIH1a.js:26:104933)
        at sl (t3code://app/assets/dist-Dd0QIH1a.js:26:96697)
    

    2071:

    Error: Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
        at fi (t3code://app/assets/dist-7UkdRZDe.js:26:27485)
        at li (t3code://app/assets/dist-7UkdRZDe.js:26:27010)
        at Is (t3code://app/assets/dist-7UkdRZDe.js:26:58533)
        at Fs (t3code://app/assets/dist-7UkdRZDe.js:26:58155)
        at t3code://app/assets/_chat-f-Exmpka.js:8:59203
        at t3code://app/assets/_chat-f-Exmpka.js:8:59884
        at Uc (t3code://app/assets/dist-7UkdRZDe.js:26:91668)
        at sl (t3code://app/assets/dist-7UkdRZDe.js:26:96139)
        at xl (t3code://app/assets/dist-7UkdRZDe.js:26:104933)
        at sl (t3code://app/assets/dist-7UkdRZDe.js:26:96697)
    

    PS. Fix tried locally on current main (371b52d9d), as an uncommitted diff: +71 −8 across BranchToolbar.tsx, BranchToolbar.logic.ts, and BranchToolbar.logic.test.ts. It is not in the 2058 or 2071 nightlies, and it is not opened as a PR. It follows the note above: do not raise the 16px hysteresis. measure() was reserving hidden label text with max(subtree scrollWidth). A MiddleTruncate is two text leaves. Once max-w-0 collapses the wrapper, that max keeps the head and drops the tail, so compact and expanded disagree by about one tail and the layout effect flips setOverflows until React stops at 50 updates.

    reserveHiddenComposerLabelWidth sums the scrollWidths of the text leaves instead (no element children, non-empty text). Wrappers are skipped, because their scrollWidth shrinks to the clipped box. One-leaf labels (env, workspace, an unsplit branch) reserve the same width as before. A split label reports the same natural width in both states.

    Checked against the real function with the Chrome widths from a split-under-240 label (feat/cache-main-20260918-180825, head 143px, tail 62px). The old max differs by 62px and flaps. The sum differs by 0.2px and the compact bit settles. A dev build with that change stayed up on the thread that crashed on 2058, with the strip parked compact.

  7. added a commit that references this issue on Sep 22, 2026
    04d4cac
  8. M4NU5 commented on Sep 22, 2026

    @M4NU5

    Still present on 0.0.43-nightly.20260922.2096 (macOS, Darwin 25.6.0). Three crashes in under four minutes, all on the same persisted thread, identical frames each time:

    • 2026-09-22T15:04:37.183Z
    • 2026-09-22T15:05:56.999Z
    • 2026-09-22T15:07:52.587Z

    Path: /1fa31834-63db-4cfe-92d1-cf51722d14db/95f2f53c-e4d5-4098-8c75-825005322039

    Asset hashes for this build are dist-15As8Mk0.js / _chat-CmbDh2V8.js. The _chat offsets moved slightly from the ones reported above (8:59166 / 8:59847 instead of 8:59203 / 8:59884), so the chunk changed between .2071 and .2096, but I extracted the bundle from app.asar and both frames still land on the same code: setOverflows(...) as the last statement of measure(), called from the dependency-free useLayoutEffect. The 16 hysteresis in BranchToolbar.logic is unchanged.

    "Try again" recovers for a minute or two and then it crashes again on the same thread without any window resize. Reduce Motion is off here, so the animation path is not what keeps it stable or unstable.

    T3 Code (Nightly) 0.0.43-nightly.20260922.2096
    Error: Minified React error #185
        at fi (t3code://app/assets/dist-15As8Mk0.js:26:27482)
        at li (t3code://app/assets/dist-15As8Mk0.js:26:27007)
        at Is (t3code://app/assets/dist-15As8Mk0.js:26:58530)
        at Fs (t3code://app/assets/dist-15As8Mk0.js:26:58152)
        at t3code://app/assets/_chat-CmbDh2V8.js:8:59166
        at t3code://app/assets/_chat-CmbDh2V8.js:8:59847
        at Uc (t3code://app/assets/dist-15As8Mk0.js:26:91665)
        at sl (t3code://app/assets/dist-15As8Mk0.js:26:96136)
        at xl (t3code://app/assets/dist-15As8Mk0.js:26:104930)
        at sl (t3code://app/assets/dist-15As8Mk0.js:26:96694)
    
  9. areidyOTH commented on Sep 23, 2026

    @areidyOTH

    Same crash on Linux, on a newer nightly than the ones reported above: 0.0.43-nightly.20260922.2123 (tag d7819c1881). This build is still affected, and nothing merged to main since then touches useLabelsOverflow / measure().

    Environment

    • T3 Code desktop (AppImage) 0.0.43-nightly.20260922.2123, local server started by the desktop app
    • Ubuntu 26.04.1 LTS, GNOME on Wayland, x64
    • Reduce Motion on: GNOME org.gnome.desktop.interface enable-animations is false, which Electron reports as prefers-reduced-motion: reduce. With it on, the [overflows] width animation is skipped, so this crash comes from measurement alone.
    • Window maximized. The desktop trace shows no bounds change between app launch (about 5 hours earlier) and the crash.

    What happened
    The user was scrolling the timeline or switching threads (unsure which) when the renderer showed the error screen. This is the first time it has happened on this install. The server trace shows the client reading a project's t3.json about 2.5s before the crash, which fits a thread switch. There was no window resize and no renderer restart beforehand; setRendererReady fires only after the crash, for the error screen and then for "Try again". Other threads on the same server were streaming turns at the time (about 840 SDK messages in the 50s around the crash), which means frequent re-renders.

    T3 Code (Nightly) 0.0.43-nightly.20260922.2123
    Time: 2026-09-23T05:08:01.963Z
    Error: Minified React error #185
        at fi (t3code://app/assets/dist-Cs9Tyim4.js:26:27482)
        at li (t3code://app/assets/dist-Cs9Tyim4.js:26:27007)
        at Is (t3code://app/assets/dist-Cs9Tyim4.js:26:58530)
        at Fs (t3code://app/assets/dist-Cs9Tyim4.js:26:58152)
        at t3code://app/assets/_chat-CYY5ggvH.js:8:59097
        at t3code://app/assets/_chat-CYY5ggvH.js:8:59778
        at Uc (t3code://app/assets/dist-Cs9Tyim4.js:26:91665)
        at sl (t3code://app/assets/dist-Cs9Tyim4.js:26:96136)
        at xl (t3code://app/assets/dist-Cs9Tyim4.js:26:104930)
        at sl (t3code://app/assets/dist-Cs9Tyim4.js:26:96694)
    

    Stack mapping
    I extracted _chat-CYY5ggvH.js from app.asar and checked the frames against the source at the tag. The offsets moved again from .2096 (59166 / 59847 → 59097 / 59778), but both frames land on the same code:

    • 8:59097 is setOverflows(nextOverflows) at the end of measure() in useLabelsOverflow (apps/web/src/components/BranchToolbar.tsx).
    • 8:59778 is the layout effect with no dependency array, useLayoutEffect(() => { measure(); }).

    No server-side errors near the crash time; the server trace only has unrelated t3.json read misses and "no git remote" warnings.

    Comment prepared with t3 triage by Claude Code (Claude Opus 5.5), with input from a second LLM's analysis checked against the logs.

  10. cloudbring commented on Sep 24, 2026

    @cloudbring

    I'm seeing the React #185 error on 0.0.43-nightly.20260924.2187 (78af372cf46a) on macOS 26.7

    This happens when I scroll up on a brand new thread.

    T3 Code (Nightly) 0.0.43-nightly.20260924.2187
    Path: /ff879517-756d-4b39-8f07-75b513f341e1/07f885d9-c547-4f63-856e-5f562208ff0e
    Time: 2026-09-24T05:48:49.054Z
    
    Error: Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
        at fi (t3code://app/assets/dist-CdJVuaCi.js:26:27482)
        at li (t3code://app/assets/dist-CdJVuaCi.js:26:27007)
        at Is (t3code://app/assets/dist-CdJVuaCi.js:26:58530)
        at Fs (t3code://app/assets/dist-CdJVuaCi.js:26:58152)
        at t3code://app/assets/_chat-Cs-KPyQM.js:8:59561
        at t3code://app/assets/_chat-Cs-KPyQM.js:8:60242
        at Uc (t3code://app/assets/dist-CdJVuaCi.js:26:91665)
        at sl (t3code://app/assets/dist-CdJVuaCi.js:26:96136)
        at xl (t3code://app/assets/dist-CdJVuaCi.js:26:104930)
        at sl (t3code://app/assets/dist-CdJVuaCi.js:26:96694)
  11. juliusmarminge commented on Sep 25, 2026

    @juliusmarminge
    Member

    Fixed by #13555 — same BranchToolbar/MiddleTruncate measure disagreement that flipped compact vs labels past the 16px hysteresis (and could loop into React #185).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions