Skip to content

perf(web): pull request chains parse each link's URL once - #21147

Merged
juliusmarminge merged 1 commit into
mainfrom
perf/thread-pull-request-key-cache
Oct 18, 2026
Merged

juliusmarminge merged 1 commit into
mainfrom
perf/thread-pull-request-key-cache

Conversation

@juliusmarminge

Copy link
Copy Markdown
Member

The chat view and every sidebar row resolve a thread's current pull request on each render. resolveThreadPullRequestChains normalized each link's key several times per call (stack grouping, branchKey for head and base, threadPullRequestKeyOf for placement), and every normalization ran parseChangeRequestUrl, which builds a new URL() and runs a few regexes. A thread with 157 linked PRs (a real stack-heavy thread from the seed data) cost about 1.3–1.6 ms per resolve, several times per ChatView render. Over a streamed turn in that thread this added up to 150–370 ms of URL parsing inside long tasks (CDP traces of three full turns on main).

Normalized keys are now cached per link object in a WeakMap. Links are immutable decoded values, and the shell reducer keeps unchanged links by reference, so the cache hits across renders. A changed link is a new object and gets a fresh key.

Measured
Microbenchmark on the real 157-link array (node, 100 iterations after warmup):

Call main this PR
resolveThreadCurrentPullRequestLink 1.57 ms 0.68 ms
resolveThreadPullRequestChains 1.30 ms 0.21 ms

In the app: production build (vp build --minify false), headless Chromium, same seeded snapshot, CDP trace per window, median of 3 runs with the build order alternated:

Window Metric main this PR
Switch 3 threads x3 changeRequestUrl.ts ms inside long tasks (≥50 ms) 40 0
Switch 3 threads x3 threadPullRequests.ts ms inside long tasks 25 16
Switch 3 threads x3 ScriptDuration ms 1,561 1,507
Open large thread (warm, n=6) URL parse + key ms inside long tasks 22 5
Open large thread (warm, n=6) ScriptDuration ms 302 258

Long-task counts and total long-task ms for open and switch are within noise (1,363 vs 1,352 ms on switch). This takes a few percent off each frame and doesn't remove any. Streaming, where ChatView re-renders on every delta, is where it adds up (see the numbers above).

Tests: packages/shared/src/threadPullRequests.test.ts passes unchanged. Lint and typecheck pass for packages/shared.

Claude Opus 5.5 via Claude Code.

🤖 Generated with Claude Code

The chat view and every sidebar row resolve a thread's current pull request on
each render, and chain resolution normalized every link's key several times,
parsing its URL each time. A thread with 157 linked PRs spent ~1.5 ms per
resolve and several hundred ms over a streamed turn. Keys are now cached per
link object, which stays stable until the link changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. label Oct 18, 2026
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 18, 2026
@github-actions github-actions Bot added the size:S 10-29 changed lines (additions + deletions). label Oct 18, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at c5e2bb4

Macroscope's review found this PR approvable — This is a contained performance optimization that caches normalized pull-request keys without changing the existing chain-resolution behavior for immutable links. It introduces no new capability, schema change, default change, or sensitive-path modification.

You can add or adjust custom eligibility rules. Learn more.

@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 5.0 KiB 4.9 KiB −30 B (−0.6%) 6.8 KiB ✅
Codex Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Codex Live turn WebSocket wire 1.2 KiB 1.2 KiB −30 B (−2.5%) 2.0 KiB ✅
Codex Live turn WebSocket decoded 20.9 KiB 20.8 KiB −41 B (−0.2%) 29.3 KiB ✅
Codex Live turn messages 2 1 −1 (−50.0%) 8 ✅
Claude Total thread wire 5.0 KiB 5.0 KiB 0 B (0.0%) 6.8 KiB ✅
Claude Thread snapshot wire 3.8 KiB 3.8 KiB 0 B (0.0%) 4.9 KiB ✅
Claude Live turn WebSocket wire 1.2 KiB 1.2 KiB 0 B (0.0%) 2.0 KiB ✅
Claude Live turn WebSocket decoded 21.2 KiB 21.2 KiB 0 B (0.0%) 29.3 KiB ✅
Claude Live turn messages 2 2 0 (0.0%) 8 ✅

Baseline: 9ba6143 · PR result: c5e2bb4 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@coderabbitai

coderabbitai Bot commented Oct 18, 2026

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: ed9638a7-b0b8-4640-b2be-2d1607204a75
📥 Commits

