Repository navigation
fix(structured): return a failure exit code so serve reports 503 - #1131
Merged
kgaughan merged 1 commit intoSep 13, 2026
Merged
Conversation
The structured outputer ended Output() with an unconditional return 0, so a failing suite exited 0 and serve.go, which maps a non-zero code to 503, answered 200. A healthcheck on goss serve -f structured could never fail. Summary.Failed was already counted a few lines above. This is issue goss-org#992 for a second format: the same gap was reported for prometheus and fixed in goss-org#1109, and structured was left behind. Because it has now happened twice, the test pins the exit code of all nine outputers and fails if a tenth is registered without a choice. The prometheus outputer accumulates into package-level counters, so each case resets them the way prometheus_test.go already does.
kgaughan
approved these changes
Sep 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checklist
make test-all(UNIX) passes. CI will also test thistest-alltarget in the Makefile, so I left this unticked rather than tick it for something else.make test-short-all(fmt, lint, vet, test) passes in full, as doesgo test ./...under-race -shuffle=on.golangci-lint run ./outputs/...reports 0 issues. Thetest-int-*targets need a Docker daemon and were not run; nothing underintegration-tests/orextras/uses the structured format.docs/cli.mdalready says serve returns the results in the requested format and "an http status of 200 or 503", and that validate "Exits with status 0 on success, non-0 otherwise". This makes the code match both, rather than changing a documented contract.Description of change
goss serve -f structuredanswers 200 on a failing suite, so a healthcheck built on it never fails.Outputinoutputs/structured.goends in an unconditionalreturn 0, andserve.gomaps a non-zero outputer code to 503 and zero to 200. Eight of the nine formats already return non-zero on failure.Summary.Failedis counted a few lines above, so the information was already there./healthzon a failing suitevalidateexitThis is issue #992 a second time. That report was "the prometheus output does not report 503 at
/healthzwhen a test fails", and the same gap was left in structured. Draft #1119 found it independently and pinned the wrong value rather than change it, on the grounds that a behaviour change did not belong inside a logging PR.Because it has now happened twice, the test pins the exit code of all nine outputers rather than only this one, and fails if a tenth is registered without a deliberate choice.
What changes for users
Three things, all intended, but worth naming.
Anyone using
-f structuredas a healthcheck or a CI gate goes from always-passing to actually reporting failures. That is the point, and it is what #992 asked for.util/config.godefaults to structured as "most appropriate for package usage", so a Go program embedding goss and callingValidatewith a default config now gets a non-zero code on failure where it got 0 before. That is the same correction, but it reaches library callers and not just the CLI.validate.goshort-circuits retries onexitCode == 0, so-f structured -r <timeout>was previously a no-op. It now retries, which means stdout can carry more than one JSON document.-f json -ralready behaves exactly this way, so this makes structured consistent rather than introducing something new. Worth knowing ifGOSS_RETRY_TIMEOUTis set globally in a container.The 503 still carries the full structured body, so consumers that read the payload are unaffected.
Verification
Ran the built binary before and after. On a failing gossfile,
validate -f structuredgoes from exit 0 to exit 1 while the other eight formats are byte-identical, andserve --format structuredgoes from HTTP 200 to 503. A passing gossfile still gives exit 0 and 200, and a skip-only suite stays 0.Dropping or weakening the guard fails the new test five different ways: reverting to
return 0, using>= 0, keying onTestCountinstead ofFailed, an off-by-one that lets a single failure through, and returning nagios's 2 instead of 1.One thing the test needed. The prometheus outputer accumulates into package-level counters, so exercising it here made
TestPrometheusOutputfail intermittently. Each case now callsdefer resetMetrics(), the wayprometheus_test.goalready does, and 20 consecutive runs plus 8-race -shuffle=onruns are clean. The fixtures use the existingmakeResultshelper so this adds no new package-level symbol.Separately, and not touched here: structured reports
"successful": falsefor passing tests, because it marshalsTestResult.Successful, which nothing ever assigns, whilejson.goderives the value instead. Different bug, happy to send it on its own.AI disclosure: written with Claude Code. I ran the binary, the healthcheck probes and the mutation checks myself.