Skip to content

refactor(tui): resolve the color env overrides at the CLI entry point - #1837

Merged
mergify[bot] merged 1 commit into
mainfrom
devs/sileht/test-env-access-ub/resolve-color-env-overrides-cli-entry-point--b099b704
Sep 18, 2026
Merged

mergify[bot] merged 1 commit into
mainfrom
devs/sileht/test-env-access-ub/resolve-color-env-overrides-cli-entry-point--b099b704

Conversation

@sileht

@sileht sileht commented Sep 18, 2026

Copy link
Copy Markdown
Member

Theme::detect() read NO_COLOR, FORCE_COLOR and CLICOLOR_FORCE
on every call, and it is called from production paths in
mergify-freeze, mergify-queue, mergify-events and
mergify-stack. So any test in any of those crates read the process
environment, on whatever thread it happened to run on, just by
rendering a themed line. That is the read half of the data race this
stack is about: the tests in mergify-ci, mergify-core,
mergify-cli and mergify-stack mutate the process environment
while these read it.

Those three reads could never change the answer inside a test binary
anyway. resolve_enabled returns false for a None choice
whatever they say, and only mergify-cli's entry point ever records
a choice, which the doc comment on set_color_choice already said. A
test harness read three environment variables to compute a value it
had already decided.

mergify-tui now takes the resolved choice and never reads the
environment. --color always|never still wins, NO_COLOR still
beats FORCE_COLOR / CLICOLOR_FORCE, and auto with neither still
defers to the TTY. The precedence moves to resolve_color_choice in
the binary, next to the clap enum it maps from, which is also where
the workspace's environment funnel lives. mergify-tui cannot use
that funnel: it is mergify-core, and this crate deliberately stays
dependency-light.

Two behaviour fixes come with it, both in the same three variables:

Exported-but-empty no longer counts as set. A workflow writing
NO_COLOR: ${{ inputs.no_color }} with no input exports the empty
string, and every mergify in that job lost color with no way back
but --color always. https://no-color.org says "present and not an
empty string"; so does every other variable this CLI reads.

The log stream obeys the same decision. init_tracing was choosing
ANSI from stderr().is_terminal() alone, so NO_COLOR=1 and
--color never gave plain stdout and a -vv stream still full of
escape sequences, which is the one output people redirect to a file.

The remaining visible difference is when the variables are read:
once at startup instead of at every Theme::detect(). Nothing
mutates the environment mid-run, so the answer is the same either
way.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

Copilot AI lite review requested due to automatic review settings September 18, 2026 07:07
@mergify
mergify Bot had a problem deploying to Mergify Merge Protections September 18, 2026 07:07 Failure
@sileht
sileht deployed to func-tests-live September 18, 2026 07:07 — with GitHub Actions Active
@sileht

sileht commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

This pull request is part of a Mergify stack:

# Pull Request Link
1 refactor(tui): resolve the color env overrides at the CLI entry point #1837 👈
2 feat(core): read the environment through one funnel, with a hermetic test overlay #1838
3 refactor(ci): read the CI environment through the funnel #1839
4 refactor(stack): read the environment through the funnel #1840
5 refactor(cli): assert the clap env hook is absent without mutating the environment #1841
6 refactor(auth): read the environment through the funnel #1842
7 chore(lint): make the next process-environment read fail the build #1843

@mergify

mergify Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 All 6 merge protections satisfied — ready to merge.

Show 6 satisfied protections

🟢 🤖 Continuous Integration

  • all of:
    • check-success=ci-gate

🟢 👀 Review Requirements

  • any of:
    • #approved-reviews-by>=2
    • author = dependabot[bot]
    • author = mergify-ci-bot
    • author = renovate[bot]

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|internal|docs|style|refactor|perf|test|build|ci|chore|revert|ui)(?:\(.+\))?!?:

🟢 🔎 Reviews

  • #changes-requested-reviews-by = 0
  • #review-requested = 0
  • #review-threads-unresolved = 0

🟢 📕 PR description

  • body ~= (?ms:.{48,})

🟢 🚦 Auto-queue

When all merge protections are satisfied, this pull request will be queued automatically.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved comments require shared-helper reuse, additional color-behavior tests, and documentation clarification.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Moves color-environment resolution to CLI startup so TUI rendering no longer reads the process environment.

Changes:

  • Resolves color overrides once and treats empty values as unset.
  • Applies the resolved choice to tracing ANSI output.
  • Updates related TUI, progress, and project documentation.
File summaries
File Summary
crates/mergify-tui/src/theme.rs Uses recorded color choices without environment reads.
crates/mergify-tui/src/select.rs Updates color-policy documentation.
crates/mergify-tui/src/lib.rs Documents environment-independent color handling.
crates/mergify-stack/src/progress.rs Updates progress color-policy documentation.
crates/mergify-cli/src/main.rs Resolves color settings and configures tracing ANSI behavior.
AGENTS.md Documents the color-resolution architecture.
Review details

Suppressed comments (4)

crates/mergify-cli/src/main.rs:2815

  • Please use mergify_core::env::var_non_empty for this flag/env resolution instead of adding another empty-as-unset helper. The shared environment module explicitly requires that helper for every flag → env → default chain (crates/mergify-core/src/env.rs:15-29) and already tests the empty-value behavior; duplicating it here lets the color semantics drift from the workspace funnel.