Reviewing files that changed from the base of the PR and between eb1d80c and c5e2bb4.

📒 Files selected for processing: 1
  • packages/shared/src/threadPullRequests.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

📝 Walkthrough

threadPullRequestKeyOf now caches normalized pull-request keys by object identity. Native-stack and derived-branch grouping use the cached lookup.

Changes: Pull-request key normalization

Layer / File(s) Summary
Cache normalized keys and use them in grouping
packages/shared/src/threadPullRequests.ts
threadPullRequestKeyOf caches normalized keys in a WeakMap. Native-stack and derived-branch grouping use this helper.

Priority: ⬇️ Low

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Refactor

Suggested reviewers: maria-rcks

Merge Risk: ⚪ Minimal · up to c5e2b

Changed link data receives a new object, so the cache does not leave supported callers with stale keys. No actionable merge-blocking risk remains.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main performance change: parsing each pull-request link URL once through caching.
Description check Passed The description clearly explains the performance problem, implementation, measured results, verification steps, and agent usage. It does not explicitly provide issue or maintainer-approval details, or…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description clearly explains the performance problem, implementation, measured results, verification steps, and agent usage. It does not explicitly provide issue or maintainer-approval details, or explain why this focused change qualifies without them.

✨ 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.

@juliusmarminge
juliusmarminge merged commit 9c16146 into main Oct 18, 2026
32 checks passed
@juliusmarminge
juliusmarminge deleted the perf/thread-pull-request-key-cache branch October 18, 2026 09:13
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Oct 18, 2026
## What's Changed
* perf(web): switching threads no longer re-renders the chat view for an unchanged preview list by @juliusmarminge in pingdotgg/t3code#21130
* fix(server): opening a large database no longer blocks startup for minutes by @t3dotgg in pingdotgg/t3code#21122
* perf(web): switching threads no longer re-renders the outgoing thread's rows by @juliusmarminge in pingdotgg/t3code#21123
* perf(web): reasoning rows skip the tool-failure text scan by @juliusmarminge in pingdotgg/t3code#21146
* perf(web): pull request chains parse each link's URL once by @juliusmarminge in pingdotgg/t3code#21147


**Full Changelog**: pingdotgg/t3code@v0.1.1-nightly.20261018.3302...v0.1.1-nightly.20261018.3315

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.1-nightly.20261018.3315
github-actions Bot added a commit to davidvanderklay/t3code-flake that referenced this pull request Oct 18, 2026
## What's Changed
* perf(web): switching threads no longer re-renders the chat view for an unchanged preview list by @juliusmarminge in pingdotgg/t3code#21130
* fix(server): opening a large database no longer blocks startup for minutes by @t3dotgg in pingdotgg/t3code#21122
* perf(web): switching threads no longer re-renders the outgoing thread's rows by @juliusmarminge in pingdotgg/t3code#21123
* perf(web): reasoning rows skip the tool-failure text scan by @juliusmarminge in pingdotgg/t3code#21146
* perf(web): pull request chains parse each link's URL once by @juliusmarminge in pingdotgg/t3code#21147


**Full Changelog**: pingdotgg/t3code@v0.1.1-nightly.20261018.3302...v0.1.1-nightly.20261018.3315

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.1-nightly.20261018.3315
vedprakash2302 added a commit to vedprakash2302/Cody that referenced this pull request Oct 18, 2026
Brings in upstream through pingdotgg#21147 (9c16146).

- Branch picker (pingdotgg#20822, "use memo" removed): reapplied Cody's worktree base selection (refs keyed with refs/heads and refs/remotes, default and picked bases stored as the draft's base ref) on the inlined picker items.
- Scheduled task editor (pingdotgg#20807 family, editor split into a form component): reapplied Cody's Post runs to field, thread reset on project or environment change, and hidden workspace fields for thread-bound tasks.
- Superseded: none. Adapted: none.
Pipyakas pushed a commit to Pipyakas/t3code that referenced this pull request Oct 18, 2026
…21147)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9c16146)
aorwall added a commit to aorwall/t3code that referenced this pull request Oct 19, 2026
Merges `pingdotgg/t3code` up to `ab6be13d43`, from base `5c5dcbc60b`:
589 commits. Branched from `main` (`7e132898fa`), with no other merge PR
open.

Most of the range is the rest of upstream's move to the React Compiler,
plus its apps/web DOM tests moving to vitest browser mode
(`*.browser.test.tsx`, run in real Chromium). Upstream removed jsdom and
`react-test-renderer`.

