Skip to content

agentHost: stop recomputing and running git for folders only archived chats use - #343275

Merged
Sandeep Somavarapu (sandy081) merged 5 commits into
mainfrom
agents/fix-agent-host-git-spawn-enoent-issue
Oct 21, 2026
Merged

Sandeep Somavarapu (sandy081) merged 5 commits into
mainfrom
agents/fix-agent-host-git-spawn-enoent-issue

Conversation

@sandy081

@sandy081 Sandeep Somavarapu (sandy081) commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Status: draft, partially verified. See Partially verified, remaining.

No tracking issue: this comes from an observed hang of the Insiders agent host (details below).

Contract

The agent host must not repeatedly recompute, re-broadcast, or run git for folders that only archived chats, or subagents/side chats of archived chats, use (deleted worktrees included), and it must stay responsive while such a session is loaded.

  • Non-goals: the pull-request-operations flip-flop; pruning persisted session workingDirectories lists from before agentHost: detach an archived chat's folders from its session #341782.
  • Invariants kept: archive/delete/passive detach behavior is unchanged; the Branch and Session summaries still include the default chat and every unarchived real chat's folders; an explicit refreshBranchChangeset still retries; a worktree deleted and recreated at the same path is picked up (no permanent negative cache for missing directories).

Observed problem

On a real Insiders agent host with a session that had ~150 archived chats and ~77 subagent chats (each pinned to 3-24 folders, some of them deleted worktrees), the host sent nothing to the window for ~110 s and a ping took 58 s. During ~109 s, for that one session: ~10,600 changeset/statusChanged broadcasts (~97/s), ~2,000 git rev-parse --show-toplevel failed: spawn git ENOENT, 147 git status 5 s timeouts, and a flip-flopping pull-request operation.

Root cause

  • AgentHostChangesetService._getBranchSummaryOwners, getSessionSummaryWorkingDirectories (also used by Session Changes discard) and AgentHostChangesetCoordinator._reconcileBranchSummaryResources walked all chats (archived ones, and subagents with pinned folders) while the "only chats in use count" rule from agentHost: detach an archived chat's folders from its session #341782/agentHost: only real chats' folders count for archive/delete detaching and Git state #341885 existed only for archive/delete detaching and git-state refresh.
  • Every git-state refresh reconciled the branch summary, which called refreshBranchChangeset() for every owner. That call clears _unavailableBranchOwners/_failedBranchOwners and recomputes the owner; every owner that ends "not a git repository" calls _updateBranchSummary, which refreshes the next owner. The cost is roughly quadratic in the number of owners and is re-armed by every git-state refresh.
  • AgentHostGitService.getRepositoryRoot never quieted a missing working directory: spawn git ENOENT has empty stderr, so it was not matched by the "not a git repository" cache and was re-spawned and re-logged on every probe.

Provenance: LIKELY regression

What changed (one commit each)

  1. 8154354f1f2 skip git spawn for missing working directories: getRepositoryRoot stats the directory first and returns undefined at trace level without spawning git. Nothing is cached for a missing path, so a recreated worktree is found by the next lookup. A spawn git ENOENT for a directory that vanished in between is also trace-level; "not a git repository" caching is unchanged.
  2. 3c168d0198e limit session changes to the folders of chats in use:
  3. b10877f5dd4 reconcile the branch summary when a chat is archived or unarchived: nothing re-evaluated the in-use set when a chat's archived state changed (found while replaying "unarchive").
  4. 7212ebd81ad drop explanatory inline comments added by the previous commits.

Tests (Node 24.22.0)

New or extended: agentHostChatsInUse.test.ts (new), agentConfigurationService.test.ts, agentHostChangesetService.test.ts, agentHostChangesetCoordinator.test.ts, agentHostGitService.integrationTest.ts.

Command Result
./scripts/test.sh --run src/vs/platform/agentHost/test/node/{agentHostChatsInUse,agentConfigurationService,agentHostChangesetService,agentHostChangesetCoordinator,agentHostChangesetStrategy,agentHostDiscardChangesOperationHandler,agentService,agentHostGitService,agentHostSessionRepositories}.test.ts 1791 passing, 0 failing
./scripts/test-integration.sh --run src/vs/platform/agentHost/test/node/agentHostGitService.integrationTest.ts --grep "getRepositoryRoot" 26 passing
npm run typecheck-client, npm run valid-layers-check, npm run eslint -- <changed files>, husky hygiene on every commit clean

