Stop CI deciding whether the TUI golden capture has colour - #5
Merged
Conversation
The TUI golden job has never passed. Not "started failing" -- every run on
record, back to 2026-08-30, fails the same way: each committed golden
carries a "-- cell styles --" block and the fresh capture carries none. The
rendered text matches exactly; only the styling is missing.
The cause is one line in termenv. Output.isTTY() returns false whenever CI
is set in the environment, before it looks at the file descriptor at all:
if len(o.environ.Getenv("CI")) > 0 {
return false
}
GitHub Actions sets CI=true. The capture harness passed the host
environment through to the child, so lipgloss resolved the Ascii profile,
the binary emitted no SGR, pyte found no styled cells, and render_snapshot
omitted the whole section. Locally, where CI is unset, the same capture
produces TrueColor spans -- which is how goldens containing styles came to
be committed against a check that could never reproduce them.
So the harness now builds the child environment itself and drops CI along
with NO_COLOR. It owns a real PTY and has already set the size on it, so
"this is a terminal" is the truthful answer; letting the host argue
otherwise is the one thing a golden harness must not permit. CLICOLOR_FORCE
would not have fixed it: it only lifts Ascii to 16-colour ANSI, while the
goldens record TrueColor.
Two things stop this recurring. The environment decision moves into
child_env(), a pure function that is unit-tested rather than reachable only
through a PTY the harness refuses to open on Windows. And assert_styled()
now fails a capture that contains no styled cells at all, because that is
not a snapshot of this TUI, it is a snapshot of colour being off -- which
previously failed silently in both directions, reporting every golden as
changed under `check` and overwriting reviewed goldens under `update`.
The goldens themselves are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
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.
The
TUI golden and protocol safetyjob has never passed. Not "started failing" — every run on record, back to 2026-08-30, fails the same way.Each committed golden ends with a
-- cell styles --block and the fresh capture has none. The rendered text matches exactly, character for character; only the styling is missing, on all 17 snapshots.Cause
One line in
termenv(v0.16.0,termenv.go:28):isTTY()answers "no" wheneverCIis set, before it looks at the file descriptor at all. GitHub Actions setsCI=true, andscripts/tui_capture.pypassed the host environment through to the captured process. So:CI=true→isTTY()false →ColorProfile()returnsAscii→ lipgloss emits no SGR → pyte sees only default-styled cells →render_snapshotomits the entire style section → all 17 goldens mismatch.Locally, where
CIis unset, the identical capture resolves TrueColor and produces the spans. That is how goldens containing styles came to be committed against a check that could not reproduce them anywhere but a developer's machine.Fix
The harness now builds the child environment itself and drops
CIalongside theNO_COLORit already removed. It owns a real PTY and has already set the window size on it, so "this is a terminal" is the truthful answer; letting the host argue otherwise is the one thing a golden harness must not permit.CLICOLOR_FORCE=1is not a substitute, and this is worth being explicit about since it looks like the obvious lever.EnvColorProfile()only consults it as a floor:It would lift Ascii to 16-colour ANSI, and the goldens record TrueColor (
fg=00d9ff). The captures would still mismatch, just differently.The goldens themselves are unchanged. This PR changes only the harness.
Stopping it recurring
Two changes beyond the one-line cause, both aimed at the same weakness — this failure was invisible from inside the harness.
child_env()is now a pure function holding the environment decision, so it is unit-tested directly. Previously that logic was reachable only throughcapture(), which needs a PTY and which refuses to run on Windows at all:So on a Windows machine there was no way to exercise it, and the three new
ChildEnvTestsnow run everywhere.assert_styled()rejects a capture containing no styled cells. A render where every cell is default-styled is not a snapshot of this TUI, it is a snapshot of colour being switched off. That previously failed silently in both directions:checkreported all 17 goldens as changed with no indication why, andupdatewould have cheerfully overwritten the reviewed goldens with colourless ones — turning a broken environment into the new baseline.Verification
python -m unittest scripts/tui_capture_test.pypytenot installed locally, runs in CI)CIremoval failstest_ci_is_removed_so_termenv_sees_a_terminalFive tests are new: three on
child_envand two onassert_styled.The end-to-end result can only be confirmed by CI, because the capture harness will not run on Windows. If the golden check passes on this PR, that is the first time it has ever passed. If any snapshot still differs after this, the remaining diff is a genuine rendering difference between the machine that generated the goldens and Linux CI, and the goldens need regenerating — but the styles-versus-no-styles difference is fully explained by the above.
Not addressed here
vulncheck,lint, and the platform test failures are separate and untouched.lintis fixed in a separate PR.🤖 Generated with Claude Code