fix(shared): bound the file-link label so bracket runs stop rescanning - #5782
Conversation
FILE_LINK_TOKEN_REGEX let the label body repeat without a bound. Every whitespace in the composer is therefore a candidate start: the engine scans the rest of the text for a closing "]", fails, and rescans from the next whitespace. On a run of unterminated brackets that is quadratic, and collectComposerInlineTokens runs on raw composer text, so pasting one freezes the tab. Capping the body makes each attempt constant-bounded: 120k chars of " [[" 2838ms -> 33ms 60k chars 676ms -> 17ms 30k chars 167ms -> 8ms The cap cannot reject a link anyone could meaningfully write. Only a basename survives the `label !== basename` check below it, and the longest filename common filesystems allow is 255. Verified equivalent to the old pattern over 156,504 generated inputs built from [ ] ( ) \ @ " whitespace and path-ish fragments, comparing match count, offsets and every capture group: zero differences. The single intended difference is at the cap itself, which the two new boundary tests pin. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Approved 9b71e34 This is a straightforward performance bug fix that bounds a regex to prevent quadratic backtracking on pathological input. The 512-character limit is generous (max filename is 255) and includes comprehensive tests verifying the boundary behavior. You can customize Macroscope's approvability policy. Learn more. |
## What's Changed * fix(server): favicon resolution no longer pins the event loop by @murenovich in pingdotgg/t3code#5538 * fix(shared): bound the file-link label so bracket runs stop rescanning by @tsouth89 in pingdotgg/t3code#5782 ## New Contributors * @murenovich made their first contribution in pingdotgg/t3code#5538 * @tsouth89 made their first contribution in pingdotgg/t3code#5782 **Full Changelog**: pingdotgg/t3code@v0.0.33-nightly.20260809.1045...v0.0.33-nightly.20260809.1047 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.33-nightly.20260809.1047
## What's Changed * fix(mobile): reconnects no longer shift the thread list by @t3dotgg in pingdotgg/t3code#5372 * feat(web): drag pinned threads into your own order by @t3dotgg in pingdotgg/t3code#5581 * chore(ci): vouch StiensWout by @t3-code[bot] in pingdotgg/t3code#5637 * feat(desktop): remember recently used sites in the Browser panel by @chrisdeeming in pingdotgg/t3code#5270 * chore: vouch chrisdeeming by @t3-code[bot] in pingdotgg/t3code#5641 * feat(web): make sidebar artwork theme-aware by @maria-rcks in pingdotgg/t3code#5636 * fix(web): reconnect the composer seam for remote non-Git projects by @caezium in pingdotgg/t3code#5633 * fix(web): show Stop button while input is pending by @ipanasenko in pingdotgg/t3code#5554 * feat(web): fold plan mode and token-by-token output into Legacy features by @t3dotgg in pingdotgg/t3code#5664 * feat: sidebar v2 is now the default sidebar by @t3dotgg in pingdotgg/t3code#5672 * fix(server): stop PR status lookups amplifying GitHub rate limits by @t3dotgg in pingdotgg/t3code#5673 * fix(web): delay transient reconnect warnings by @t3-code[bot] in pingdotgg/t3code#5670 * fix(web): inherit terminal size in simple typography by @chrisdeeming in pingdotgg/t3code#5628 * fix(server): stop the reaper from silently killing live background subagents by @t3dotgg in pingdotgg/t3code#5677 * fix(desktop): zoom shortcuts no longer die when the preview browser has focus by @t3dotgg in pingdotgg/t3code#5691 * feat(mobile): one sheet for model and thread settings by @t3dotgg in pingdotgg/t3code#5625 * feat(usage): usage page reading provider transcripts across environments by @t3dotgg in pingdotgg/t3code#5684 * fix(web): usage chart no longer makes Claude look like the bigger spender by @t3dotgg in pingdotgg/t3code#5697 * fix(web): persist diff view mode by @leorivastech in pingdotgg/t3code#5731 * feat(web): show how many subagents are running at a glance by @t3dotgg in pingdotgg/t3code#5745 * fix(web): add missing cursor-pointer styling to dropdowns and interactive buttons by @naMqe-h in pingdotgg/t3code#5716 * fix(server): stop Claude resume handshakes from completing turns that never ran by @gfsaaser24 in pingdotgg/t3code#5710 * chore: vouch gfsaaser24 by @t3dotgg in pingdotgg/t3code#5761 * chore: vouch saphid by @t3dotgg in pingdotgg/t3code#5763 * fix(server): stop Codex threads with queued follow-ups by @t3dotgg in pingdotgg/t3code#5762 * fix(web): usage page loses the cost quality panel, gains a back button by @t3dotgg in pingdotgg/t3code#5756 * feat(server): agents can now open the images you paste into chat by @t3dotgg in pingdotgg/t3code#5757 * fix(web): pinned reorder no longer reshuffles while writes land by @t3dotgg in pingdotgg/t3code#5767 * feat(web): overhaul project settings into a real settings page by @t3dotgg in pingdotgg/t3code#5768 * fix(web): usage totals no longer jump while devices report in by @t3dotgg in pingdotgg/t3code#5772 * fix(server): settle no longer leaves monitors and dev servers running by @t3dotgg in pingdotgg/t3code#5774 * feat: pick worktree or current checkout per project by @t3dotgg in pingdotgg/t3code#5766 * fix(web): sidebar rows show the branch again, not a truncated plan step by @t3dotgg in pingdotgg/t3code#5776 * feat(server): vp run migrate-dev-db seeds worktree dev dbs with real data by @t3dotgg in pingdotgg/t3code#5773 * feat(web): keep unsent drafts one click away in the sidebar by @t3dotgg in pingdotgg/t3code#5777 * feat(web): project icons can be chosen manually by @t3dotgg in pingdotgg/t3code#5775 * fix(server): one greedy agent process no longer takes down the whole server by @t3dotgg in pingdotgg/t3code#5788 * ci: label-gated hosted-web preview deploys by @t3dotgg in pingdotgg/t3code#5465 * Add cross-platform mobile usage dashboard by @juliusmarminge in pingdotgg/t3code#5743 * fix(web): preserve desktop route during Clerk auth by @wobsoriano in pingdotgg/t3code#5770 * fix(web): match create theme and import theme buttons to the standard outline style by @UtkarshUsername in pingdotgg/t3code#5860 * fix(server): favicon resolution no longer pins the event loop by @murenovich in pingdotgg/t3code#5538 * fix(shared): bound the file-link label so bracket runs stop rescanning by @tsouth89 in pingdotgg/t3code#5782 * fix(web): thread title button no longer eats the drag area by @nathangerday in pingdotgg/t3code#5857 * fix(web): unify usage page chrome by @t3-code[bot] in pingdotgg/t3code#5823 * fix(shell): add ~/.local/bin to the Windows CLI resolver so native-installed providers are found by @arhxam in pingdotgg/t3code#5074 * fix(web): match settings search shortcut styling to command palette's by @UtkarshUsername in pingdotgg/t3code#5841 * fix(mobile): long-pressing a thread row no longer navigates into the thread by @juliusmarminge in pingdotgg/t3code#5901 * fix(server): usage no longer double-counts forked Codex sessions by @t3dotgg in pingdotgg/t3code#5887 * fix(server): sandbox user-provided SVGs by @t3dotgg in pingdotgg/t3code#5916 * fix(web): match usage titlebar text styling by @t3-code[bot] in pingdotgg/t3code#5897 * Move project settings to contextual project routes by @juliusmarminge in pingdotgg/t3code#5923 * Retain thread sidebar data when navigating to /settings so back navigation is instant by @juliusmarminge in pingdotgg/t3code#5930 * Automate production mobile EAS releases by @juliusmarminge in pingdotgg/t3code#5609 * Add settings and usage breadcrumbs by @juliusmarminge in pingdotgg/t3code#5929 * fix(web): correct model picker trigger padding by @Chrono-byte in pingdotgg/t3code#5935 * fix(web): show worktree icon in sidebar v2 by @tris203 in pingdotgg/t3code#5909 * fix(web): enable restore defaults after theme mix changes by @Lucenx9 in pingdotgg/t3code#5928 * fix(web): trait menu closes after you pick a level by @t3dotgg in pingdotgg/t3code#5879 * fix(web): align project name with headline by @carterwsmith in pingdotgg/t3code#5864 * fix(web): update pills use readable theme foregrounds by @chrisdeeming in pingdotgg/t3code#5938 * fix(web): use themed confirmation dialogs by @StiensWout in pingdotgg/t3code#5624 * fix(web): use import/export-appropriate icons for theme buttons by @UtkarshUsername in pingdotgg/t3code#5964 * fix(mobile): detect PowerShell cmdlet errors in work log rows by @myacoub91 in pingdotgg/t3code#5726 * fix(mobile): stop Android user bubbles with code blocks from overlapping by @Brechard in pingdotgg/t3code#5659 * fix(mobile): parse EAS fingerprint JSON by @juliusmarminge in pingdotgg/t3code#5991 ## New Contributors * @chrisdeeming made their first contribution in pingdotgg/t3code#5270 * @wobsoriano made their first contribution in pingdotgg/t3code#5770 * @murenovich made their first contribution in pingdotgg/t3code#5538 * @tsouth89 made their first contribution in pingdotgg/t3code#5782 * @nathangerday made their first contribution in pingdotgg/t3code#5857 * @carterwsmith made their first contribution in pingdotgg/t3code#5864 * @myacoub91 made their first contribution in pingdotgg/t3code#5726 **Full Changelog**: pingdotgg/t3code@v0.0.32...v0.0.33 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.33
fix(shared): bound the file-link label so bracket runs stop rescanning
What changed
FILE_LINK_TOKEN_REGEXinpackages/shared/src/composerInlineTokens.tslet thelabel body repeat without a bound:
/(^|\s)\[((?:\\.|[^\]\\])*)\]\(([^)\s]+)\)(?=\s)/gThis caps it at 512 characters. Two files, +36/-1.
Why it should exist
With an unbounded body, every whitespace in the composer is a candidate start:
the engine scans the rest of the text for a closing
], fails, then rescansfrom the next whitespace. On a run of unterminated brackets that is quadratic,
and
collectComposerInlineTokensruns on raw composer text — so pasting onefreezes the tab.
Measured on
" [["repeated, before → after:Doubling the input quadruples the old time, which is the quadratic signature.
Scope, stated honestly: ordinary pastes are unaffected, because a
]anywhereahead bounds each attempt. A 104k-char markdown table of
[a] [b] [c]cells anda 172k-char code paste full of
arr[i][j][k]both measure 0ms before and after.The trigger is a long run of
[with no closing bracket following. So this is aself-inflicted UI freeze on degenerate or hostile paste content, not a server-side
availability issue — worth fixing, not worth alarm.
Why the cap is safe
The cap cannot reject a link anyone could meaningfully write. Only a basename
survives the
label !== basenamecheck immediately below the match, and thelongest filename any common filesystem allows is 255.
How it was verified
Behavioural equivalence against the old pattern over 156,504 generated
inputs — exhaustive triples over an alphabet of
[ ] ( ) \ @ ", tab, newline,space and path-ish fragments; structured
[label](path)shapes across prefix andsuffix contexts; and 150,000 randomized fuzz strings. Compared match count, match
offsets, and every capture group.
Zero differences.
The single intended difference is at the cap itself: a 512-character label still
matches, 513 no longer does. The two new boundary tests pin exactly that, and a
third asserts the bracket-run case completes in under a second.
No UI change, so no screenshots — this is pure tokenizer logic behind the
composer.
Note
Low Risk
Localized tokenizer regex change with extensive equivalence testing; only rejects implausibly long link labels and improves client-side paste performance.
Overview
Fixes composer freezes when pasted text has long runs of unclosed
[brackets by capping how much of a[label](path)label the file-link regex will scan.FILE_LINK_TOKEN_REGEXincomposerInlineTokens.tsno longer uses an unbounded label repeat; it limits the label body to 512 characters viaMAX_FILE_LINK_LABEL_LENGTH, so each match attempt is constant-bounded instead of rescanning the rest of the string from every whitespace (quadratic on inputs like repeated" [[").Normal file links are unchanged in practice because only basenames pass validation and real filenames are far below the cap. Links with labels at 512 chars still tokenize; longer labels are left as plain text.
Tests add cap boundary cases and a performance guard on a 40k-char unterminated bracket run.
Reviewed by Cursor Bugbot for commit 9b71e34. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Bound file-link label length to 512 chars to prevent catastrophic bracket rescanning
MAX_FILE_LINK_LABEL_LENGTH = 512in composerInlineTokens.ts and rebuildsFILE_LINK_TOKEN_REGEXwith a bounded quantifier to cap label body length.Macroscope summarized 9b71e34.