Skip to content

fix: avoid os.environ mutation for color control - #1125

Merged
noahnu merged 1 commit into
syrupy-project:mainfrom
frenck:frenck/fix-955-color-contextvar
Jun 26, 2026
Merged

fix: avoid os.environ mutation for color control#1125
noahnu merged 1 commit into
syrupy-project:mainfrom
frenck:frenck/fix-955-color-contextvar

Conversation

@frenck

@frenck frenck commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Description

syrupy signaled "disable color" by temporarily mutating os.environ (through the env_context helper). Mutating the environment calls glibc setenv, which is not thread safe and races with getenv called from native code in other threads. During the pytest_assertrepr_compare and diff_snapshots paths this could segfault unrelated code, for example a database driver running in a pytest-django live_server request handler, as reported in #955.

This replaces the environment mutation with a contextvars.ContextVar. terminal._is_color_disabled() now consults that ContextVar in addition to the external NO_COLOR and ANSI_COLORS_DISABLED variables, which are still read (reads are safe, only writes race). Both call sites, __terminal_color (when --snapshot-no-colors is set) and SnapshotReporter.diff_snapshots, use the new disable_color() context manager, so no os.environ writes happen anywhere in the path.

The now-unused env_context helper is removed from syrupy.utils. It was undocumented and not part of __all__, and was the source of the race. Its only remaining user was the osenv test fixture, which now defines the helper locally in conftest.py (setting a real environment variable inside a single threaded test is fine).

#956 narrowed when the mutation happened; this removes it entirely.

Related Issues

Checklist

  • This PR has sufficient documentation.
  • This PR has sufficient test coverage.
  • This PR title satisfies semantic convention.

Additional Comments

New tests in tests/syrupy/test_terminal.py cover the toggle behavior, that the external NO_COLOR variable still disables color, and crucially that disable_color() does not touch os.environ. Behavior for users is unchanged: --snapshot-no-colors, external NO_COLOR/ANSI_COLORS_DISABLED, and the diff color suppression all work as before.

syrupy signaled "disable color" by temporarily mutating os.environ via
env_context. Mutating the environment calls glibc setenv, which is not
thread safe and races with getenv called from native code in other
threads, and could segfault unrelated code (for example a database
driver in a pytest-django live_server request handler) during the
pytest_assertrepr_compare and diff_snapshots paths.

Signal color suppression through a contextvars.ContextVar instead.
terminal._is_color_disabled() now consults that ContextVar in addition to
the external NO_COLOR and ANSI_COLORS_DISABLED variables, which are still
read (reads are safe, only writes race). Both call sites use the new
disable_color() context manager, so no os.environ writes happen.

The now-unused env_context helper is removed from syrupy.utils; its only
remaining user was the osenv test fixture, which now defines it locally.

Closes syrupy-project#955
@codecov

codecov Bot commented Jun 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1125   +/-   ##
=======================================
  Coverage   97.43%   97.43%           
=======================================
  Files          24       24           
  Lines        1754     1754           
=======================================
  Hits         1709     1709           
  Misses         45       45           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@noahnu
noahnu merged commit 7db49e9 into syrupy-project:main Jun 26, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pytest_assertrepr_compare unnecessarily calls os.environ.update every time - increased segfault risk due to getenv not thread safe

2 participants