fn non_empty(name: &str) -> bool {
    env::var_os(name).is_some_and(|v| !v.is_empty())

crates/mergify-cli/src/main.rs:729

  • This changes user-visible tracing output, but no test covers the new stderr ANSI policy. The existing tests only exercise color-choice folding, so a regression that makes --color never or NO_COLOR emit escape sequences in -vv logs would go unnoticed; add focused coverage for ansi_on_stderr or an actual captured verbose run.
        .with_ansi(ansi_on_stderr(color))

crates/mergify-cli/src/main.rs:694

  • This changes the documented environment contract: README.md:216 still says that NO_COLOR disables colored output, but an exported empty NO_COLOR now intentionally leaves color enabled. Please update the README to document the non-empty requirement so workflow users are not misled.
    // Exported-but-empty is not set: a workflow writing
    // `NO_COLOR: ${{ inputs.no_color }}` with no input exports the
    // empty string, and <https://no-color.org> says that does not
    // count. Same rule as every other variable this CLI reads.

crates/mergify-stack/src/progress.rs:114

  • This now describes the recorded --color choice as controlling Progress, but Progress::new still uses Theme::new(false) whenever the output is non-interactive (including --color always) and only calls Theme::detect on the interactive branch. Please document that exception here, or change the implementation if --color always is intended to force color for streamed progress too.
    /// read. Color is delegated to [`Theme::detect`] (the recorded
    /// `--color` choice, else the TTY; off outside the CLI entry
    /// point, so tests are never colored).
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/mergify-cli/src/main.rs Outdated
@mergify
mergify Bot requested a review from a team September 18, 2026 07:17
`Theme::detect()` read `NO_COLOR`, `FORCE_COLOR` and `CLICOLOR_FORCE`
on every call, and it is called from production paths in
`mergify-freeze`, `mergify-queue`, `mergify-events` and
`mergify-stack`. So any test in any of those crates read the process
environment, on whatever thread it happened to run on, just by
rendering a themed line. That is the read half of the data race this
stack is about: the tests in `mergify-ci`, `mergify-core`,
`mergify-cli` and `mergify-stack` mutate the process environment
while these read it.

Those three reads could never change the answer inside a test binary
anyway. `resolve_enabled` returns `false` for a `None` choice
whatever they say, and only `mergify-cli`'s entry point ever records
a choice, which the doc comment on `set_color_choice` already said. A
test harness read three environment variables to compute a value it
had already decided.

`mergify-tui` now takes the resolved choice and never reads the
environment. `--color always|never` still wins, `NO_COLOR` still
beats `FORCE_COLOR` / `CLICOLOR_FORCE`, and `auto` with neither still
defers to the TTY. The precedence moves to `resolve_color_choice` in
the binary, next to the clap enum it maps from, which is also where
the workspace's environment funnel lives. `mergify-tui` cannot use
that funnel: it is `mergify-core`, and this crate deliberately stays
dependency-light.

Two behaviour fixes come with it, both in the same three variables:

Exported-but-empty no longer counts as set. A workflow writing
`NO_COLOR: ${{ inputs.no_color }}` with no input exports the empty
string, and every `mergify` in that job lost color with no way back
but `--color always`. <https://no-color.org> says "present and not an
empty string"; so does every other variable this CLI reads.

The log stream obeys the same decision. `init_tracing` was choosing
ANSI from `stderr().is_terminal()` alone, so `NO_COLOR=1` and
`--color never` gave plain stdout and a `-vv` stream still full of
escape sequences, which is the one output people redirect to a file.

The remaining visible difference is *when* the variables are read:
once at startup instead of at every `Theme::detect()`. Nothing
mutates the environment mid-run, so the answer is the same either
way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Change-Id: Ib099b7044861960ca2c9e85f443072914b333c20
@sileht
sileht force-pushed the devs/sileht/test-env-access-ub/resolve-color-env-overrides-cli-entry-point--b099b704 branch from 1838eec to 092133b Compare September 18, 2026 07:38
@sileht

sileht commented Sep 18, 2026

Copy link
Copy Markdown
Member Author

Revision history

# Type Changes Reason Date
1 initial 1838eec 2026-09-18 07:38 UTC
2 content 1838eec → 092133b 2026-09-18 07:38 UTC

@sileht
sileht deployed to func-tests-live September 18, 2026 07:39 — with GitHub Actions Active
@mergify
mergify Bot deployed to Mergify Merge Protections September 18, 2026 07:39 Active
@sileht
sileht marked this pull request as ready for review September 18, 2026 08:17
@mergify
mergify Bot requested a review from a team September 18, 2026 09:11
@mergify

mergify Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

This pull request spent 9 minutes 28 seconds in the queue, including 8 minutes 55 seconds running CI.

Required conditions to merge

@mergify mergify Bot added the queued label Sep 18, 2026
@mergify
mergify Bot merged commit d963e8b into main Sep 18, 2026
22 checks passed
@mergify
mergify Bot deleted the devs/sileht/test-env-access-ub/resolve-color-env-overrides-cli-entry-point--b099b704 branch September 18, 2026 09:45
@mergify mergify Bot removed the queued label Sep 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants