Skip to content

Close the remaining Windows gaps in the agent host E2E suite - #327761

Merged
roblourens merged 7 commits into
mainfrom
roblou/agents/e2e-windows-followups
Jul 28, 2026
Merged

Close the remaining Windows gaps in the agent host E2E suite#327761
roblourens merged 7 commits into
mainfrom
roblou/agents/e2e-windows-followups

Conversation

@roblourens

@roblourens roblourens commented Jul 28, 2026

Copy link
Copy Markdown
Member

Draft. Stacked on #327642 — base that PR's branch, not main, so the diff shows only the commits here. Retarget to main once #327642 lands.

Follow-ups from the cross-platform capture work. Two commits close the last Windows gaps that were portability problems; two fix bugs found while verifying them.

Scrub the account name only where it identifies a user

Snapshot and capture normalization ended with an unanchored replaceAll(userName, '${user}'). With the GitHub Actions Linux account name runner — an ordinary English word — captured text such as the runner completed was rewritten to the ${user} completed.

That is worse than cosmetic. It only misfires where the account name happens to be a common word, so the same recording normalizes differently on macOS and Linux CI — exactly the cross-platform mismatch this normalization exists to prevent.

The obvious fix (restrict to path contexts) is wrong. The committed subagent capture records the account name in ls -l owner columns, outside any path:

drwx------     4 ${user}  staff     128 ${timestamp} .
-rw-r--r--     1 ${user}  staff       5 ${timestamp} file-a.txt

Dropping that would leak developer identity into committed fixtures. The scrub now handles exactly two positions — after a path separator (including the escaped \\ form found in embedded JSON), and the owner/group columns of a listing — and leaves prose alone. Shared by both normalizers, which had duplicated the same bug.

Enable session configuration resolves and completes git branches on Windows

Disabled because its temporary git repository could not be deleted, failing suite teardown even though every assertion passed. The assertions were always platform-independent.

Two independent causes:

  • Read-only .git/objects files. A read-only file cannot be deleted on Windows, and rmSync's force only suppresses ENOENT — it does not override the attribute, so the retry loop burned its full timeout on a condition waiting can never fix. Fixed in Make the deterministic shell command E2E test platform-neutral #327642, which also unblocked inspects git status.
  • Background git gc holding handles under .git. New here: initTestGitRepo sets gc.auto 0. These repositories never create enough objects to need it.

The init/identity setup duplicated across three suites is collapsed into initTestGitRepo, so the next test needing a repository cannot forget the gc setting.

Enable worktree session uses the resolved worktree as working directory on Windows

