Summary
The nightly scheduled azure-dev - cli pipeline (ADO azure-sdk/internal, definition 4643) is failing every night due to 7 deterministic phantom unit-test failures in the cmd and cmd/middleware packages. The tests actually pass, but a spinner writes to os.Stdout during the test, corrupting the go test -json event stream that gotestsum consumes, so they are reported as (unknown) → failure. This is the same class of failure as #8385.
Failing tests (identical every run, all platforms — Windows/Mac)
cmd TestToolInstallAction_resolveToolIds_AllFlag
cmd TestToolInstallAction_resolveToolIds_NoPromptWithoutTarget_Errors
cmd TestToolUpgradeAction_AllFlag_NoPrompt
cmd TestToolUpgradeAction_NoPrompt_WithoutTarget_Errors
cmd TestToolUninstallAction_resolveToolIds_AllFlag_NoPrompt
cmd TestToolUninstallAction_resolveToolIds_NoPromptWithoutTarget_Errors
cmd/middleware TestToolFirstRunMiddleware_OfferInstall_PromptError
Log signature (each test PASSES but is marked FAIL):
=== FAIL: cmd TestToolInstallAction_resolveToolIds_AllFlag (unknown)
| Detecting tool status...
--- PASS: TestToolInstallAction_resolveToolIds_AllFlag (0.00s)
When it started
Regression introduced by #9045 ("UX: implement new design of azd tool (install/upgrade/list/check)"), merged 2026-07-14 17:51 UTC (commit 197a3aec). The first nightly to include it (2026-07-14) and every nightly since have been red on these tests.
Why it happens (root cause)
In cli/azd/cmd/tool.go, five uxlib.NewSpinner(...) calls omit the Writer option, so the spinner defaults to os.Stdout (cli/azd/pkg/ux/spinner.go:43):
- L206
"Detecting tools..."
- L374
"Checking tool status..."
- L907
detectAllTools ("Detecting tool status..." / "Detecting installed tools...") — hit by the resolveToolIds tests
- L1920
"Checking for upgrades..."
- L2118
"Checking %s..."
During the unit tests these spinners write raw ANSI to os.Stdout (fd 1), bypassing testing's output capture and corrupting the parallel go test -json stream that gotestsum parses. The result is phantom (unknown) failures on whichever cmd/cmd/middleware tests are emitting at that moment.
The correct pattern already exists in cli/azd/cmd/middleware/tool_first_run.go, where every spinner sets Writer: m.console.Handles().Stdout (test-safe buffer under test). The five tool.go spinners were not given a Writer.
This is explicitly called out in cli/azd/AGENTS.md ("Never write to os.Stdout in tests") and is meant to be guarded by the forbidigo linter — but that linter is scoped to *_test.go only, so a spinner created in non-test code (tool.go) and exercised by a test slips through.
Why the nightly retry fix (#9118) does not help
#9118 added gotestsum --rerun-fails to the integration run only:
- These 7 failures are unit tests, which have no retry — so they always red the run.
- They also defeat the integration retry: 7 phantom failures + a couple of real live-Azure flakes exceed
--rerun-fails-max-failures=5, so gotestsum skips all reruns (ERROR number of test failures (9) exceeds maximum (5)), meaning even genuinely flaky integration tests never get retried.
Proposed fix
Route all five tool.go spinners through the console writer instead of os.Stdout (pass the input.Console into detectAllTools and set Writer: console.Handles().Stdout), matching cmd/middleware/tool_first_run.go. That clears the 7 phantom failures; with the count back under 5, #9118's retry can also do its job on real integration flakes.
Follow-up worth considering: extend forbidigo/lint coverage (or a Writer-required lint) to catch spinners created in non-test code that default to os.Stdout.
Evidence
Summary
The nightly scheduled
azure-dev - clipipeline (ADOazure-sdk/internal, definition 4643) is failing every night due to 7 deterministic phantom unit-test failures in thecmdandcmd/middlewarepackages. The tests actually pass, but a spinner writes toos.Stdoutduring the test, corrupting thego test -jsonevent stream thatgotestsumconsumes, so they are reported as(unknown)→ failure. This is the same class of failure as #8385.Failing tests (identical every run, all platforms — Windows/Mac)
cmd TestToolInstallAction_resolveToolIds_AllFlagcmd TestToolInstallAction_resolveToolIds_NoPromptWithoutTarget_Errorscmd TestToolUpgradeAction_AllFlag_NoPromptcmd TestToolUpgradeAction_NoPrompt_WithoutTarget_Errorscmd TestToolUninstallAction_resolveToolIds_AllFlag_NoPromptcmd TestToolUninstallAction_resolveToolIds_NoPromptWithoutTarget_Errorscmd/middleware TestToolFirstRunMiddleware_OfferInstall_PromptErrorLog signature (each test PASSES but is marked FAIL):
When it started
Regression introduced by #9045 ("UX: implement new design of azd tool (install/upgrade/list/check)"), merged 2026-07-14 17:51 UTC (commit
197a3aec). The first nightly to include it (2026-07-14) and every nightly since have been red on these tests.Why it happens (root cause)
In
cli/azd/cmd/tool.go, fiveuxlib.NewSpinner(...)calls omit theWriteroption, so the spinner defaults toos.Stdout(cli/azd/pkg/ux/spinner.go:43):"Detecting tools...""Checking tool status..."detectAllTools("Detecting tool status..."/"Detecting installed tools...") — hit by theresolveToolIdstests"Checking for upgrades...""Checking %s..."During the unit tests these spinners write raw ANSI to
os.Stdout(fd 1), bypassingtesting's output capture and corrupting the parallelgo test -jsonstream thatgotestsumparses. The result is phantom(unknown)failures on whichevercmd/cmd/middlewaretests are emitting at that moment.The correct pattern already exists in
cli/azd/cmd/middleware/tool_first_run.go, where every spinner setsWriter: m.console.Handles().Stdout(test-safe buffer under test). The fivetool.gospinners were not given aWriter.This is explicitly called out in
cli/azd/AGENTS.md("Never write toos.Stdoutin tests") and is meant to be guarded by theforbidigolinter — but that linter is scoped to*_test.goonly, so a spinner created in non-test code (tool.go) and exercised by a test slips through.Why the nightly retry fix (#9118) does not help
#9118 added
gotestsum --rerun-failsto the integration run only:--rerun-fails-max-failures=5, so gotestsum skips all reruns (ERROR number of test failures (9) exceeds maximum (5)), meaning even genuinely flaky integration tests never get retried.Proposed fix
Route all five
tool.gospinners through the console writer instead ofos.Stdout(pass theinput.ConsoleintodetectAllToolsand setWriter: console.Handles().Stdout), matchingcmd/middleware/tool_first_run.go. That clears the 7 phantom failures; with the count back under 5, #9118's retry can also do its job on real integration flakes.Follow-up worth considering: extend
forbidigo/lint coverage (or aWriter-required lint) to catch spinners created in non-test code that default toos.Stdout.Evidence