Repository navigation
fix(box): listing projects runs git only when a repository changed - #195
Conversation
|
@Stener1 is attempting to deploy a commit to the seanbrydonio's projects Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
1 issue found across 9 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="docs/changelog.mdx">
<violation number="1" location="docs/changelog.mdx:25">
P3: `cachedGit` bypasses the cache for locations without their own `.git`, so those locations still run Git on every listing. Qualify this claim for repository-backed projects.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
| branch, and asks git again only when the repository changes (or after a | ||
| minute). |
There was a problem hiding this comment.
P3: cachedGit bypasses the cache for locations without their own .git, so those locations still run Git on every listing. Qualify this claim for repository-backed projects.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/changelog.mdx, line 25:
<comment>`cachedGit` bypasses the cache for locations without their own `.git`, so those locations still run Git on every listing. Qualify this claim for repository-backed projects.</comment>
<file context>
@@ -18,6 +18,12 @@ again.
+ git commands per project every time the app, a plugin, the MCP or its
+ own worktree watcher listed them, dozens a second on a box with a few
+ dozen projects. It now keeps each project's worktrees, remote and default
+ branch, and asks git again only when the repository changes (or after a
+ minute).
- **Switching projects no longer opens a new task.** Clicking a project on
</file context>
| branch, and asks git again only when the repository changes (or after a | |
| minute). | |
| branch, and usually reuses results for repositories with their own `.git` until a | |
| relevant Git file changes (or after a minute); other locations are checked each time. |
There was a problem hiding this comment.
Not changing this one. The changelog is plain words for users (AGENTS.md), and the exception is a project folder with no .git of its own, such as a folder that isn't a repository or one inside another repository. Those run git exactly as before, so nothing gets worse for them. The precise rule is in cachedGit's comment and the PR description. The bullet has moved to a new ## Unreleased section, because v0.3.31 shipped while this PR was open.
Every Locations.List and Get ran git worktree list, remote get-url and symbolic-ref for each location, and the app, plugins, the MCP and the worktree watcher ask many times a second. Each answer is now kept with a stamp of the .git files git read for it, and asked again only when one changes, or after a minute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… checks the sidebar follows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… it switches branch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eloads Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st, and the live probe cleans up only what it made From review: - worktreeStamp also stamps the checkout's config.worktree, which can set core.bare for it. - worktree-git-live removes the probe worktree and its branches only when this run created them, and with --force twice, so a failed step that left it locked still gets cleaned up. - countGit quotes its paths with shellQuote. - Comments and the feature file say a minute also expires an answer, that an open app needs a reload to drop a removed row, and which XDG config is watched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018aGVrvzCLsVZaL92TTA95t
b6d1844 to
c7a6a4b
Compare
Summary
berthd servewas the busiest process on an idle box: about 0.5 cores, and about 48 git execs a second (a box with 24 projects and about 100 worktrees). Nearly all of them weregit worktree list,git remote get-url originandgit symbolic-ref, one set per project, every time anything described the projects.Where they came from:
describe()runs those 3–4 commands for a location, and everyLocations.ListandLocations.Getcalls it, for all locations (List) or one (Get). About 70 call sites use those two. The steady callers:Listevery 10 s (watch.go);GET /v1/locations, from the app (on everyworktree.*event, plus a 60 s backstop), plugins and the CLI;Gets: presets, skills, services, sessions and the like;placeAgentson/v1/stats(oneworktree listper location, on every agent event).The fix keeps each answer with a stamp of the
.gitfiles git reads to produce it, and runs git again only when the stamp changes (or after a minute):url.*.insteadOf, or an included config file..gitof its own is asked every time, as before.parseWorktreesstill runs on every call, so a worktree whose folder is gone drops at once.internal/box/gitcache.gois new;describe,worktreePaths,remoteURLanddefaultBranchgo through it.Evidence
Real box, same clients open, 40 s
bpftraceof berthd's execve calls (tracepoint:syscalls:sys_enter_execve /curtask->real_parent->tgid == <berthd>/), with berthd's CPU over the same window read from/proc/<pid>/stat:That's about 99% fewer git execs and about 70% less berthd CPU. An earlier build on the same box measured 1,800–1,913 per 40 s at 0.42–0.55 cores. "Before" is the build the box ran; "after" is the same tree plus this change.
Correctness on that box: every location in
/v1/locationsfrom the patched daemon matched git run directly (24 locations, 102 worktrees: worktrees, branches, HEADs, remotes, default branches; 0 mismatches). Each of the following showed up on the very next/v1/locationsread:berthd worktree new, a commit in the new worktree, a branch switch,git worktree lock,berthd worktree rm, andgit worktree add/removedone outside Shipyard. The MCP'sberth_sessionsstill places every session in its worktree.Tests (
internal/box/gitcache_test.go). Agitwrapper first on PATH counts every real exec:With the cache turned off, the first test fails with the four git calls listed on every repeat.
App on a real dev box (
verify-berthd, new scenarioworktree-git-live, a dev box of this checkout with--agent). A worktree added with git shows in the sidebar, and once the box drops it aftergit worktree remove, it's gone after a reload:An app that's already open keeps the removed worktree's row until it reloads, even though the box sends
worktree.removed. That happens on main too (the same scenario fails the same way against a dev box built from main), so it's left for its own fix. The feature file says so.Checks:
go vet ./...passes.go test -race ./...passes exceptTestDetach(leadline/api, fails on main on this machine) andTestPaneDeadOfASignalIsItsShellStatus, asleep 0.2; kill -INTtiming test that also flakes on main under load (0/10 on main, 1/10 here at load ~50). The app build and the docs build pass.cargo checkwas not run: nothing inapp/src-taurichanged.Running in production
Since 2026-10-28 this change, together with #194, has run on one of my own boxes through my personal update channel (upstream's latest release plus my open PRs). The box is busy: 35–37 coding-agent sessions, 23 projects and about 91 worktrees, with load between 13 and 30 on 8 cores. All figures are from the same 40 s
bpftracetrace and/procCPU read as above.Before the fix, berthd was the box's largest single CPU user, and its git exec rate grew with the number of worktrees and with how often clients listed locations. On the patched build:
/v1/locationsmatchedgit worktree listrun directly for every project.Merge Danger
Door: two-way
Box-side only, with no stored state: a revert puts the git calls back as they were.
Blast Radius: worktree-list
The risk is a stale answer: a branch, HEAD, lock or remote that has changed but still shows the old value until the minute runs out. The stamp covers what git itself reads for each answer, including linked worktrees, packed refs and reftable. What it can't see (global or included git config) is bounded by the minute. Worktree creation and removal read the list through the same cache, but their own changes always change the stamp.
🤖 Generated with Claude Code