Skip to content

fix: suppress ANSI escape codes in deno fmt/lint subprocess output#865

Merged
stack72 merged 1 commit intomainfrom
fix/no-color-ansi-subprocess
Mar 25, 2026
Merged

fix: suppress ANSI escape codes in deno fmt/lint subprocess output#865
stack72 merged 1 commit intomainfrom
fix/no-color-ansi-subprocess

Conversation

@stack72
Copy link
Contributor

@stack72 stack72 commented Mar 25, 2026

Summary

Fixes raw ANSI escape sequences leaking into log output when swamp extension fmt runs deno fmt and deno lint as subprocesses.

  • Root cause: Deno.Command inherits the parent TTY environment, causing deno to emit color codes even when stdout/stderr are piped and captured as strings.
  • Fix: Add env: { ...Deno.env.toObject(), NO_COLOR: "1" } to all four subprocess invocations that capture deno fmt/lint output, suppressing ANSI codes while preserving PATH and other required environment variables.

Affected locations:

File Function Invocation
src/domain/extensions/extension_quality_checker.ts checkExtensionQuality deno fmt --check
src/domain/extensions/extension_quality_checker.ts checkExtensionQuality deno lint
src/libswamp/extensions/fmt.ts createExtensionFmtDeps deno fmt (fix mode)
src/libswamp/extensions/fmt.ts createExtensionFmtDeps deno lint --fix

User impact: Extension authors running swamp extension fmt on a system with a color-capable terminal will now see clean, readable error messages instead of output like \x1b[1m\x1b[31merror\x1b[0m.

Test plan

  • Two new unit tests in extension_quality_checker_test.ts assert that fmt and lint issue output contains no ANSI escape sequences (\x1b[)
  • All 28 existing extension_quality_checker_test.ts tests pass
  • Full test suite: 3549 passed, 3 failed (all 3 failures are pre-existing network connectivity tests hitting swamp.club, unrelated to this change)
  • deno check clean
  • deno lint clean
  • deno fmt clean

Fixes #862

🤖 Generated with Claude Code

When swamp invokes deno fmt and deno lint as subprocesses with piped
stdout/stderr, deno still emits ANSI color codes unless NO_COLOR=1 is
set. This causes raw escape sequences to appear in log output, making
it hard to read and breaking structured logging.

Add `env: { ...Deno.env.toObject(), NO_COLOR: '1' }` to all four
Deno.Command invocations that capture deno fmt/lint output:
- checkExtensionQuality() fmt check (extension_quality_checker.ts)
- checkExtensionQuality() lint check (extension_quality_checker.ts)
- createExtensionFmtDeps() runFmt (fmt.ts)
- createExtensionFmtDeps() runLint (fmt.ts)

Spreads the parent environment so PATH and other required vars are
preserved; only NO_COLOR is overridden.

Also adds two unit tests to assert that fmt and lint issue output
contains no ANSI escape sequences.

Fixes #862
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

CLI UX Review

Blocking

None.

Suggestions

None.

Verdict

PASS — This PR fixes a UX regression where raw ANSI escape codes (e.g. \x1b[1m\x1b[31merror\x1b[0m) were leaking into log-mode output when swamp extension fmt ran deno fmt/deno lint subprocesses on a color-capable terminal. The fix is minimal and targeted: adding NO_COLOR: "1" to the subprocess env in the four affected invocations. No flags, help text, JSON output shape, or command structure changed. Two new tests directly assert the fix by checking that captured output contains no \x1b[ sequences.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review

Clean, well-scoped bug fix. The approach of setting NO_COLOR=1 in the subprocess environment is the standard way to suppress ANSI codes and correctly preserves the rest of the environment via Deno.env.toObject().

Blocking Issues

None.

Suggestions

  1. Test assertions style (extension_quality_checker_test.ts:337,353): Consider using assert(fmtIssue !== undefined) or assertExists(fmtIssue) from @std/assert instead of assertEquals(fmtIssue !== undefined, true) — it reads more naturally and gives a better failure message. Non-blocking since existing tests in this file use the same pattern.

DDD review: Changes are appropriately scoped to infrastructure concerns (subprocess environment configuration). No domain model impact.

All CLAUDE.md requirements satisfied:

  • ✓ License headers present on all modified files
  • ✓ No any types introduced
  • ✓ Named exports used
  • ✓ Unit tests next to source files
  • ✓ No libswamp import boundary violations
  • ✓ Minimal blast radius — only touches the four relevant subprocess invocations

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Adversarial Review

Medium

  1. Test fragility in CI — lint ANSI test may pass vacuously (extension_quality_checker_test.ts:344-356): The lint test uses "// deno-lint-ignore no-explicit-any\nexport const x = 1;\n" to trigger a lint error (unused ignore directive via ban-unused-ignore). This relies on ban-unused-ignore being enabled by default in Deno's linter. If a future Deno version changes this default, the test would fail to produce a lint issue and the assertEquals(lintIssue !== undefined, true) assertion would fail — but at least it would fail loudly rather than silently pass. Acceptable risk.

  2. Deno.env.toObject() snapshots env at call time (extension_quality_checker.ts:267,284, fmt.ts:79,92): Each invocation copies the full process environment. This is fine for correctness (and no worse than the previous implicit inheritance behavior), but worth noting that any env var present in the parent process — including potentially sensitive ones — is explicitly forwarded. Since Deno.Command inherits the parent environment by default when env is not specified, this is actually equivalent to the prior behavior plus NO_COLOR, so no new exposure.

Low

  1. bundle.ts subprocess not updated (src/domain/models/bundle.ts:331): This file also spawns denoPath with piped stdout/stderr and captures output for error messages, but was not updated with NO_COLOR. However, it already calls stripAnsiCode() at line 347 as a post-hoc cleanup, so it's handled — just via a different strategy. Noting for completeness; no action needed.

Verdict

PASS — Clean, minimal fix. The NO_COLOR environment variable is the standard convention (no-color.org), the spread preserves the existing env, and the four call sites are all correctly patched. Tests are reasonable. No critical or high issues found.

@stack72 stack72 merged commit bdad9b1 into main Mar 25, 2026
10 checks passed
@stack72 stack72 deleted the fix/no-color-ansi-subprocess branch March 25, 2026 17:28
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.

extension fmt: colorized ANSI escape codes in log output from deno fmt/lint subprocesses

1 participant