Fix opaque "Process exited with code 1" in VS Code workspace view#9136
Fix opaque "Process exited with code 1" in VS Code workspace view#9136vhvb1989 wants to merge 2 commits into
Conversation
7adc968 to
b19aa8e
Compare
|
Azure Pipelines: Successfully started running 1 pipeline(s). 21 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
/azp run azure-dev - vscode |
|
Azure Pipelines: Successfully started running 1 pipeline(s). |
|
🚧 Blocked by #9140. The remaining red check ( |
The Azure Resources workspace tree resolves each application node by running `azd show --no-prompt --output json` via execAsync. When azd exits non-zero, spawnStreamAsync rejects from a ChildProcess handler with a generic "Process exited with code 1" message *before* the caller reads stderr, so the real azd error is discarded. The failure is then reported as a modal error with a "Report Issue" button, prompting users to file bugs (e.g. #9130, #9131) for routine states like not being logged in or having no environment yet. - execAsync: on non-zero exit, append the child process's stderr to the error message so callers can classify and report the real failure instead of an opaque exit code. This also makes the existing azure.yaml error classification in AzureDevShowProvider effective. - AzureDevCliApplication.getResults: suppress the error display for this background tree resolution so expected/environmental failures no longer surface a Report Issue popup. Telemetry still records them. - Add execAsync unit tests covering success, stderr-on-failure, and empty-stderr failure paths. Fixes #9130 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8907c2b4-8adc-4280-8f11-af43d905644f
The previous execAsync test spawned a real `node -e` subprocess, which failed on Windows CI: NoShell stripped the inner double quotes from the inline script (`process.stdout.write(out)` -> ReferenceError). Relying on a real process / process.execPath inside the extension host is fragile and platform-dependent. Add an optional injectable spawn function to execAsync (defaulting to the real spawnStreamAsync) and rewrite the tests to inject a fake that writes to the accumulator pipes and resolves/rejects. This exercises the real stderr-appending logic deterministically with no subprocess, so it passes identically on Linux, macOS, and Windows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8907c2b4-8adc-4280-8f11-af43d905644f
82e3535 to
304a3cd
Compare
|
Azure Pipelines: Successfully started running 1 pipeline(s). 21 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
📋 Prioritization NoteThanks for the contribution! The linked issue isn't in the current milestone yet. |
There was a problem hiding this comment.
Pull request overview
Improves VS Code workspace-view error handling for failed azd show calls.
Changes:
- Suppresses background tree-resolution error dialogs.
- Includes stderr in process-exit errors.
- Adds unit tests and release notes.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
AzureDevCliApplication.ts |
Suppresses passive resolution dialogs. |
execAsync.ts |
Appends stderr to exit errors. |
execAsync.test.ts |
Tests process output and failures. |
CHANGELOG.md |
Documents the fix. |
| // with code 1") from within a ChildProcess handler, before the caller ever reads stderr. Append | ||
| // the process's stderr to the error so callers can classify and report the real failure instead | ||
| // of an opaque exit code. | ||
| const stderr = (await stderrFinal.getString()).trim(); |
| options?.stdErrPipe?.write(Buffer.from(stderr)); | ||
| } | ||
| options?.stdErrPipe?.end(); | ||
|
|
||
| return rejectWith ? Promise.reject(rejectWith) : Promise.resolve(); |
VSCode Extension Installation Instructions
|
jongio
left a comment
There was a problem hiding this comment.
One issue on the error-surfacing change, the rest looks fine.
The new catch block in execAsync can hang for any rejection that isn't a non-zero process exit (details inline). I confirmed it against @microsoft/vscode-processutils 0.2.1: with this change an ENOENT command (azd not on PATH) never settles, where the old await spawnStreamAsync(...) rejected right away. Gating the stderr read on isChildProcessError keeps the fix for the code-1 case while letting pre-spawn and spawn-error failures rethrow.
The suppressDisplay change and CHANGELOG entry look good.
| // with code 1") from within a ChildProcess handler, before the caller ever reads stderr. Append | ||
| // the process's stderr to the error so callers can classify and report the real failure instead | ||
| // of an opaque exit code. | ||
| const stderr = (await stderrFinal.getString()).trim(); |
There was a problem hiding this comment.
I verified this hangs. stderrFinal.getString() only resolves on the stream's close event, which needs .end() called on stdErrPipe. spawnStreamAsync only pipes and ends that stream after the process actually spawns and exits, so for a pre-spawn throw (cancelled token) or a spawn error event (ENOENT when azd is not on PATH) the pipe is never ended and this await never settles. The caller then hangs instead of receiving the original error.
Reproduced against @microsoft/vscode-processutils 0.2.1: an ENOENT command rejects immediately with the old await spawnStreamAsync(...), but with this catch block it never resolves or rejects. Same for a pre-cancelled token. Since azd-not-on-PATH is a common first-run state, this turns a clean not found error into an indefinite hang on the workspace tree node.
Only the non-zero-exit path guarantees stdErrPipe was ended, so gate the stderr read on it and let everything else rethrow:
} catch (error) {
// Only a non-zero process exit (ChildProcessError) guarantees stdErrPipe was ended, so only
// then is it safe to await it. Pre-spawn failures (cancelled token, ENOENT) never end the pipe.
if (isChildProcessError(error)) {
const stderr = (await stderrFinal.getString()).trim();
if (stderr) {
error.message = `${error.message}\n${stderr}`.trim();
}
}
throw error;
}isChildProcessError is exported from @microsoft/vscode-processutils.
| ); | ||
| expect((error as Error).message, 'Error should surface the underlying stderr').to.include( | ||
| 'parsing project file: File is empty.' | ||
| ); |
There was a problem hiding this comment.
This failure test writes and ends stderr before returning the rejected promise, so it can't catch the hang above. A regression test that leaves the stderr pipe open when it rejects (or drives the real spawn path with a bogus command) would fail on the current code and pass once the stderr read is gated on the exit case.
Warning
Blocked by #9140 — the
azure-dev - vscodeADO pipeline currently fails for allext/vscodePRs due tonpm ci --no-optionaldropping esbuild's platform binary (unrelated to this change). GitHubbuild-testis green on all 3 OSes for this commit.Fixes #9130 (and its duplicate #9131).
Problem
Customers received an opaque error notification with a Report Issue button:
Root cause
The Azure Resources workspace tree view resolves each application node by running
azd show --no-prompt --cwd <dir> --output jsonviaexecAsync(
AzureDevCliApplication.getResults→WorkspaceAzureDevShowProvider.getShowResults).azd showexits non-zero,spawnStreamAsyncrejects from inside aChildProcessexithandler with a genericProcess exited with code 1message (hence theChildProcess.<anonymous>call stack) — beforeexecAsyncever reads the accumulatedstderr. So the real azd error (on stderr) is silently discarded, and the existing
azure.yamlerror classification ingetShowResultscan never match.getResultsdid not setsuppressDisplay, so this background tree resolution surfaced amodal error with a Report Issue prompt. Routine states (not logged in, no environment yet,
invalid
azure.yaml) therefore looked like extension bugs and got auto-reported (Process exited with code 1 #9130, Process exited with code 1 #9131).Fix
execAsync.ts— on a non-zero exit, append the child process's stderr to the thrownerror's message.
AccumulatorStream.getString()awaits the streamcloseevent, so stderr isfully flushed (no race). This surfaces the real reason and makes the existing classification in
AzureDevShowProvidereffective.AzureDevCliApplication.getResults— seterrorHandling.suppressDisplay = truefor thispassive tree resolution so expected/environmental failures no longer pop a Report Issue dialog.
Telemetry still records the failure for real triage, and
azure.yamlproblems continue to bereported via the Problems panel.
execAsyncunit tests (success, stderr-on-failure, empty-stderr failure).Validation
npm run lint✓npx tsc --noEmit✓npx cspell "src/**/*.ts" --config .vscode/cspell.yaml✓ (0 issues)npm run build(esbuild + tsc) ✓npm test(vscode-test) could not run in this environment (requires a display/Xvfb, which isunavailable). The new tests use the current Node.js executable to exercise the real
spawnStreamAsyncpath cross-platform.