Skip to content

Fail loudly when tree-sitter grammars are unavailable - #328905

Draft
DonJayamanne wants to merge 2 commits into
mainfrom
don/agents/root-cause-analysis-failing-tests-6300d7e9
Draft

Fail loudly when tree-sitter grammars are unavailable#328905
DonJayamanne wants to merge 2 commits into
mainfrom
don/agents/root-cause-analysis-failing-tests-6300d7e9

Conversation

@DonJayamanne

Copy link
Copy Markdown
Contributor

Fixes the macOS unit test failures reported in vscode-engineering#3484, without reverting #328863 — that PR is innocent.

Root cause

Build 20260804.3 (macOS, Electron unit tests) failed with 37 assertion mismatches across CommandAutoApprover (28), SessionPermissionManager (7) and AgentSideEffects (2). Every failure had the same cause: the bash and PowerShell tree-sitter grammars could not be used, so _extractSubCommands returned undefined and every command degraded to noMatch.

That degradation is deliberate and fail-closed in production, but it is silent: grammars load via Promise.allSettled and failures are only _logService.warn, which tests discard through NullLogService. One environment problem therefore surfaced as dozens of unrelated-looking assertion failures — and CI misattributed it to the unrelated commit at the head of the build.

dce00340cb3 touches none of commandAutoApprover.ts, sessionPermissions.ts, or agentSideEffects.ts.

Evidence

Deleting only the two grammar files (leaving core tree-sitter.wasm intact) reproduces CI exactly:

Condition Result Console noise Matches CI?
Baseline 37 pass / 0 fail none
Both grammars unusable 9 pass / 28 fail zero ✅ exact — counts and all 9 passing test names
Same, all three suites 28 + 7 + 2 zero ✅ exact
Core tree-sitter.wasm missing 2 pass / 1 fail, aborts loud ENOENT ❌ refuted
Bash grammar only 17–20 fail zero ❌ no
Grammars truncated 9 pass / 28 fail zero ✅ exact
Grammars replaced with a valid but wrong grammar 9 pass / 28 fail zero ✅ exact

The last row matters: the signature can arise even when Language.load() succeeds, so a file-existence check is not sufficient. Missing / truncated / corrupt / wrong-ABI are all indistinguishable from the failure alone.

Changes

Make it loud (source)

  • CommandAutoApprover.initialize() now resolves with { parser, bash, powershell } readiness. Readiness is determined by analyzing a probe command per shell, not by trusting Language.load() — this catches the valid-but-wrong-grammar case above.
  • The test suite asserts full readiness in setup(), collapsing 28 confusing failures into 1 clear one.

Stop an incomplete dependency tree reaching the tests (build)

  • When the pipeline restores its node_modules cache it skips npm ci entirely, so a truncated/partial archive is never repaired. Added verifyNodeModulesCache.ts, run after every cache extraction (7 pipelines), which clears the cache-hit variable so the install steps run and rebuild the tree.
  • listNodeModules.ts swallowed statSync failures via catch { continue }, silently dropping files from the cache manifest. It now only skips dangling symlinks and throws otherwise.

Production behavior is unchanged: an unavailable grammar still fails closed and requires user confirmation rather than blocking startup.

Verification

  • 225 passing across all three affected suites (was 224; +1 new readiness test)
  • ✅ With grammars removed → 1 clear failure: tree-sitter must be fully available for these tests (was 28)
  • ✅ Same single clear failure for the valid-but-wrong-grammar case
  • typecheck-client: 7 errors before, 7 after — no regressions
  • build/ typecheck clean; ESLint clean on all changed files
  • verifyNodeModulesCache.ts manually exercised: passes on a good tree, detects missing and zero-length files, honors the custom variable name used by the copilot pipeline

Note on the caching hypothesis

The most plausible source is a poisoned Darwin node_modules cache (per-platform cache keys explain macOS-only). That remains plausible but unproven without the failed archive — which is precisely why this PR hardens detection at both layers rather than asserting a single physical cause. Recommend also invalidating the darwin cache entry.

Analysis methodology