Two claims in the old comment were wrong: the path assertions use URI.fsPath (platform-native, compared against a value derived at runtime from the host's own sessionAdded notification, not a hardcoded POSIX path), and the shell turn is only reached when the provider routes commands through the host terminal tool.

Approve tool calls in a loop, not once

Pinning node -e "…" in the worktree test initially stalled the turn, which looked like the pinned command being unsupported. It was not.

A pinned command is not on the provider's auto-approve list the way pwd is, so it adds an approval round-trip. Every other ported test handles this transparently because driveTurnToCompletion confirms each unconfirmed chat/toolCallReady as it arrives. This test drives its turn by hand, and its two branches disagreed: the SDK-shell branch already used startBackgroundApprovalLoop, while the host-terminal branch approved exactly once.

A turn can raise more than one approval, so the second stayed pending and the turn stalled on session/inputNeededSet — presenting as a hang rather than a permission problem.

Both branches now use the shared loop, and the command is pinned like everywhere else. Verified by re-recording both providers. This removes the last special case: no test depends on a command being auto-approved, and POSIX_COMMAND_EXCEPTIONS stays empty.

Result

The Windows-disabled list is down to one row — a bang command runs locally and exposes terminal output — which is a turn-completion problem, not a portability one.

Verification

  • Full E2E suite: 150 passing, 0 failing, stable across repeated runs (macOS)
  • 13 unit tests, including 5 new for the account-name scrub covering forerunner, runneradmin, runner.js, and regex escaping
  • Re-recorded the worktree capture for both providers against live CAPI
  • typecheck-client, valid-layers-check, hygiene: pass

What CI has to confirm: both newly enabled tests pass on Windows. The assertions are platform-independent by inspection, but the failure modes here were teardown and approval behavior — neither reproducible on macOS. If either fails, that is a real finding rather than a flake.

Base automatically changed from roblou/agents/e2e-cross-platform-captures to main July 28, 2026 04:46
@roblourens roblourens changed the title Give me a title and description for the PR Close the remaining Windows gaps in the agent host E2E suite Jul 28, 2026
roblourens and others added 5 commits July 27, 2026 21:54
Snapshot and capture normalization ended with an unanchored
`replaceAll(userName, '${user}')`. With the GitHub Actions Linux account
name `runner` — an ordinary English word — captured text such as
`the runner completed` was rewritten to `the ${user} completed`.

That is worse than cosmetic. It only misfires on platforms whose account
name happens to be a common word, so the same recording normalizes
differently on macOS and Linux CI, which is exactly the cross-platform
mismatch this normalization exists to prevent.

Replace only the two positions where the name genuinely identifies a
user: after a path separator (including the escaped form found in
embedded JSON), and the owner/group columns of an `ls -l` listing. The
listing case is load-bearing — the committed subagent capture records the
account name there, outside any path — so it cannot simply be dropped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The test was disabled on Windows because its temporary git repository
could not be deleted, which failed suite teardown even though every
assertion passed. Its assertions were always platform-independent.

Two independent causes, both now addressed:

- Read-only `.git/objects` files. Fixed by clearing read-only attributes
  before retrying removal, which also unblocked `inspects git status`.
- Background `git gc` holding handles under `.git` after the test
  finishes. `initTestGitRepo` now sets `gc.auto 0`; these repositories
  never create enough objects to need it.

Collapse the duplicated init/identity setup in three suites into
`initTestGitRepo` so the gc setting cannot be forgotten by the next test
that needs a repository.

Whether this holds can only be confirmed on a Windows run; the remaining
risk is a handle neither mechanism covers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The comment claiming this test could not be made portable was wrong on
two of its three counts:

- The path assertions use `URI.fsPath`, which is platform-native, and the
  expected value is derived at runtime from the host's own `sessionAdded`
  notification rather than hardcoded as a POSIX path.
- The recorded `pwd` is only reached when the provider routes commands
  through the host terminal tool, which on Windows is PowerShell, where
  `pwd` is an alias for `Get-Location`.

The third — that pinning a command stops the turn on an unanswered
permission prompt — was observed in a different test. This one already
handles confirmation, dispatching `ChatToolCallConfirmed` when
`toolCallReady` arrives unconfirmed.

Drop `pwd` from the record-time command blocklist for the same reason,
which empties `POSIX_COMMAND_EXCEPTIONS`; a recording now passes the check
without an opt-out.

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

Tried replacing `pwd` with `node -e "console.log(process.cwd())"` on both
of the test's turns. Both stalled on `session/inputNeededSet`: providers
auto-approve `pwd` as a safe read-only command, and an arbitrary `node -e`
invocation is not on that list, so pinning turns a silent tool call into
one that waits for a confirmation this test's flow does not answer — even
on the turn that does dispatch `ChatToolCallConfirmed`.

`pwd` is portable regardless, since PowerShell defines it as an alias for
`Get-Location`, so the test still runs on Windows.

Correct the stale comment, which still claimed the assertions were
POSIX-shaped, and record the general point: pinning a command changes its
permission posture, so prefer steering to a file tool where one exists.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Answers why pinning `node -e` failed here when it works in every other
ported test: those all go through `driveTurnToCompletion`, which confirms
each unconfirmed `chat/toolCallReady` as it arrives. This test drives its
turn by hand, and its host-terminal branch approved exactly once while its
SDK-shell branch already used `startBackgroundApprovalLoop`.

A pinned command is not on the provider's auto-approve list the way `pwd`
is, so it adds an approval round-trip. With single-shot approval a later
request stays pending and the turn stalls on `session/inputNeededSet`,
which reads as a hang rather than a permission problem.

Both branches now use the shared loop, and the command is pinned like
everywhere else. Verified by re-recording both providers.

This also removes the last special case: no test now depends on a command
being auto-approved, and `POSIX_COMMAND_EXCEPTIONS` stays empty.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@roblourens
roblourens force-pushed the roblou/agents/e2e-windows-followups branch from c2bed94 to 4b09421 Compare July 28, 2026 04:58
@roblourens
roblourens marked this pull request as ready for review July 28, 2026 05:10
Copilot AI review requested due to automatic review settings July 28, 2026 05:10
@roblourens
roblourens enabled auto-merge (squash) July 28, 2026 05:10
@roblourens
roblourens marked this pull request as draft July 28, 2026 05:14
auto-merge was automatically disabled July 28, 2026 05:14

Pull request was converted to draft

Windows CI failed it for two reasons, neither about command portability,
and both specific to this test's output assertions:

- The expected path comes from `os.tmpdir()`, which on Windows CI returns
  an 8.3 short form (`C:\Users\CLOUDT~1\...`) while the shell reports the
  long form, so the `includes()` assertion can never match and the wait
  times out. Same class of mismatch as `/var` versus `/private/var` on
  macOS, which snapshot normalization already special-cases.
- The Copilot branch waits for a `chat/toolCallContentChanged` carrying a
  terminal resource, and on Windows that notification never arrives even
  though the tool call starts, is confirmed, and completes.

Keep the pinned command and the shared approval loop from the previous
commit — both are correct regardless of platform — and record what CI
showed so the next attempt starts from evidence rather than a fresh guess.

Reworking this needs `realpathSync.native` on both sides of the path
comparison, and the missing terminal content understood first; neither is
reproducible without a Windows machine.

`session configuration resolves and completes git branches` passed on
Windows in the same run, so the git lock fixes hold.

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

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.

Pull request overview

Note

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.

Closes remaining Windows portability gaps in the agent host E2E suite by tightening fixture normalization (avoid over-scrubbing usernames), stabilizing git-backed tests on Windows, and making shell-driven turns handle tool approval correctly.

Changes:

  • Introduces a targeted scrubUserName normalizer + unit tests; integrates it into snapshot and capture normalization.
  • Adds initTestGitRepo to centralize git test repo setup (identity + gc.auto 0) and uses it across suites.
  • Updates worktree E2E flow to pin a portable “print cwd” command and approve tool calls in a loop; updates recorded captures and docs.
Show a summary per file
File Description
src/vs/platform/agentHost/test/node/userNameScrub.test.ts Adds unit coverage for username scrubbing behavior and regex escaping.
src/vs/platform/agentHost/test/node/posixCommandLint.test.ts Updates lint expectations to treat pwd as allowed (PowerShell alias).
src/vs/platform/agentHost/test/node/e2e/suites/workspaceSuite.ts Pins portable cwd command, uses approval loop, and refactors git init to shared helper.
src/vs/platform/agentHost/test/node/e2e/suites/hostFeaturesSuite.ts Uses shared git init helper and enables the git-branch completion test on Windows.
src/vs/platform/agentHost/test/node/e2e/suites/fileOperationsSuite.ts Uses shared git init helper for Windows-stable temp repo cleanup.
src/vs/platform/agentHost/test/node/e2e/harness/userNameScrub.ts Implements targeted username scrubbing (path segments + ls -l owner/group columns).
src/vs/platform/agentHost/test/node/e2e/harness/posixCommandLint.ts Removes pwd from POSIX-only patterns and documents rationale.
src/vs/platform/agentHost/test/node/e2e/harness/capiReplayProxy.ts Switches capture normalization from naive replaceAll to scrubUserName.
src/vs/platform/agentHost/test/node/e2e/harness/ahpSnapshot.ts Switches snapshot normalization from naive replaceAll to scrubUserName.
src/vs/platform/agentHost/test/node/e2e/harness/agentHostE2ETestHarness.ts Adds initTestGitRepo helper and removes the POSIX-command exception list.
src/vs/platform/agentHost/test/node/e2e/captures/copilotcli-worktree-session-uses-the-resolved-worktree-as-working-directory.yaml Re-records worktree capture using pinned Node cwd command instead of pwd.
src/vs/platform/agentHost/test/node/e2e/captures/claude-worktree-session-uses-the-resolved-worktree-as-working-directory.yaml Re-records worktree capture using pinned Node cwd command instead of pwd.
src/vs/platform/agentHost/test/node/e2e/KNOWN_ISSUES.md Updates Windows-scoped status and documents approval-loop + git-temp-repo lessons.

Review details

  • Files reviewed: 13/13 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment thread src/vs/platform/agentHost/test/node/e2e/harness/userNameScrub.ts Outdated
Comment thread src/vs/platform/agentHost/test/node/e2e/suites/workspaceSuite.ts
Comment thread src/vs/platform/agentHost/test/node/e2e/harness/posixCommandLint.ts Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@roblourens
roblourens marked this pull request as ready for review July 28, 2026 20:55
@roblourens
roblourens enabled auto-merge (squash) July 28, 2026 20:55
@roblourens
roblourens merged commit 5144ff6 into main Jul 28, 2026
29 checks passed
@roblourens
roblourens deleted the roblou/agents/e2e-windows-followups branch July 28, 2026 21:27
@vs-code-engineering vs-code-engineering Bot added this to the 1.132.0 milestone Jul 28, 2026
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.

3 participants