## How the conflicts were resolved

All 27 conflicted files were resolved by the verdict `preflight.mjs`
printed for each. The tracker entry in `docs/fork/upstream-merge-log.md`
has the details.

**Tests**
- Seven upstream tests became browser tests. The fork's mocks were
re-ported into the new files, and the inventory was re-pointed at them.
- The fork's framed-preview tests moved to the fork-only
`PreviewView.frame.browser.test.tsx`. A browser-mode mock binds its
exports once, at load, so one file can't run both with and without the
desktop bridge.
- Three fork-only tests (ThreadForkDialog, MoatlessArtifactChip,
MobileSidebarDrawer) depended on `react-test-renderer` and on test
helpers upstream deleted. They are rewritten as browser tests.

**ChatView**
- Upstream moved the rewind dialog into `chat/ChatOverlayHosts.tsx`, and
the `checkpointFileRestore` gate moved with it.
- The ThreadDetailsPanel script props were re-applied in upstream's
inline JSX.

**Lockfile and package.json**
- `pnpm-lock.yaml` was re-derived with `install.mjs`.
- `apps/web/package.json` keeps the `moatless-api` workspace link and
the `tsc --noEmit` typecheck script.

**Converged**
- Upstream now renders Mermaid itself (`chat/MermaidDiagram.tsx`). The
fork's `fork/mermaidDiagram*` files, their inventory entries and the
Mermaid Diagram Delta section are deleted.

**Fixed along the way**
- The provider list's own "Add provider" button had no gate, on `main`
too. It is now behind `providerConfiguration`, like the title button.
- Upstream's React Compiler lint rules flagged 64 errors in fork-owned
files, and the compiler-coverage test found 2 bailouts (`finally` inside
a hook). All are fixed.
  - Most were `useMemo`/`useCallback` removals.
- The only behaviour change: sandbox "elapsed" times are now measured
from the status query's `dataUpdatedAt` instead of a `Date.now()` read
during render.

## Verification

`verify.mjs --sequential`: duplicate-adds, tripwires, resolution-check,
unsupported-methods (ADD 0 / DROP 0 / KEEP 2), lockfile, fmt, lint,
typecheck and build are all green.

The `test` step is red only on the five files that failed alone on
2026-10-17 too, on the same machine-only causes:
- `shared` `AgentScope`
- `ssh` `tunnel`
- `provider-opencode` `OpenCodeServerLedger`
- `t3` `AcpAdapterV2`
- `t3` `OrchestratorReplayFixtures`

`@t3tools/web` failed in the full run, and re-ran green after the fixes:
565 files, 8497 tests. The failure that wasn't a compiler bailout,
`WorkflowOpeningBlock.browser.test.tsx`, is upstream's file unchanged,
and it passed alone.

## Feature classification