The first root cause I proposed was wrong (I blamed the product.commit heuristic in appNodeModules.ts). It was falsified by experiment: that path produces loud Emscripten ENOENT errors and aborts after ~2 tests, and repo-root product.json is never stamped with a commit. A proposed isBuilt fix was also rejected — it would have broken the working test harness. Findings were independently re-derived and verified by two separate models before this PR.

A macOS unit test run failed with 37 assertion mismatches across
CommandAutoApprover, SessionPermissionManager and AgentSideEffects. All of
them had the same cause: the bash and PowerShell tree-sitter grammars could
not be used, so every command degraded to `noMatch`.

That degradation is deliberate and fail-closed in production, but it is also
silent: grammars are loaded via `Promise.allSettled` and a failure is only
logged with `warn`, which tests discard through `NullLogService`. A single
environment problem therefore surfaced as dozens of unrelated-looking
assertion failures, and was misattributed to the unrelated commit at the head
of the build.

Make the failure obvious instead:

- `CommandAutoApprover.initialize()` now resolves with which pieces are
  usable. Readiness is determined by analyzing a probe command per shell
  rather than by trusting that `Language.load()` resolved, because a grammar
  can load cleanly and still be the wrong grammar or an incompatible ABI --
  which produces exactly the same silent `noMatch` behavior.
- The test suite asserts full readiness in `setup()`, collapsing 28
  confusing failures into one clear one.

Also address how an incomplete dependency tree can reach the tests at all.
When the pipeline restores its `node_modules` cache it skips `npm ci`
entirely, so a truncated or partially written archive is never repaired. Add
a post-extract verification that clears the cache-hit variable so the install
steps run and rebuild the tree. `listNodeModules.ts` additionally swallowed
`statSync` failures, silently dropping files from the cache manifest; it now
only skips dangling symlinks and throws otherwise.

Production behavior is unchanged: an unavailable grammar still fails closed
and requires user confirmation rather than blocking startup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 4, 2026 07:11

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.

🟡 Not ready to approve

Some affected suites still discard readiness, and symlink stat failures can still be silently omitted.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Adds explicit tree-sitter readiness reporting and hardens cached dependency restoration.

Changes:

  • Probes bash and PowerShell grammars and exposes readiness.
  • Adds fail-fast test assertions.
  • Verifies restored caches and surfaces manifest-generation errors.
File summaries
File Description
commandAutoApprover.test.ts Asserts grammar readiness.
sessionPermissions.ts Propagates readiness.
commandAutoApprover.ts Probes parser and grammars.
agentSideEffects.ts Adapts initialization return type.
product-build-win32-compile.yml Verifies restored cache.
product-build-web.yml Verifies restored cache.
product-quality-checks.yml Verifies restored cache.
product-build-linux-compile.yml Verifies restored cache.
product-build-darwin-compile.yml Verifies restored cache.
copilot/setup-steps.yml Verifies Copilot build cache.
verifyNodeModulesCache.ts Adds required-file validation.
listNodeModules.ts Tightens stat error handling.
product-build-alpine.yml Verifies restored cache.
Review details
  • Files reviewed: 13/13 changed files
  • Comments generated: 4
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread src/vs/platform/agentHost/node/sessionPermissions.ts
Comment thread src/vs/platform/agentHost/node/agentSideEffects.ts Outdated
Comment thread build/azure-pipelines/common/listNodeModules.ts Outdated
Comment thread src/vs/platform/agentHost/test/node/commandAutoApprover.test.ts Outdated
- Assert readiness in the `SessionPermissionManager` and `AgentSideEffects`
  test setups too. Previously only `CommandAutoApprover` checked it, so those
  suites still produced the nine downstream assertion mismatches instead of a
  readiness failure.
- Stop discarding the readiness result in `AgentSideEffects.initialize()` so
  the two grammar-dependent tests can assert it.
- Share the assertion through `treeSitterReadinessTestUtils` rather than
  repeating it per suite.
- Only skip a failing `statSync` in `listNodeModules` when it fails with
  ENOENT on a symlink. Errors such as EACCES, EIO or ELOOP were still
  silently skipped and could still yield an incomplete cache manifest.
- Reference the actual report (microsoft/vscode-engineering#3484) instead of
  the unrelated PR that was misattributed as the cause.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants