Skip to content

perf(hub): cross-project pages stop re-reading every activity log (2.146.0) - #199

Merged
drknowhow merged 7 commits into
mainfrom
perf/hub-speed
Sep 22, 2026
Merged

drknowhow merged 7 commits into
mainfrom
perf/hub-speed

Conversation

@drknowhow

Copy link
Copy Markdown
Owner

The Hub's Credentials page was slow to open and slow after every save, worst on a work laptop. Almost none of the time went to credentials. Every cross-project route called list_projects() just for names and paths, and that call re-reads every project's activity log to work out live sessions. ActivityLog.find_last JSON-parsed each line on the way back, so a type a project had never logged cost a parse of the whole file, about six times per project.

Measured with 71 registered projects, same box, origin/main vs this branch:

Route Before After
/api/hub/credentials/overview 925 ms 108 ms
/api/hub/credentials/audit 1004 ms 68 ms
/api/hub/sessions 1745 ms 287 ms
/api/hub/tokens/overview 928 ms 180 ms
locks / enforcement / access / approval-policy overviews 760–825 ms 14–26 ms
/api/projects (5 s poll) 713 ms 225 ms
/api/ci/inspect (2–10 s poll) 327 ms 61 ms
Babel compile per page load ~1.9 s ~0.35 s

One commit per change:

  • activity log: find_last and typed get_recent skip chunks and lines without the type's JSON name before parsing. get_recent reads its tail from the end.
  • creds: value_presence() checks a whole vault with one registry read. Before, 43 entries cost 362 config.json reads and 912 path resolutions. is_resolvable delegates to it.
  • hub: eleven call sites move to list_registered(), the stored rows. The backup banner reuses the listing's probe.
  • sessions: one transcript-root listing per request, and worktree transcripts go through the per-file cache. The listing is per request because Windows does not refresh a folder's mtime in its parent's listing when a file is created inside it. A test caught that when I first cached it longer.
  • ci: inspect reuses the act/docker probe for 30 s. Runs and doctor still probe fresh.
  • ui: both shells set data-presets="react", since babel's default ran preset-env on the 600 KB bundle. The global vault manager reuses the page's overview instead of fetching it a second time. usePoll no longer overlaps a slow call, and the project list polls every 30 s off the Projects view.

Not measured: the work laptop itself. If its projects sit on network drives, per-project I/O may still dominate there.

Verification: ruff clean. New tests cover the prefilter, the tail window, per-kind presence answers, registry-read counts, transcript-root staleness and the probe cache. Both JS bundles ran top-level under the React-only preset in a Node VM, and the branch's Hub rendered in Chrome with no console errors (one overview request on the Credentials page, 21/21 global entries).

https://claude.ai/code/session_01DEKoSbeEFepPGwPHQDHxAJ

find_last JSON-parsed every line walking back, so a type a project never
logged cost a parse of the whole file (up to the 5 MB cap). The Hub's
list_projects calls it about six times per project on every request.
Chunks and lines without the type's JSON-encoded name are now skipped
unparsed, and get_recent reads its tail from the end of the file instead
of the whole file forward. list_projects: 628 ms -> 173 ms on 71 projects.

Claude-Session: https://claude.ai/code/session_01DEKoSbeEFepPGwPHQDHxAJ
The missing-value flag added in 2.144 called is_resolvable per entry,
which re-read config.json and re-resolved the home and project paths each
time: 43 entries cost 362 registry reads, 912 path resolutions and 120
keyring reads. value_presence reads the registry, realm, sidecar and
master key once per vault. is_resolvable delegates to it; the project UI,
c3_credentials list and c3 creds list use presence_for over their merged
view, and backup status probes each vault once and accepts a caller's map.

Claude-Session: https://claude.ai/code/session_01DEKoSbeEFepPGwPHQDHxAJ
Eleven Hub call sites used list_projects() only for names and paths,
paying for its per-project activity-log scan and session-port probes on
every request, including the 5 s polls on the locks, access and
enforcement pages. They now call list_registered(), the stored rows. The
credentials overview and project listing probe each vault once via
value_presence and hand the result to the backup banner instead of
probing every entry twice. Test stubs rename list_projects to match.

Claude-Session: https://claude.ai/code/session_01DEKoSbeEFepPGwPHQDHxAJ
Every orphan C3 record checked every folder under ~/.claude/projects for
its transcript (9.6k stat calls per Hub sessions request), and a project
without an exact slug match re-listed the root. One listing now serves the
whole request, and a transcript found in another folder goes through the
per-file cache instead of being re-read each time. The listing is not kept
across requests: Windows does not refresh a folder's mtime in its parent's
listing when a file is created inside, so a kept one misses new sessions.

Claude-Session: https://claude.ai/code/session_01DEKoSbeEFepPGwPHQDHxAJ
The Hub CI tab re-inspects every 10 s (2 s during a run), and each inspect
spawned act --version, docker version and docker image inspect: 250 ms
here, and a stalled Docker Desktop can hold it for the 30 s probe timeout.
inspect_project now passes max_age so a recent probe is reused. Starting a
run and c3_ci doctor call availability() without it and still probe fresh.

Claude-Session: https://claude.ai/code/session_01DEKoSbeEFepPGwPHQDHxAJ
babel-standalone's default presets include preset-env, which compiled the
600 KB Hub bundle to ES5 on every page load (1.9 s on a desktop vs 0.35 s
for JSX alone); both shells now ask for the React preset only. The global
vault manager takes its rows from the Credentials page's overview instead
of loading it again. usePoll skips a tick while the last call is running,
and the project list polls every 30 s off the Projects view. Both bundles
ran top-level in a VM, and the Hub rendered in Chrome with no errors.

Claude-Session: https://claude.ai/code/session_01DEKoSbeEFepPGwPHQDHxAJ
@drknowhow
drknowhow merged commit c199237 into main Sep 22, 2026
12 checks passed
@drknowhow
drknowhow deleted the perf/hub-speed branch September 22, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant