Repository navigation
fix(render): emit colour only on a TTY, honour NO_COLOR; README: -C operates on the named directory - #197
Merged
Conversation
…perates on the named directory Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159tcanCgA3jxi2E7DdRVWJ
This was referenced Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Problem
codemapemitted ANSI colour escapes unconditionally. There was no isatty check anywhere andNO_COLOR/TERM=dumbwere ignored. Measured on this repo atorigin/main(e8e7e73) with stdout piped:codemap . | wc -ccodemap hook session-start(stdin{"session_id":"t","cwd":"<repo>"})Hooks always run with stdout piped into an agent prompt, so every session paid roughly 1,500 tokens of pure escape codes.
Fix
render/colors.go: the colour names (Reset,Bold,Cyan, ...) are now package variables backed by a constant palette. Every renderer keeps concatenating them unconditionally; the variables are the escape sequence while colour is on and""while it is off.skyline'sbuildingColorsis derived from the same switch.render/color_mode.go(new):ColorAuto | ColorAlways | ColorNever,SetColorMode,ParseColorMode,ColorEnabled. Auto resolves at package init and again when the flag is applied: colour only ifgolang.org/x/term.IsTerminal(stdout)andNO_COLORis unset (https://no-color.org) andTERM != dumb.golang.org/x/termwas already a direct dependency;go mod tidyis a no-op.cmd/root.go:--color=auto|always|never(also--color <mode>; bare--colormeansalways, likels/grep) is parsed with the other global options, so it works before any subcommand.-C --color=neveris rejected like the other missing-path cases.main.go: applies the mode right after global options; thehookdispatcher forcesColorNever(hook stdout is never a terminal). Hooks exec the binary into a pipe, so auto already yields no colour there; the force is belt-and-braces.--jsonis untouched and byte-identical (cmpof before/after output on this repo: identical; e2e test asserts auto/always/never produce the same JSON).--skyline --animatekeeps colour on a terminal and goes monochrome under--color=never/piped.Before / after
--color=alwayscodemap .codemap hook session-startcodemap --json .New or changed printed lines (docs/OUTPUT-STANDARD.md)
--color <mode> Colour output: auto (default: only on a terminal, off when NO_COLOR is set), always, neverSourced:
codemap --help. Precise: names the three modes and the two conditions. Bounded: states the default and when auto is off. Useful: tells the human which flag keeps colour underless -R.--color: unknown mode "sometimes" (want auto, always or never)(exit 2)Sourced: the rejected argument is echoed. Precise: lists the accepted values. Bounded: nothing else fails. Useful: names what to type instead.
`--color <mode>` | `auto` (default): ANSI colour only when stdout is a terminal and `NO_COLOR` is unset; `always` keeps colour when piping into `less -R`; `never` turns it off-Cwording (two lines):`-C`/`--project-root` operates on exactly the named directory, like `git -C`; it may be a subdirectory of a repository, and storage and config discovery still walk up to the git root.and the table row`-C, --project-root <dir>` | Operate on exactly `<dir>` (may be a subdirectory of a repository; storage and config discovery still walk up to the git root). Sourced: fix(cmd): -C operates on the given directory, not the Git root #194. Precise and bounded: says what-Cdoes and the one thing that still walks up.No other printed line changed; tree, skyline and hook text is identical minus the escapes.
Tests
render/color_mode_test.go(new): piped tree (stdoutIsTerminalstubbed false, auto) contains no\x1b[;ColorAlwaysrestores\x1b[1;34m src/app/\x1b[0mand the bold project name even withNO_COLOR=1;ColorNeveron a terminal is quiet; static skyline honours the mode andbuildingColorsis blanked/restored;autoColorEnabledtable (tty, piped,NO_COLOR=1,NO_COLOR=set-but-empty,TERM=dumb);ParseColorModeround trip and rejects.render/colors_test.go:TestGetFileColor*andTestANSIConstantsnow forceColorAlwaysfor the test (with cleanup) so they keep asserting the real palette instead of becoming vacuous undergo test's piped stdout.cmd/root_test.go:--color=never,--color always, bare--color,--color=autoafter a subcommand, no flag,--color=sometimesrejected,-C --color=neverrejected,--coloralone does not activate root options.color_e2e_test.go(new, runs the built binary): pipedcodemap .has no escapes;--color=alwayswithNO_COLOR=1has them;--color=never,--color never,NO_COLOR=1,TERM=dumbare quiet;--jsonidentical across modes; unknown mode exits non-zero with the error above.gofmt -l .,go vet ./...,staticcheck ./...: clean.go mod tidy: no change.go test ./...: all green except the three pre-existing root-only failures (TestRunSetupCreatesConfigAndHooks,TestSelectReportsInaccessiblePrimarySetup,TestListProjectsPreservesPerChildScanErrors).watch.TestEventDetectionflaked once on a full run and passed 3/3 on rerun; this PR touches nothing underwatch/.Fixes part 1 of #154
Type of change
Checklist
go build && ./codemap .Additional notes
Rebased onto #194 (393b048) before pushing; the README
-Clines were still the pre-#194 wording on main.🤖 Generated with Claude Code
https://claude.ai/code/session_0159tcanCgA3jxi2E7DdRVWJ
Generated by Claude Code