Skip to content

fix(server): skip live git in fetch_agent_history placement#1383

Open
yinaoxiong wants to merge 1 commit into
getpaseo:mainfrom
yinaoxiong:pr/skip-git-in-history-placement
Open

fix(server): skip live git in fetch_agent_history placement#1383
yinaoxiong wants to merge 1 commit into
getpaseo:mainfrom
yinaoxiong:pr/skip-git-in-history-placement

Conversation

@yinaoxiong
Copy link
Copy Markdown

Linked issue

Closes #1382

Type of change

  • Bug fix

What does this PR do

The Sessions (history) list resolves every agent's placement through getCheckoutStatus(cwd), which spawns git subprocesses per cwd. On slow / network filesystems each git status takes seconds, and the sum across all distinct cwds blows past the client's 10s fetch_agent_history RPC timeout — so the list hangs and renders empty.

The history path doesn't use that git data at all: the client requests it with showCheckoutInfo={false}, never reads the project/checkout payload, and applies no project filter here. So the git work is pure overhead.

This PR makes fetch_agent_history resolve placement with refreshGit: false (cached snapshot + workspace registry lookup) instead of live git. The fetch_agents (active list) path is unchanged, the wire protocol is unchanged, and no client/CLI changes are needed.

How did you verify it

Tested on a real machine with ~180 sessions across 15 distinct cwds, several on network mounts.

Before (current code), from daemon.log:

  • fetch_agent_history_request latency: 10.7s – 11.9s — over the 10s client timeout, list came back empty.

Micro-benchmark of the placement step (real getCheckoutStatus against the real cwds, vs the registry lookup the fix uses):

  • Before (per-cwd git): ~21,000 ms total for 15 cwds (cold cache — the worst case the slow-FS user hits). Top offenders: one repo 4.6s, four network-mount repos ~3s each.
  • After (registry lookup): ~0.03 ms.

End-to-end after the fix: built the server, swapped it into my daemon, restarted, and opened the Sessions list from the app — it now loads sub-second instead of timing out.

Automated test: added a test that mocks getCheckoutStatus to throw and asserts the history path still returns entries without ever calling it (proves no git is spawned). Full session.workspaces.test.ts passes (53 passed, 4 pre-existing skips).

Checklist

  • One focused change. Unrelated cleanups split out.
  • npm run typecheck passes (0 errors, all workspaces)
  • npm run lint passes (oxlint: 0 warnings, 0 errors)
  • npm run format ran
  • UI changes include screenshots — N/A, server-only change
  • Tests added or updated where it made sense

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 6, 2026

Greptile Summary

This PR fixes a timeout in fetch_agent_history_request by passing { refreshGit: false } when resolving each session's project placement, replacing per-cwd live git status calls with a cached snapshot + registry lookup on the history path.

  • session.ts: Introduces skipGitPlacement (true only for fetch_agent_history_request) and threads { refreshGit: false } into buildProjectPlacementForCwd on that path, leaving the active-list path (fetch_agents_request) unchanged.
  • session.workspaces.test.ts: Adds a regression test that wires getCheckout to throw and asserts the history response still returns populated entries, proving no git subprocess is spawned.

Confidence Score: 5/5

Safe to merge — the change is two production lines scoped precisely to the history path, backed by a test that would fail if git is ever invoked on that path.

The fix is surgical: one boolean variable and one conditional argument. The active-list path is entirely unaffected. The { refreshGit: false } option already existed and was used in forwardAgentUpdate, so no new behavior surface is introduced. The fallback in resolveWorkspaceDirectory preserves existing null-return behavior.

No files require special attention.

Important Files Changed

Filename Overview
packages/server/src/server/session.ts Two-line targeted fix: adds skipGitPlacement scoped strictly to the fetch_agent_history_request branch and passes { refreshGit: false } to buildProjectPlacementForCwd. Active-list path is unaffected.
packages/server/src/server/session.workspaces.test.ts New regression test wires getCheckout to throw, runs fetch_agent_history_request, and asserts the response contains the expected entry via ports-and-adapters injection.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[listFetchAgentsEntries] --> B{request.type?}
    B -->|fetch_agent_history_request| C[skipGitPlacement = true]
    B -->|fetch_agents_request| D[skipGitPlacement = false]
    C --> E[getPlacement with refreshGit: false]
    D --> F[getPlacement with undefined options]
    E --> G[resolveWorkspaceDirectory]
    F --> G
    G --> H{refreshGit === false?}
    H -->|Yes - history path| I[peekSnapshot - cached\nno git subprocess]
    H -->|No - active path| J[getCheckout - live git]
    I --> K[workspace registry lookup]
    J --> K
    K --> L[ProjectPlacementPayload]
Loading

Reviews (2): Last reviewed commit: "fix(server): skip live git in fetch_agen..." | Re-trigger Greptile

page: { limit: 25 },
});

expect(getCheckout).not.toHaveBeenCalled();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Mock call-count assertion on the boundary

expect(getCheckout).not.toHaveBeenCalled() asserts implementation internals rather than observable behavior. The test already covers the behavioral goal because getCheckout is wired to throw — if the history path ever calls it, the test fails with an unhandled rejection before reaching the expect(emitted) check. The explicit not-called assertion is therefore redundant and falls into the pattern the project rules flag (asserting spy/mock call counts instead of recording observable state). Consider relying solely on the throw-on-call guard and the emitted-entries assertion.

Rule Used: # Code Review Pattern Reference: Slop, Tests, Feat... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@yinaoxiong yinaoxiong force-pushed the pr/skip-git-in-history-placement branch from d31f79a to 4cf9422 Compare June 6, 2026 14:50
The history list never renders checkout info (the client requests it
with showCheckoutInfo=false and does not read the project payload), yet
the daemon resolved each agent's placement via getCheckoutStatus, which
spawns a sequence of git subprocesses per cwd.

On slow filesystems (network mounts) a per-cwd `git status` takes
seconds; summed across many sessions it exceeded the client's 10s RPC
timeout, so the Sessions menu hung and showed an empty list.

Resolve history placement from cached snapshots + the workspace registry
(refreshGit: false) instead — no git spawns on the history path. The
fetch_agents (active list) path is unchanged, the wire protocol is
unchanged, and no client/CLI changes are needed.

Verified on a real ~180-session machine: history placement dropped from
~11s (timeout, blank list) to sub-millisecond.
@yinaoxiong yinaoxiong force-pushed the pr/skip-git-in-history-placement branch from 4cf9422 to 9789b9b Compare June 6, 2026 14:50
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.

Sessions list hangs and shows empty when sessions span slow/network filesystems

1 participant