Skip to content

fix(review): keep git and jj output color-free under color.diff=always (#1661) - #1662

Merged
backnotprop merged 2 commits into
mainfrom
fix/1661-git-color-free
Oct 1, 2026
Merged

backnotprop merged 2 commits into
mainfrom
fix/1661-git-color-free

Conversation

@backnotprop

Copy link
Copy Markdown
Owner

Fixes #1661. Thanks to @sholsinger for the report and for tracing it to the colorized /api/diff payload.

Root cause

Plannotator ran git with no color override. With color.diff = always in the user's git config, every piped diff line came back wrapped in ANSI escapes. The parser matches ^diff --git , so it found 0 files, and viewed-file identity capture (--raw --patch) found 0 identities. -c color.ui=never alone does not fix it: the specific color.diff key wins over color.ui (checked on git 2.50). jj has the same problem with ui.color = "always".

Fix

  • git, both runtimes: prepareGitCommand (the one runner every review git call uses, Bun and Pi) now prepends --no-pager -c color.ui=never -c color.diff=never -c color.status=never -c color.branch=never -c color.grep=never -c color.showBranch=never -c color.interactive=never. This covers the review diff, file identities, since-base sections, the Commits panel, generated files, hunk expansion, image previews, GitButler's git calls, and PR worktrees.
  • jj, both runtimes: runJj / runJjBytes add --color=never --no-pager (JJ_COLOR_FREE_ARGS).
  • gh pr diff: --color=never, so CLICOLOR_FORCE / GH_FORCE_TTY cannot color it.
  • Agent jobs (review, tour, guide; Bun and Pi): the spawned agent gets the same git settings as GIT_CONFIG_* variables, so the git diff it runs itself stays plain.
  • Parser backstop: parseDiffToFiles strips ANSI codes only when the patch's own diff --git headers are colored (for example, a colorized --patch-file). A normal patch, including one whose content contains escape bytes, is returned byte for byte, so patch bytes and draft keys do not change.

Checked and left alone: ripgrep code-nav uses --json, which is never colored, even with --color=always in RIPGREP_CONFIG_PATH. The annotate file browser runs only status --porcelain and diff --numstat, which git never colors. GitButler's but calls use JSON output. P4 has no color.

Evidence

The real plannotator review server ran on a temp repo with a sandboxed GIT_CONFIG_GLOBAL (color.ui/diff/status/branch=always, diff.noprefix, core.pager, pager.diff=true):

before after
ESC in rawPatch yes no
files parsed 0 a.txt +1/-1, b.txt +1/-0, c.txt +1/-0, d.txt +1/-0
review-progress fingerprints {} 4
/api/commits ok ok
/api/file-content ok ok
jj (ui.color="always") files 0 b.txt, d.txt

Tests

  • packages/server/review-git-color-config.test.ts: runs both servers (Bun and Pi) against a sandboxed hostile config, using a temp GIT_CONFIG_GLOBAL / JJ_CONFIG, so the real config is never touched. It checks the files and their +/- counts, identities, since-base sections, Commits history, and file expansion. It also checks the jj diff in both runtimes (skipped without jj) and the agent-job environment. A guard test asserts that the sandbox really colors a piped git diff. With the fix reverted, the Bun/Pi git tests and both jj tests fail.
  • packages/core/diff-files.test.ts: a colorized patch parses, and a plain patch with escape bytes in its content is unchanged.
  • review-core.test.ts: the argv expectations now include the new prefix.
  • bun run typecheck is clean. The full bun test run passes, except 2 entry-asset tests that read apps/hook/dist, which was not built locally.

#1661)

A user config with color.diff = always (or jj ui.color = "always") colored
every piped diff, so the patch parser found 0 files and viewed-file identity
capture found 0 identities. color.ui=never alone does not override an explicit
color.diff, so prepareGitCommand now sets each color key to never plus
--no-pager, in both runtimes. jj runtimes add --color=never --no-pager,
gh pr diff gets --color=never, agent jobs inherit the same git settings via
GIT_CONFIG_*, and parseDiffToFiles strips ANSI codes from a patch whose
headers are colored as a backstop.

Reported by @sholsinger.
@backnotprop
backnotprop merged commit 898268b into main Oct 1, 2026
28 checks passed
@backnotprop
backnotprop deleted the fix/1661-git-color-free branch October 1, 2026 23:51
backnotprop added a commit that referenced this pull request Oct 2, 2026
CallDiff runs git itself and inherited process.env without the color-free
GIT_CONFIG_* entries, so a user's color.diff = always could still break Call
Flow analysis after #1662. Both worker spawns now carry gitColorFreeEnvironment.
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.

review shows 0 files when git color.diff=always: ANSI codes in the diff are silently dropped by the parser

1 participant