From e3836efc5fbcae31929057abec9d330a0e26ec07 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sun, 9 Aug 2026 06:53:29 -0500 Subject: [PATCH 1/3] perf(gateway): stop spawning git for paths that cannot answer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The project tree probes every distinct session cwd, and on a long-lived history most of those directories are deleted worktrees — `git -C` there can only fail, at the price of a fork each. Stat first. The second elision is `common_repo_root`: only repos have a common dir, and the parallel warm never covers that probe because `resolve()` reaches it only for cwds that already resolved. Every non-repo cwd was therefore paying a serial `git` spawn on the discovery pass. --- tests/tui_gateway/test_projects_rpc.py | 32 ++++++++++++++++++++++++++ tui_gateway/git_probe.py | 13 ++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/tests/tui_gateway/test_projects_rpc.py b/tests/tui_gateway/test_projects_rpc.py index e3dcf61a51e61..2c4ad82c65c82 100644 --- a/tests/tui_gateway/test_projects_rpc.py +++ b/tests/tui_gateway/test_projects_rpc.py @@ -146,6 +146,38 @@ def test_warm_roots_probes_in_parallel_and_fills_the_cache(monkeypatch): assert live["calls"] == before +def test_missing_directory_costs_no_subprocess(monkeypatch): + # Deleted worktrees dominate a long session history's cwds, and `git -C` on + # one can only fail — so it must never reach the fork. + from tui_gateway import git_probe + + def boom(*_a, **_kw): + raise AssertionError("spawned git for a directory that does not exist") + + monkeypatch.setattr(git_probe, "bounded_git_probe", boom) + + assert git_probe.run_git("/gone/worktree", "rev-parse", "--show-toplevel") == "" + + +def test_non_repo_cwd_is_not_probed_for_a_common_dir(monkeypatch, tmp_path): + # `warm_roots` only reaches `common_repo_root` for cwds that ARE repos, so a + # common-dir probe here is one the warm can't absorb: it runs serially on + # the discovery pass, once per non-repo cwd. + from tui_gateway import git_probe + + git_probe.invalidate() + asked = [] + + def probe(cwd, *args): + asked.append(args[-1]) + return "" # not a repo, whatever we ask + + monkeypatch.setattr(git_probe, "run_git", probe) + + assert git_probe.common_repo_root(str(tmp_path)) == "" + assert asked == ["--show-toplevel"] + + def test_create_list_roundtrip(tmp_path): created = _call("projects.create", {"name": "Demo", "folders": [str(tmp_path)], "use": True}) assert created["project"]["slug"] == "demo" diff --git a/tui_gateway/git_probe.py b/tui_gateway/git_probe.py index 96053d85cdb90..a0b5d13530641 100644 --- a/tui_gateway/git_probe.py +++ b/tui_gateway/git_probe.py @@ -50,7 +50,11 @@ def run_git(cwd: str, *args: str) -> str: session readiness when a killed git left a suspended descendant holding the pipe handles (issue #68609). """ - if not cwd: + if not cwd or not os.path.isdir(cwd): + # `git -C` on a directory that no longer exists can only fail, and it + # fails at the price of a fork. Deleted worktrees dominate the cwds a + # long-lived session history hands us, so the stat pays for itself many + # times over on every project-tree build. return "" return bounded_git_probe(["git", "-C", cwd, *args], timeout=_GIT_TIMEOUT) @@ -153,6 +157,13 @@ def common_repo_root(cwd: str) -> str: if not cwd: return "" + # No work tree, nothing to fold. Reading the (warmed, negative-cached) + # toplevel first spares every non-repo cwd a second `git` spawn — one the + # parallel warm can never absorb, since `resolve()` only reaches here for + # cwds that ARE repos. + if not repo_root(cwd): + return "" + def _probe() -> str: gitdir = run_git(cwd, "rev-parse", "--path-format=absolute", "--git-common-dir") if gitdir: From 3ef8cdd2678275092abca6a1d3ac2d530dcda8e1 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sun, 9 Aug 2026 06:53:39 -0500 Subject: [PATCH 2/3] perf(gateway): quit reading system prompts the project tree discards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_project_tree_row` keeps about eighteen fields and drops the rest, but the query behind it selected `s.*` plus the resolved system prompt — 37MB of blob per build on my session history, read out of the B-tree and then thrown away. --- tui_gateway/server.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tui_gateway/server.py b/tui_gateway/server.py index b9dcb00bff8cf..ea8359bb0ce40 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -11772,6 +11772,10 @@ def _project_tree_inputs( include_children=False, exclude_sources=_PROJECT_TREE_EXCLUDED_SOURCES, include_archived=False, + # `_project_tree_row` keeps ~18 fields and drops the rest, so selecting + # the system-prompt blob only to discard it costs tens of MB of B-tree + # reads per build on a long-lived database. + compact_rows=True, ) sessions = [_project_tree_row(r) for r in rows] # Parallel-warm the git cache so build_tree's resolver reads it instead of From 368625e001d9e8b333efbfb0749b2d63f9508a32 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sun, 9 Aug 2026 06:53:43 -0500 Subject: [PATCH 3/3] perf(gateway): warm every path the project tree will resolve The warm covered session cwds, but build_tree also resolves each declared project folder and each discovered repo root. Those were the last probes running one directory at a time while the sidebar showed a skeleton. --- tests/tui_gateway/test_projects_rpc.py | 27 ++++++++++++++++++++++++++ tui_gateway/server.py | 7 +++++++ 2 files changed, 34 insertions(+) diff --git a/tests/tui_gateway/test_projects_rpc.py b/tests/tui_gateway/test_projects_rpc.py index 2c4ad82c65c82..55fa04363590b 100644 --- a/tests/tui_gateway/test_projects_rpc.py +++ b/tests/tui_gateway/test_projects_rpc.py @@ -178,6 +178,33 @@ def test_non_repo_cwd_is_not_probed_for_a_common_dir(monkeypatch, tmp_path): assert asked == ["--show-toplevel"] +def test_tree_build_warms_every_path_it_will_resolve(monkeypatch, tmp_path): + # build_tree resolves declared project folders and discovered repo roots as + # well as session cwds. Anything left out of the warm is probed one + # directory at a time while the sidebar shows a skeleton. + from tui_gateway import git_probe + + repo = tmp_path / "repo" + (repo / ".git").mkdir(parents=True) + _call("projects.create", {"name": "Repo", "folders": [str(repo)]}) + + warmed: list[str] = [] + real_warm = git_probe.warm_roots + + def recording_warm(cwds, **kw): + paths = list(cwds) + warmed.extend(paths) + return real_warm(paths, **kw) + + monkeypatch.setattr(git_probe, "warm_roots", recording_warm) + + server._build_project_tree( + server._get_db(), preview_limit=3, hydrate=False, session_limit=5, include_discovered=True + ) + + assert str(repo) in warmed + + def test_create_list_roundtrip(tmp_path): created = _call("projects.create", {"name": "Demo", "folders": [str(tmp_path)], "use": True}) assert created["project"]["slug"] == "demo" diff --git a/tui_gateway/server.py b/tui_gateway/server.py index ea8359bb0ce40..bfb54656b6dcd 100644 --- a/tui_gateway/server.py +++ b/tui_gateway/server.py @@ -11842,6 +11842,13 @@ def _build_project_tree( sessions, projects, discovered, active_id = _project_tree_inputs( db, session_limit, include_discovered=include_discovered ) + # build_tree resolves every declared project folder and every discovered + # repo root too, and those paths are not session cwds — without this they + # are the one part of the build still probing git one directory at a time. + git_probe.warm_roots( + [str(f.get("path") or "") for p in projects for f in (p.get("folders") or [])] + + [str(r.get("root") or "") for r in discovered] + ) tree = project_tree.build_tree( projects, sessions,