Repository navigation
perf(server): t3_thread_list reads only the listed project's threads - #17843
Merged
Yash-Singh1 merged 1 commit intoOct 10, 2026
Merged
Conversation
listProjectThreads asked for the whole shell snapshot, so every t3_thread_list page ran the shell query over every non-deleted thread in every project, archived ones included, then kept one project's active threads in JS. Shell snapshots now take a projectId, which adds t.project_id = ? and uses the (project_id, updated_at) index, and the list asks for that project's active threads. Fork sources still load by id from any project, so visible item counts are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2 tasks done
Contributor
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a narrowly scoped performance fix that pushes an existing project filter into the shell snapshot query while preserving fork-source data and existing unscoped callers. Focused SQL and in-memory tests cover the changed path, with no schema, default, deployment, or static-analysis changes. You can add or adjust custom eligibility rules. Learn more. |
github-actions Bot
added a commit
to omarcresp/t3code-flake
that referenced
this pull request
Oct 10, 2026
## What's Changed * feat(server): stop Claude subagents without stopping their owner by @Yash-Singh1 in pingdotgg/t3code#17826 * fix(server): stopped native subagents no longer read as Running by @im-kvijay in pingdotgg/t3code#17223 * perf(server): t3_thread_list reads only the listed project's threads by @only21mil in pingdotgg/t3code#17843 * fix(server): show diffs for projects outside the server cwd by @maria-rcks in pingdotgg/t3code#17724 * fix(server): check the specific scope for scripts, preview input, and full-access MCP grants by @juliusmarminge in pingdotgg/t3code#17772 * fix(source-control): stop Forgejo status refresh from scanning every pull request by @loispostula in pingdotgg/t3code#12223 * refactor(contracts): source control provider kind is an open branded slug by @juliusmarminge in pingdotgg/t3code#17739 * feat(source-control): each host package ships a client definition by @juliusmarminge in pingdotgg/t3code#17746 * refactor(client-runtime): add project clone sources come from host definitions by @juliusmarminge in pingdotgg/t3code#17756 * refactor(web): host presentation and behavior come from client definitions by @juliusmarminge in pingdotgg/t3code#17757 * refactor(source-control): reference parsing and project matching are host resolvers by @juliusmarminge in pingdotgg/t3code#17770 * feat(pull-requests): quick actions follow each host's capabilities, not GitHub by @juliusmarminge in pingdotgg/t3code#17774 * feat(projects): new projects can be published to any ready host by @juliusmarminge in pingdotgg/t3code#17860 * fix(server): send Claude MCP servers over the control channel by @juliusmarminge in pingdotgg/t3code#17898 * fix(web): a finished reply replaced by a steer is no longer labeled partial by @juliusmarminge in pingdotgg/t3code#17761 * fix(client-runtime): queued runs that start after a steer show up in the thread by @juliusmarminge in pingdotgg/t3code#17764 * feat(mobile): choose the microphone order for voice input by @juliusmarminge in pingdotgg/t3code#17896 * feat(source-control): host settings live on each host's definition, with a GitCafe token by @juliusmarminge in pingdotgg/t3code#17901 ## New Contributors * @only21mil made their first contribution in pingdotgg/t3code#17843 * @loispostula made their first contribution in pingdotgg/t3code#12223 **Full Changelog**: pingdotgg/t3code@v0.0.46-nightly.20261010.2935...v0.0.46-nightly.20261010.2948 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.46-nightly.20261010.2948
github-actions Bot
added a commit
to davidvanderklay/t3code-flake
that referenced
this pull request
Oct 10, 2026
## What's Changed * feat(server): stop Claude subagents without stopping their owner by @Yash-Singh1 in pingdotgg/t3code#17826 * fix(server): stopped native subagents no longer read as Running by @im-kvijay in pingdotgg/t3code#17223 * perf(server): t3_thread_list reads only the listed project's threads by @only21mil in pingdotgg/t3code#17843 * fix(server): show diffs for projects outside the server cwd by @maria-rcks in pingdotgg/t3code#17724 * fix(server): check the specific scope for scripts, preview input, and full-access MCP grants by @juliusmarminge in pingdotgg/t3code#17772 * fix(source-control): stop Forgejo status refresh from scanning every pull request by @loispostula in pingdotgg/t3code#12223 * refactor(contracts): source control provider kind is an open branded slug by @juliusmarminge in pingdotgg/t3code#17739 * feat(source-control): each host package ships a client definition by @juliusmarminge in pingdotgg/t3code#17746 * refactor(client-runtime): add project clone sources come from host definitions by @juliusmarminge in pingdotgg/t3code#17756 * refactor(web): host presentation and behavior come from client definitions by @juliusmarminge in pingdotgg/t3code#17757 * refactor(source-control): reference parsing and project matching are host resolvers by @juliusmarminge in pingdotgg/t3code#17770 * feat(pull-requests): quick actions follow each host's capabilities, not GitHub by @juliusmarminge in pingdotgg/t3code#17774 * feat(projects): new projects can be published to any ready host by @juliusmarminge in pingdotgg/t3code#17860 * fix(server): send Claude MCP servers over the control channel by @juliusmarminge in pingdotgg/t3code#17898 * fix(web): a finished reply replaced by a steer is no longer labeled partial by @juliusmarminge in pingdotgg/t3code#17761 * fix(client-runtime): queued runs that start after a steer show up in the thread by @juliusmarminge in pingdotgg/t3code#17764 * feat(mobile): choose the microphone order for voice input by @juliusmarminge in pingdotgg/t3code#17896 * feat(source-control): host settings live on each host's definition, with a GitCafe token by @juliusmarminge in pingdotgg/t3code#17901 ## New Contributors * @only21mil made their first contribution in pingdotgg/t3code#17843 * @loispostula made their first contribution in pingdotgg/t3code#12223 **Full Changelog**: pingdotgg/t3code@v0.0.46-nightly.20261010.2935...v0.0.46-nightly.20261010.2948 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.46-nightly.20261010.2948
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
t3_thread_listlists one project's threads, butThreadManagementService.listProjectThreadsbuilds it fromorchestrator.getShellSnapshot()with no options. So every call, and every page of a paged listing, runsselectShellThreadRowsand its correlated subqueries over every non-deleted thread in every project, archived ones included. Only then does it keep the requested project's active threads in JS. The cost follows the size of the whole environment, not the size of the project being listed.On a long-running server with 4,409 threads in 14 projects, server traces showed 3.2 to 6.5 s per
t3_thread_listcall, all on the main thread. #14701 explains why that blocks every client and every other statement. A monitoring agent that listed 6 projects every 4 minutes held the main thread about 20 s per cycle, 8.6% of wall time.Change
ShellSnapshotOptionstakes an optionalprojectId.selectShellThreadRowsaddsAND t.project_id = ?, which SQLite serves fromorchestration_v2_projection_threads_project_updated_idx (project_id, updated_at). The in-memory store applies the same filter.listProjectThreadsasks for{ projectId, location: "active" }. It only ever returnedsnapshot.threads, which is the active set, so archived threads were already dropped. Now SQL drops them before their subqueries run.visibleItemCountdoes not change.project_idcolumn and the payload'sprojectIdare written by the same upsert from the same event, so the SQL filter keeps exactly the rows the JS filter kept. The subagent filter, sort, MCP filters and paging are unchanged and receive the same array.Scope and approval
This fixes one cause of #14701, which a maintainer triaged as a real bug: #14701 (comment). The triage named two callers of this read, the client shell load and the pull request pass.
t3_thread_listis a third caller, and the widest one: it reads every project and the archive to answer for one project.It also fits the small focused fix exception. One option is pushed into SQL, the output is identical, and two tests cover it. No contract, client or behavior change.
It is independent of the
last_errorjoin order in the same statement (#17842), which makes each row cheaper; this PR reads fewer rows. #14703 moves client shell loads and the pull request pass to worker reads and does not touch this path. Other callers that read the whole snapshot with no options (project deletion, checkpoint restore safety, the relay activity snapshot) are left alone.Verification
New tests in
apps/server/src/orchestration-v2/ProjectionStore.test.ts, on a fixture with two projects: project A has an active thread, an archived thread, and a fork of a run in an archived thread of project B.location: "active"returns no archived threads, that{ projectId }alone returns the project's archived threads, and that the fork keeps its inherited items.ProjectionStoreReadErrorand the scoped one succeeds, so the scoped read never touched project B's rows.Focused checks (Linux x64, Node 24.18.1):
vp test runonProjectionStore.test.ts,ThreadManagementService.test.ts,ThreadLaunchService.test.ts,ThreadPullRequestService.test.ts,storageCleanup.test.tsandmcp/OrchestratorMcpToolkit.integration.test.ts: 6 files, 166 tests passed. The MCP toolkit test lists threads withsettled,snoozed,includeSubagents: falseand a foreignprojectId.vp lintandvp fmt --checkon the three changed files: clean.vp run --filter t3 typecheck: exit 0, no errors. The only diagnostics in the changed files are existing suggestions on untouched lines.Synthetic file database built by the repo's migrations and
ProjectionStore.apply: 14 projects, 4,410 threads (108 archived), one run and one item per thread, 2,520 threads sharing 1,434 sessions on one provider instance, two cross-project forks. "Before" is the call main makes (getShellSnapshot(), then the project filter); "after" is the scoped call. Same process, median of 5 runs after a warm-up:The row counts are the threads the initial shell query returns. Fork-source reads are additional and included in the timings: the largest project's scoped call also reads one cross-project fork source, 1,962 thread rows in all. The sorted lists deep-equal each other for all three projects, including the fork's
visibleItemCount.EXPLAIN QUERY PLANon the scoped statement showsSEARCH t USING INDEX orchestration_v2_projection_threads_project_updated_idx (project_id=?).Real database, read-only with Node 24.18.1
node:sqlite(21 GBstatev2.sqlitefrom 0.0.46-nightly.20261007.2787, whose shell SQL matches main): the shipped statement over all 4,409 threads took 3,448 ms. The same statement with onlyAND t.project_id = ?added, for a 331-thread project, took 413 ms.Not checked: a patched server serving
t3_thread_listend to end on that database, macOS and Windows, and the full suite (CI).Model and harness: Claude Opus 5.5 (1M context) in Claude Code, run from T3 Code.
🤖 Generated with Claude Code