Red without the fix (source changes stashed, new tests kept):

  • Commit 1: 7 of the 26 getRepositoryRoot integration tests fail on the base.
  • Commit 2: the two new agentHostChangesetService tests, the new agentConfigurationService summary test and the new coordinator test fail on the base. The fifth new test ("the default chat is kept") passes either way by design.
  • Commit 3: the new coordinator archive/unarchive test fails without the hook (73 passing, 1 failing).

Live before/after

Real agent host (not mocks) from the base revision e0a4363d21a and from this branch, through the repo's agent-host E2E harness: isolated home and user-data directory, scratch git repos, one recorded model exchange set (a few tiny prompts, including a real explore subagent) replayed identically on both builds. Scenario: a worktree-isolation session; a peer chat with its own host-created worktree running a subagent (subagent chat has origin tool and is pinned to 2 session folders); 11 more peer chats each with their own worktree; archive all 12 and delete their worktrees; keep the session subscribed for 3 min.

Per 5 s timeline after archiving and deleting (same fixture, same window):

base (e0a4363d21a) this branch
changeset/statusChanged broadcasts, first 5 s / next 5 s / rest 412 / 28 / 0 10 / 0 / 0
"Branch git diff unavailable" 168 + 12 1
spawn git ENOENT lines 396 + 27 1
git spawns 1,181 + 79 41

Earlier whole-window (3 min) trials: base 297-428 broadcasts and 774-1,276 git spawns (4 trials); this branch 18-19 broadcasts and 79-96 git spawns (3 trials).

Correction / scale caveat.

  • In this reproduction the base behavior is a burst (peak ~82 broadcasts/s for ~10 s, comparable to the reported ~97/s), then silence. It is not a sustained loop.
  • The cost is roughly quadratic in the number of owners (12 here, 41 in the observed session), and the observed session also had live git status calls timing out at 5 s and git-state refreshes re-arming the cascade. Why it lasted minutes there is an inference from the mechanism, not something I reproduced.
  • Ping was not discriminating: p50 1-2 ms on both; the max (0.1-2.8 s) tracks machine load (load average 60-260 from unrelated work). So the "stays responsive / 58 s ping" part of the contract is not verified at the reported scale.

Neighbors replayed on this branch: PASS.

  • A single-folder session is identical to base.
  • Unarchive re-attaches the folder and the host starts its file watcher again.
  • A visible chat's deleted worktree gives a bounded burst that settles (102 broadcasts in the first 20 s, then 0 for 100 s; base: 73, then 0), and after recreating the worktree an explicit refresh returns the branch changeset to ready within ~1 s.
  • Host restart/restore gives a bounded burst in the first ~10 s, then silence.

Performance / lifecycle assessment

  • getChatsInUse is O(chats) per call plus an ancestor walk per chat (bounded by visited-set); it replaces a walk that already existed in AgentService.
  • No new listeners, timers or disposables beyond one extra branch inside the existing onDidEmitEnvelope listener. Subscription release goes through the existing _releaseBranchSummaryResource path (covered by a unit test); file-watcher start/stop counts per worktree are identical on base and this branch in the live run. Heap/listener-count measurement was not run.

Partially verified, remaining

  • Independent different-model code review: approved. Reviewer model gpt-5.4 (the implementer is a different model), read-only, target e0a4363d21a..7212ebd81ad, all 11 changed files plus surrounding ownership, state-envelope, changeset-scope, refresh/retry, watcher, discard and git-cache paths, full review matrix. Findings: none; no repairs requested. The reviewer independently ran the four targeted unit files (263 passing, 30 pending) and the 26 real-git getRepositoryRoot integration tests, and confirmed git diff --check is clean and all four commits are signed. It noted the two follow-ups below as reasonable and out of scope.
  • Memory-leak audit: PASS by static review and unit coverage; heap/listener measurement NOT RUN.
  • Live check at the reported scale (41 owners, live git status timeouts, ping under load): NOT RUN.
  • Follow-up (not in this PR): AgentHostGitService._runGit still spawns git for other callers (rev-parse HEAD, reviewed ref lookups) in a missing working directory; trace-level only.
  • Follow-up (not in this PR): on restore, each restored chat's own branch changeset is computed about 4 times, archived chats included; bounded, once per host start.
  • Out of scope: the pull-request-operations flip-flop (I expect it to stop once status broadcasts stop; not measured); pruning stale persisted workingDirectories; retry/backoff for 5 s git status timeouts; file watcher exclusions.

getRepositoryRoot spawned 'git rev-parse --show-toplevel' with cwd set to
the working directory even when that folder had been deleted. The resulting
'spawn git ENOENT' has empty stderr, so it was never negatively cached and
was re-probed and re-logged on every refresh.

