cat (via rtk) emits ANSI color codes to non-TTY stdout, corrupting command substitution
#1409 opened on Apr 20, 2026
Description
Summary
When cat is invoked through rtk's command-substitution path (e.g. $(cat <<'EOF' ... EOF)), its output is wrapped with ANSI color escape codes (\e[38;5;231m...\e[0m). When that substituted value is passed as data to another command (typical case: gh pr create --body "$(cat <<EOF...EOF)"), the escape codes end up embedded verbatim in the downstream payload — in my case the literal text of a GitHub PR body.
Reproduction
$ rtk --version
rtk 0.36.0
$ echo 'test' | cat | od -c | head -2
0000000 033 [ 3 8 ; 5 ; 2 3 1 m t e s t 033
0000020 [ 0 m \n
$ echo 'test' | rtk proxy cat | od -c | head -2
0000000 t e s t \n
0000005
# Real-world impact:
$ gh pr create --title "..." --body "$(cat <<'EOF'
## Summary
- foo
EOF
)"
# -> GitHub PR body literally renders: [38;5;231m## Summary[0m
The rtk-wrapped cat colorizes its output even when stdout is a pipe (not a TTY).
Impact
This silently corrupts any command pattern of the form some_cmd --arg "$(cat <<EOF...EOF)". It's a common pattern documented in many CLI tools, Claude Code's own prompts, AI agent workflows, and shell scripts. The corruption is invisible in the terminal (Claude Code renders markdown) but surfaces on GitHub, in commit messages piped this way, in webhook payloads, etc.
Expected Behavior
rtk should not emit ANSI color codes when stdout is not a TTY. Standard Unix convention (and what most color libraries honor via NO_COLOR, isatty, etc.) is "only colorize when attached to a terminal".
Suggested Fixes (in order of preference)
- Detect non-TTY stdout and skip colorization automatically. This is the standard fix and what tools like
ls,grep,git,rgall do. - Honor
NO_COLOR=1/CLICOLOR=0environment variables as an escape hatch. - Document the behavior prominently in the README and recommend
rtk proxy <cmd>or--body-file -patterns for scripts/automations.
(1) alone would fix this without any user action needed.
Workarounds I'm using today
rtk proxy cat <<'EOF' ... EOF— bypasses the wrapper.gh pr create --body-file /tmp/body.md— writes body to a file first.gh pr create --body-file - <<'EOF' ... EOF— stdin from heredoc, nocatinvolvement.
Happy to contribute a PR if a maintainer can confirm the approach. Thanks for rtk — it's a great tool!