Skip to content

[Dream Cycle 2026-09-09] developer-experience: verdictColor() leaks ANSI color through --no-color + cli,tui scan #102

Description

@ruvnet

Rotation

DEEP=developer-experience · SCAN=cli,tui · SLOT=4 (20260909 % 5 == 4). No bonus deep dive (20260909 % 25 == 9, % 75 == 34).

Ledger Check

docs/dream-cycle/LEDGER.md on main (39 rows) inspected. GitHub-authenticated (get_me → ruvnet) — re-checked the last 7 rows' PR fates directly via pull_request_read. Found: the GitHub MCP list_pull_requests tool reports merged: false for every PR in this repo, including ones with merged_at set and independently confirmed merged: true via pull_request_read (get) on #21, #29, #33, #46, #55, #91 — the same defect class PR #89 already fixed inside this repo's own learningSignals(). Recomputed ledger signals --merged <confirmed list>: zeroMergeStreak=false (a large batch-merge landed 2026-09-07, ~40 PRs). Not actionable as a code change here (the defect is in the calling GitHub MCP server, not this repo) — recorded for cross-night memory; see report's Next Steps §3.

Deep Dive

packages/cli/src/tui.ts's renderDashboard(ledgerMd, { noColor: true }) still leaks a raw ANSI SGR color escape in the verdict column. PR #21 (2026-08-19, merged) explicitly flagged this exact defect as a known, unfixed follow-up ("verdictColor() bypasses the no-color proxy, leaking a raw ANSI escape into --no-color output"). Reproduced live tonight against main @ 3edd426 before writing any fix:

$ node -e "renderDashboard(md, { noColor: true })"
contains ANSI escape with noColor:true -> true
"est                 \x1b[32mACCEPT         "

Root cause: verdictColor(v: string): string closed over the module-level, always-colored C constant directly, instead of the noColor-aware c (either C itself, or a Proxy mapping every key to '') that renderDashboard already resolves and threads through every other cell in the same row.

Hypothesis

Given renderDashboard(ledgerMd, { noColor: true }), when verdictColor() is changed to accept and use the resolved no-color-aware color map (c) instead of the raw C constant, then noColor: true output should contain zero ANSI escapes for any verdict value, while noColor: false output stays byte-identical to today. Confirmed.

Evaluation Receipt

Real evaluator: npm test (vitest run && npm run test:governance).

Baseline (3edd426) Candidate
vitest 616 618 (+2, 0 removed/modified)
governance 81 81
Total 697 699
Lint clean clean
Build clean (7 pkgs) clean (7 pkgs)

Live: pre-fix leaks \x1b[32m in noColor:true output; post-fix does not; colored (noColor:false) output unchanged (\x1b[32m/\x1b[31m still present). Self-hosted dream.config.json compile output byte-identical before/after (out of scope, verified anyway).

Darwin Results

Not run — DARWIN=not-applicable. Single deterministic color-dispatch parameterization, no evolvable population (same judgment as PRs #11/#21/#29/#33/#46).

Evidence

OBSERVATION (grade A, live, pre-fix code read + reproduction) → OBSERVATION (grade A, PR #21's own body names this exact defect, "not fixed") → MEASUREMENT (grade A, live noColor leak before/after) → MEASUREMENT (grade A, 697→699 tests) → DECISION (parameter-thread c rather than add a second no-color check inside verdictColor).

Reward-Hack Check

Independent critic (fresh subagent, no shared authoring context): CLEAR. Confirmed the bug is real by reading pre-fix code and reproducing the leak first-hand (not on faith); confirmed the fix fully closes it (grepped for any remaining direct C.xxx reference in renderDashboard or its helpers — zero); no weakened/skipped tests, no gold-data edits, no threshold changes; verified by hand both new tests are bug-exercising (the noColor test fails pre-fix, passes post-fix; the colored-path test passes on both, correctly proving no regression); no regression to colored output (c === C when noColor is false); scope minimal (4-line function + 1-line call site + 2 additive tests).

Security Review

No security-sensitive surface: no prompt-injection vector (no LLM calls in this path), no MCP/tool-authority change, no credential exposure, no filesystem/network scope change, no evaluator/gate/safety-constant touched. Pure ANSI-escape string-formatting logic.

Scan Findings

  • cli: the GitHub MCP list_pull_requests tool's merged field is unreliable in this environment (always false); see Ledger Check above. Not a dream-machine code defect.
  • tui: no other direct C.xxx reference bypassing the c no-color proxy remains in renderDashboard or its helpers (confirmed by grep, both by this session and independently by the critic).

Competitors

Sakana AI Scientist (plain-text CLI output, structurally can't leak — grade C, general repo inspection); OpenHands/rich-based TUIs (same bypass-a-plain-mode-wrapper footgun is a recognized recurring class across rich/ANSI-wrapper tools generally — grade C); DSPy/GEPA (keeps machine-readable logs entirely separate from any colorized renderer, sidestepping the bug class — grade B, documented format); SWE-agent (routes color through a single per-line wrapper, reducing but per public issue history not eliminating the same risk — grade C). Full rows with grades in the committed report.

Gist

GIST=LOCAL — no gh CLI or gist-creation MCP tool available this session (consistent with every prior night). Full report committed at docs/dream-cycle/2026-09-09-developer-experience-report.md.

Witness

report_sha256 : 69a97031631f19d559ebc5c0d7601d36cb02efa7708fda9776eb1616e7372ebe
session_commit: 3edd426f6c9c4b1e80235f7447dc863e749345cc
witness       : 16c867ef6fc58d714ac698888e99b25e24e6f39a21e45bf4fcc7e03098b23ee0

Recommendation

evaluated: accepted — sufficient evidence to recommend human review. PR to follow, draft, human-review-only per this repo's merge policy.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions