Skip to content

Compress command output

Build logs, test runs, and compiler diagnostics are the other half of agent context bloat. ctxctl exec runs a command and returns only the lines that matter: key error/warning lines with their file:line locations, plus a head/tail summary, with everything else collapsed to one marker line.

With no flags, exec keeps lines matching error, warning, failed, panic, fatal (case-insensitive), keeps diagnostic location lines (--> src/foo.rs:12:34, rustc/cargo style) so kept errors retain context, and shows 5 head + 5 tail lines:

Terminal window
ctxctl exec "cargo build"
$ cargo build
error[E0308]: mismatched types --> src/main.rs:12
... [34 lines omitted]
warning: unused variable: `x` --> src/server.rs:88
= note: 2 warnings emitted
Saved ~70% (1,240 -> 372 tokens)

Collapsing only kicks in past 20 output lines (the collapse threshold), so short commands pass through untouched. The wrapped command’s exit code passes through — 0 means success regardless of compression; on Unix, a signal-killed child yields exit code 128 + signal.

--keep appends one extra regex (rg syntax) to the configured defaults — it does not replace them:

Terminal window
# Test verdicts alongside errors
ctxctl exec "cargo test" --keep 'FAILED|passed'
# Go builds: surface package-level failures
ctxctl exec "go build ./..." --keep 'cannot|undefined'

Pick patterns that match verdict lines, not noise:

  • Good: status words (FAILED, passed), your framework’s failure tags (E0308), assertion markers (AssertionError).
  • Risky: generic words like test, run, or file names — they match most lines and defeat compression (see the warning below).

The head preserves the command echo; the tail carries the final summary (“N tests passed”, “N warnings emitted”). Tune both per call:

Terminal window
ctxctl exec "cargo test" --head 10 --tail 15

Or change the defaults project-wide in .ctxctl/config.toml:

[exec]
keep = ["error", "warning", "failed", "panic", "fatal"]
head_lines = 5
tail_lines = 5
collapse_threshold = 20

Set collapse_threshold = 0 to always collapse when there is anything to fold; an empty keep list (keep = []) disables mid-output retention entirely instead of keeping every line — location-line keeps stay active either way.

If folding ran but saved at most 10% — the signature of a --keep pattern that matches nearly every line — ctxctl appends a deterministic warning: line at the end of the text output. The output stays byte-stable; the warning is just part of the deterministic result. Treat it as a hint to tighten the pattern, not an error. In JSON mode (--json) it appears as a top-level warning field instead.

exec splits commands shell-word style and runs them directly — there is no interactive shell, so pipes, redirections, globs, and && chains do not work as-is. Wrap compound commands in an explicit shell:

Terminal window
# Wrong: pipe would be split as plain arguments
ctxctl exec "cargo build 2>&1 | tee build.log"
# Right: hand the whole string to a shell
ctxctl exec 'sh -c "cargo build 2>&1 | tee build.log"'

This is the single most common first-day stumble — agents in particular tend to assume a full shell is present. If output looks like flag soup, this is why.

Log triage is where exec shines because the input is unbounded but the interesting content is tiny. A scripted-agent benchmark task analyzing a 1,914-line deterministic build log landed at −84% task cost versus raw reads:

Terminal window
# Local: compress any command's output on the spot
ctxctl exec "npm run build" --keep 'error|warn'
# CI: re-run or replay the failing step through a shell so only key lines surface
ctxctl exec "sh -c './ci.sh 2>&1 | tee ci.log'" --keep 'FAIL|error'
ctxctl exec "sh -c 'grep -nE \"(error|failed)\" ci.log'"

The simplest CI pattern is to run the failing step under exec and let the compressed transcript go straight into the issue report or the agent’s context. Compression streams incrementally with bounded memory, so gigabyte-scale logs are safe.

When orchestrating from scripts or pipelines, add --json (alias of --format json) for the machine contract: a fixed envelope and a stable schema version. In text mode errors go to stderr, but in JSON mode the error envelope — {"error": {"code": <exit-code>, "message": "<text>"}} — prints to stdout, so machine consumers always get exactly one parseable stream. Exit-code semantics are unchanged: exec passes through the wrapped command’s exit code.

For reading source files with the same discipline, see Explore unfamiliar code; for exposing these commands to agents as tools, see Wire up coding agents. The full flag list lives in Commands.