Check that the directory exists before spawning git and return undefined
quietly without caching, so a worktree recreated at the same path is
picked up by the next lookup. A spawn ENOENT for a directory that vanished
after the check is handled the same way.
The Branch summary computed and watched every folder any chat of a session
ever had, including archived chats and the folders subagents are pinned to
(often deleted worktrees). Each git-state refresh re-armed every one of
those owners through refreshBranchChangeset, which clears what is known
about an unavailable owner, so a session with many archived chats and
subagents kept recomputing and re-broadcasting changeset status.

Move the 'only chats in use count' rule (#341782, #341885) out of
AgentService into getChatsInUse and apply it to the Branch summary owners,
the summary working directories and the coordinator's branch summary
subscriptions. AgentService behavior is unchanged. The default chat is
always kept.

Also stop two ways a round restarts itself: do not dispatch a changeset
status that is already current, and do not recompute an owner whose
computation already ended in an error from the summary update; an explicit
refresh still retries it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…archived

The branch summary subscribes to the folders of the chats in use only, but
nothing re-evaluated that set when a chat's archived state changed. Archiving
left the chat's changeset and file watcher subscribed, and unarchiving left it
unobserved until an unrelated refresh.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lder and in-use fixes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 21, 2026 17:24
@sandy081 Sandeep Somavarapu (sandy081) added this to the 1.144.0 milestone Oct 21, 2026
@sandy081
Sandeep Somavarapu (sandy081) marked this pull request as ready for review October 21, 2026 17:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

3 open issues
What changed in this PR

This PR reduces agent host hangs by avoiding repeated git probes and changeset recomputation for folders that are only referenced by archived chats (including archived subagents / deleted worktrees), and by reconciling branch-summary subscriptions when archive state changes.

Changes:

  • Skip git spawning for missing/non-directory working directories and reduce noisy warnings/log churn.
  • Centralize “chats in use” resolution and apply it to branch summary owners, session summary working directories, and branch summary reconciliation.
  • Add/adjust unit + integration tests covering missing dirs, recreated worktrees, and archive/unarchive effects on subscriptions.
File Description
src/​vs/​platform/​agentHost/​node/​agentHostGitService.ts Avoid git spawn for missing working directories; add helper + ENOENT handling.
src/​vs/​platform/​agentHost/​node/​agentHostChatsInUse.ts New shared logic to compute chats “in use” and session summary sources.
src/​vs/​platform/​agentHost/​node/​agentService.ts Delegate in-use folder computation to new shared helper.
src/​vs/​platform/​agentHost/​node/​agentHostChangesetService.ts Restrict branch-summary owners to in-use sources; reduce redundant status dispatches.
src/​vs/​platform/​agentHost/​node/​agentHostChangesetCoordinator.ts Reconcile branch summary resources on chat archive/unarchive and removals.
src/​vs/​platform/​agentHost/​node/​agentConfigurationService.ts Limit session-summary working directories to in-use sources.
src/​vs/​platform/​agentHost/​test/​node/​agentHostGitService.integrationTest.ts Add integration tests for missing dirs, recreated worktrees, and contribution probing behavior.
src/​vs/​platform/​agentHost/​test/​node/​agentHostChatsInUse.test.ts New unit tests for chat-in-use resolution rules.
src/​vs/​platform/​agentHost/​test/​node/​agentHostChangesetService.test.ts Add tests ensuring archived chats/subagents don’t trigger branch summary computation; reduce recomputation loops.
src/​vs/​platform/​agentHost/​test/​node/​agentHostChangesetCoordinator.test.ts Add tests for branch summary refresh scoped to in-use chats and for archive/unarchive subscription behavior.
src/​vs/​platform/​agentHost/​test/​node/​agentConfigurationService.test.ts Add tests for session summary directory selection using in-use logic.

Review effort: Lite

…t they detect

Review feedback: _isDirectory returned true for stat failures other than ENOENT/ENOTDIR, and isSpawnNotFoundError also matched a missing working directory. Rename them to _isMissingDirectory (true only when the path is definitely not a directory) and isSpawnEnoentError. No behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@connor4312 Connor Peet (connor4312) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the security and correctness of the changes and relevant remote context. No actionable P1 or P2 findings identified.

Written by Connor's agent running GPT-6.1 Sol

@sandy081
Sandeep Somavarapu (sandy081) merged commit 6cad9f7 into main Oct 21, 2026
58 checks passed
@sandy081
Sandeep Somavarapu (sandy081) deleted the agents/fix-agent-host-git-spawn-enoent-issue branch October 21, 2026 18:22
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.

4 participants