### Usable as-is
- **Reasoning-effort stepping**: new `composer.increaseEffort` and
`composer.decreaseEffort` commands. They have no default binding, and
keybinding editing stays behind `serverAdministration`.
- **Transcript and markdown fixes**:
  - Windows paths and terminal links (pingdotgg#21069, pingdotgg#21199, pingdotgg#19627)
  - Math in Mermaid diagrams (pingdotgg#16198)
  - Code-block highlight jump (pingdotgg#20923)
  - Copied task lists (pingdotgg#14778)
  - Table expand button (pingdotgg#19782)
- **Message-list fixes**:
  - Huge-thread scroll loop (pingdotgg#21269)
  - Answered questions stay visible (pingdotgg#21086)
  - "Failed" status for failed runs (pingdotgg#20996)
  - Resumed-subagent rows (pingdotgg#18099)
  - Background-agent count (pingdotgg#20785)
  - A single completion sound (pingdotgg#17540)
- **Composer fixes**:
  - Spacing and centring (pingdotgg#21345, pingdotgg#12952, pingdotgg#17820, pingdotgg#21241)
  - Quotes (pingdotgg#20350, pingdotgg#19450)
  - Send shortcut on follow-ups (pingdotgg#20929)
  - `/tmp/x` no longer opens the slash menu (pingdotgg#19671)
  - Failed attachments don't block send (pingdotgg#20737)
  - HEIC attachments (pingdotgg#10429)
  - Long approval commands wrap (pingdotgg#17802)
  - Android Backspace (pingdotgg#18063)
- **Thread drag and drop into chat** (pingdotgg#19697, pingdotgg#20024).
- **Diff, file and pull request panel fixes**: pingdotgg#20297, pingdotgg#20333, pingdotgg#19089,
pingdotgg#18288, pingdotgg#17132, pingdotgg#21048, pingdotgg#17554, pingdotgg#19945, pingdotgg#15341, pingdotgg#21245, pingdotgg#21147.
- **Sidebar and breadcrumb fixes**: middle truncation (pingdotgg#18858, pingdotgg#17208),
"No project" scratch breadcrumb (pingdotgg#19721), and snoozed threads waking in
an open chat (pingdotgg#21019).
- **Settings and miscellaneous UI fixes**:
  - Model picker lists every custom model (pingdotgg#14961)
  - Font picker (pingdotgg#9814)
  - Settings search highlight (pingdotgg#20764)
  - Usage popover (pingdotgg#19808, pingdotgg#16936)
  - Web app manifest name (pingdotgg#19175)
  - Dark-mode switches (pingdotgg#14984)
- **Phone browser**: tapping away from the model picker no longer
presses what is under it (3f2dbd7).
- **client-runtime**: queries stay pending during a connection hand-off
(pingdotgg#18155), and a dropped session is not shown as a failed connection
(pingdotgg#18469).
- **React Compiler and render-performance work**: about 190 commits.
- **Workflow thread view**: `WorkflowOpeningBlock` shows a workflow's
phases and agents (pingdotgg#20857). It renders only when `workflowScript` is
set, which Moatless never sends; see the backend list.

### Unsupported in Moatless / needs implementation
- **`t3 doctor`, server telemetry and the Node CLI** (pingdotgg#20948, pingdotgg#15028,
pingdotgg#15921, pingdotgg#15913): upstream's server only, with no client surface.
- **SSH device hosts and T3 Connect/relay fixes** (pingdotgg#20377, pingdotgg#17440,
pingdotgg#18832, pingdotgg#19444, pingdotgg#18621, pingdotgg#17743, pingdotgg#18838): behind `FEATURES.connections`
or `FEATURES.deviceHub`.
- **Tailscale HTTPS row** (pingdotgg#21057): behind `FEATURES.connections`.
- **Remote server self-update and how-to-update messages** (pingdotgg#18980,
pingdotgg#18110): behind `FEATURES.serverUpdateBanner`.
- **Masking of secret settings fields** (pingdotgg#20446): the form is behind
`FEATURES.providerConfiguration`, because Moatless does not dispatch
`server.updateSettings`.
- **Desktop updater, desktop preview and local-machine fixes** (pingdotgg#21210,
pingdotgg#20994, pingdotgg#16026, pingdotgg#18549, pingdotgg#19846, pingdotgg#20206, pingdotgg#20277): desktop bridge only, or
behind `FEATURES.openInEditor`.

### Backend behavior to consider reproducing in Moatless
Added to [gaps: runtime
fixes](docs/fork/gaps.md#runtime-fixes-upstream-made-to-its-own-server).
- **`workflowScript: true` on a conversation message whose body is a
provider-native workflow script.** Without it the timeline shows the raw
script.
- **`endedWatch` on an ended pull request watch**, so watching again
doesn't repeat news the thread already got (pingdotgg#21155).
- **`pullRequests` on orchestrator MCP thread list and read results**
(pingdotgg#17454). Related: `t3_thread_send` now says why a message was refused
(pingdotgg#21027).
- **`vcs.createWorktree` always sends `path: null`** and expects the
server to place the worktree. Moatless doesn't dispatch this method; it
is under `projectManagement`.
- **Run lifecycle**:
  - Refuse `thread.create` for an id that already exists (pingdotgg#19704).
  - Cancel a preparing run on archive (pingdotgg#20073).
  - A parent Stop clears orphaned delegated follow-ups (pingdotgg#20310).
  - Queued messages land in order after a resume (pingdotgg#17773).
- **Workflow members stream thinking and tool calls into their own
threads** (pingdotgg#20786).
- **Pull request link handling**: canonicalise legacy links (pingdotgg#19129),
don't inherit watches into forks (pingdotgg#16623), and settle threads after
their pull request merges (pingdotgg#18232, pingdotgg#19937).

No new RPC methods. `vcs.createWorktree` is the only change to `rpc.ts`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---
Moatless task:
https://moatless.soaplabstest.com/tasks/5bcfa708-8eac-41b0-a580-c885a620afb5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:S 10-29 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant