[6.x] Quiet CLI output when running under an AI agent#11812
[6.x] Quiet CLI output when running under an AI agent#11812
Conversation
af92e18 to
d0dfc6f
Compare
d0dfc6f to
f74c310
Compare
| // which emits one line per phase transition. | ||
| $quiet_progress = $in_ci || !CliUtils::streamIsInteractive(STDERR); | ||
| if (isset($options['long-progress']) || $quiet_progress) { | ||
| $progress = new LongProgress($show_errors, $show_info, $quiet_progress); |
There was a problem hiding this comment.
CI progress silently switches from DefaultProgress to LongProgress
Low Severity
The $quiet_progress variable conflates two concerns: choosing the progress class and setting its constructor parameter. When $in_ci is true, $quiet_progress becomes true, causing the condition isset($options['long-progress']) || $quiet_progress to select LongProgress instead of the previous DefaultProgress. For projects with >1500 files in CI (without explicit --long-progress), the analysis output changes from DefaultProgress's \r-based progress bar to LongProgress's ░/E dot output. The PR behavior matrix states "CI: LongProgress (unchanged)" but the old default was DefaultProgress.
Reviewed by Cursor Bugbot for commit f74c310. Configure here.
There was a problem hiding this comment.
False positive. Old 6.x behavior in CI was already LongProgress, not DefaultProgress — see Psalm.php:280-282 on 6.x which sets $options["long-progress"] = true unconditionally when $in_ci, so initProgress took the LongProgress branch (with in_ci=true, producing the ░/E dot output). My new code preserves that: $quiet_progress = $in_ci || !streamIsInteractive(STDERR) is true in CI, and the combined condition still selects LongProgress($show_errors, $show_info, true) — identical to before. No behavior change for projects >1500 files in CI.
b7cc051 to
18843d1
Compare
Add auto-detection for AI-agent invocations, non-TTY stderr, and the NO_COLOR convention so piped output is no longer dominated by progress tokens the agent will never render. - Under an AI agent: default to VoidProgress and drop ANSI colors. Detected via per-vendor presence markers for Claude Code, Cursor, Gemini CLI, OpenAI Codex, Augment, Cline, OpenCode, Amp, TRAE AI, GitHub Copilot CLI, Google Antigravity, Pi, Replit; the Vercel AI_AGENT vendor-neutral convention; a value-gated AGENT=goose check; and the /opt/.devin sandbox marker used by Devin. - On non-TTY stderr (and not in CI), switch DefaultProgress to the CI-flavored LongProgress so we stop flushing \r-based 'N / M...' scanning updates into log files. - Respect NO_COLOR (https://no-color.org) for the stdout report. Explicit flags (--no-progress, --long-progress, -m) still win. Measured on filament/4.x (clean cache, ~10.4K errors): - Output under an AI agent: 4.20 MB -> 3.94 MB (saved ~260 KB / ~65K tokens), stderr drops from 10.5 KB to 0. - Non-AI piped run: stderr drops from 10.5 KB to 3.1 KB.
18843d1 to
b4c7d74
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
Reviewed by Cursor Bugbot for commit b4c7d74. Configure here.
| : new DefaultProgress(); | ||
| // CI takes precedence over AI detection, matching Psalm.php / Psalter.php. | ||
| $no_progress = isset($options['no-progress']) | ||
| || (!$in_ci && CliUtils::runningUnderAiAgent()); |
There was a problem hiding this comment.
--no-progress not in Refactor's valid options list
Medium Severity
The new code checks isset($options['no-progress']) but 'no-progress' was never added to $valid_long_options in Refactor.php. Since getopt() won't parse an unlisted option, this check is always false — dead code. Worse, if a user actually passes --no-progress as an escape hatch (as the PR intends), the argument validator earlier in the function rejects it as an "Unrecognised argument" and exits with an error. The option string 'no-progress' needs to be added to $valid_long_options for the escape hatch to function.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit b4c7d74. Configure here.


Why
AI coding agents (Claude Code, Cursor, Gemini CLI, Codex, Goose, Amp, etc.) run
psalmand feed the combined output back into a language model. Every token spent on progress bars, ANSI colors, and\r-based scanning updates is wasted budget.Related reading:
What changes
Three conservative defaults that kick in only when the existing flags are not set:
VoidProgressand disable ANSI colors.--long-progressstill wins (escape hatch).CI=trueand an AI marker are set, the run usesLongProgress. Colors still drop because most CI renderers treat ANSI inconsistently and agents reading the CI log still benefit.DefaultProgressto the CI-flavoredLongProgress. Previously, piping stderr to a file captured hundreds of\r-based "N / M..." scanning updates into one giant line. Now the CI path kicks in and we only emit per-phase output plus the compact░/Eanalysis dots.NO_COLOR(https://no-color.org): disables ANSI colors on the stdout report when set to a non-empty value. Convention already respected by git, clang, cargo, etc.No existing flag changes meaning.
-m,--no-progress, and--long-progressall still win when set explicitly.Agent detection
Each agent exports a presence marker into every spawned subprocess. The list mirrors the coverage of
@vercel/detect-agentand shipfastlabs/agent-detector:CLAUDECODE,CLAUDE_CODECURSOR_AGENT,CURSOR_TRACE_IDGEMINI_CLICODEX_SANDBOX,CODEX_THREAD_IDAUGMENT_AGENTCLINE_ACTIVEOPENCODE_CLIENT,OPENCODEAMP_CURRENT_THREAD_IDTRAE_AI_SHELL_IDCOPILOT_CLIANTIGRAVITY_AGENTPI_CODING_AGENTREPL_IDAI_AGENTAGENT=gooseAGENTusage)/opt/.devinUsing
getenv()rather than$_SERVERmatches the dominant pattern in the CLI code and survives strictvariables_ordersettings. Help text speaks of "an AI coding agent" in the abstract rather than naming vendors, so adding the next marker is a one-line change.Measurement
Ran on
filament/4.x(real Laravel codebase, ~4.8K files, ~10.4K errors, cache cleared between runs):\r-based scanning flood.Files
src/Psalm/Internal/CliUtils.php: three static helpers (runningUnderAiAgent,streamIsInteractive,noColorRequested) plus a smallenvVarLooksTruthyinternal.src/Psalm/Internal/Cli/Psalm.php: maininitProgressandinitStdoutReportOptionspick quieter defaults.--helptext updated.src/Psalm/Internal/Cli/Psalter.php: same defaults for--alter, including the piped-stderr fallback.src/Psalm/Internal/Cli/Refactor.php: VoidProgress and color-off under AI agent.tests/EndToEnd/PsalmRunnerTrait.php: the SymfonyProcessconstructor now receives anagentEnvVarsToUnset()map so the E2E harness doesn't silently swap to VoidProgress when an agent-using dev runscomposer phpunit.Behavior matrix
NO_COLOR=<non-empty>--long-progress--no-progressBC note
Non-AI users who pipe stderr to a file will see the cleaner
LongProgressformat instead of the interleaved\r-basedDefaultProgress. The old format was effectively unreadable off-TTY anyway, so this is meant as an improvement, but it is a visible change for anyone reading saved logs.Note
Medium Risk
Moderate risk because it changes default CLI progress/color behavior based on environment detection (AI markers, NO_COLOR, non-TTY stderr), which could surprise users relying on previous log formats.
Overview
Makes Psalm/psalter/refactor CLI output quieter by default when it would otherwise be token/log noise: auto-disables ANSI color when
NO_COLORis set or whenCliUtils::runningUnderAiAgent()detects an AI agent, and auto-disables progress under AI agents unless CI/--long-progressis in effect.Improves non-interactive logging by switching to
LongProgresswhenSTDERRis not a TTY (and in CI), avoiding\r-based progress-bar flooding; help text is updated to document these auto-defaults. AddsCliUtilshelpers (runningUnderAiAgent,streamIsInteractive,noColorRequested) and updates E2E test harness to unset agent-related env vars to keep output deterministic.Reviewed by Cursor Bugbot for commit b4c7d74. Bugbot is set up for automated code reviews on this repo. Configure here.