NAME

mcp-run-compress - Claude Code hooks that run Bash tool output through MCP::Run::Compress

VERSION

version 0.107

SYNOPSIS

# One-shot install (Docker, no Perl needed)
docker run --rm -v "$HOME:$HOME" -e HOME="$HOME" \
    raudssus/mcp-run-compress --install-claude

# One-shot install (native Perl)
cpanm MCP::Run
mcp-run-compress --install-claude

# Bypass compression for a single Bash call
no-compress ls -l

DESCRIPTION

Registers two hooks on Claude Code's Bash tool so its output is passed through MCP::Run::Compress. 30+ command-specific filters strip noise (make "Entering directory"), reduce columns (ls -l to type+name), drop progress lines (cpanm, cargo, npm), and cap long output with head+tail.

PostToolUse

Does the compression, by replacing the recorded tool output through hookSpecificOutput.updatedToolOutput. The command itself is never touched.

PreToolUse

Only what must happen before the command runs: the Co-Authored-By rewrite of git commit, and turning a no-compress prefix into a marker (see "THE no-compress MARKER").

Those two are independent. no-compress and the background check govern compression; they never touch the trailer. The Co-Authored-By rewrite is a commit policy that applies to every command and has exactly one off switch, MCP_RUN_COMPRESS_NO_CO_AUTHORED. In particular a command that names this tool is treated like any other — up to 0.106 a self-bypass on the name stopped the hook from wrapping its own wrapper, but nothing is wrapped any more, so all that match still did was drop the trailer from messages like git commit -m "Fix mcp-run-compress hook".

Both are the same command, mcp-run-compress --hook; the script dispatches on the payload's hook_event_name.

NAME

mcp-run-compress - Claude Code hooks that compress Bash tool output

LIMITS

Commands that fail are not compressed

Claude Code does not fire PostToolUse for a command that exits non-zero. It fires PostToolUseFailure, whose payload carries no tool_response at all and which ignores updatedToolOutput. A failing command's output therefore arrives uncompressed, in full.

This is the one real regression against the pre-0.107 rewrite, which compressed failures too because it worked from inside the execution. Failing builds are the loudest output there is, so the limit is worth knowing rather than discovering. The only thing that event accepts is additionalContext, which adds text instead of replacing it and so cannot save anything.

Background commands are not compressed

The hook fires when a background command starts: tool_response carries backgroundTaskId and noOutputExpected, and both output streams are empty. The real output arrives later over the task file and is not a Bash tool response any more. The hook passes those through untouched rather than gluing a header onto the "Command running in background" notice.

PostToolUse hooks do not chain

Every registered PostToolUse hook is shown the original tool response, not the previous hook's replacement. If more than one returns an updatedToolOutput, the last registered one wins and the others are discarded silently. An unrelated hook on Bash can therefore switch this compression off without any error. --install-claude appends its entry, so whatever is installed after it takes precedence.

Replaced output has a byte ceiling

Claude Code persists a tool output above roughly 30000 bytes to a file and shows the model a 2 KB preview — and it applies that to the replacement as well. A compressor returning 40 KB would therefore deliver 2 KB, worse than no compression at all. Output above the ceiling (MCP_RUN_COMPRESS_MAX_BYTES, default 29000 bytes) is cut head-and-tail with a [mcp-run-compress: … omitted …] line that names the file holding the complete text. The cut is never silent.

When the original was persisted, the hook reads the full file, compresses that, and deletes persistedOutputPath/persistedOutputSize from the replacement — which is what removes the preview framing. Since deleting the path also removes the only pointer to the untouched original, the path is written into the compressed text.

Files above 4 MB are left alone entirely: the harness's own preview plus path is a better answer than pulling that through a regex pipeline inside a hook.

Docker mode cannot read the persisted file

persistedOutputPath is a path on the host. In docker mode the hook compresses the 30000 bytes it was handed, still drops the preview framing, and names the file it could not read. Mounting the file is not offered: it lives in ~/.claude/projects/<project>/<session>/tool-results/, so the only directory known at install time is ~/.claude/projects, which holds every session transcript of every project.

THE no-compress MARKER

PostToolUse is shown the command as PreToolUse rewrote it, never the user's original. A bypass prefix that PreToolUse strips is therefore invisible by the time compression happens, so the wish has to survive inside the command.

no-compress ls -l becomes:

ls -l
# mcp-run-compress: no-compress

A trailing comment line is invisible to bash and leaves the command's first word intact, which keeps Claude Code's permission prefix matching working — a leading comment would not. Measured against 24 command shapes (multiline commands, heredocs including <<- and quoted delimiters, two heredocs on one line, function definitions, if/case/while blocks, subshells, line continuations, background jobs): output and exit code stay byte-identical except in two cases.

a command ending in a dangling backslash

The line continuation would splice the comment onto the last command line. Guarded: the marker is not appended, which costs the bypass for that one call, never the command.

an unterminated heredoc

cat <<EOF with no closing EOF swallows the marker into the heredoc body, so it appears in the output. bash already warns about such a command ("here-document delimited by end-of-file"); not guarded.

OPTIONS

--hook

Hook entry point for both events. Reads the hook invocation JSON on stdin, dispatches on hook_event_name, writes the response JSON on stdout. A payload for any other event is answered with an empty response carrying that event's name.

Fail-open: if the script cannot load its own dependencies it still emits a valid pass-through response and exits 0. A PreToolUse hook that exits non-zero blocks the tool call, so a broken install must never be able to take the user's Bash tool down with it. The same response is emitted when the hook entry carries an option this version no longer knows.

--install-claude

Patch ~/.claude/settings.json to register the hook for PreToolUse and PostToolUse (idempotent), and drop a skill under ~/.claude/skills/bash-output-is-compressed-prefix-no-compress-to-bypass/ that tells Claude about the no-compress bypass prefix. The hook command matches the current install mode. Hooks and skill are installed independently and each is idempotent, so a deleted skill is restored even if the hooks are already registered. The file is only rewritten when something actually changed.

It also migrates an installation from before the PostToolUse move: adding the missing PostToolUse hook is the migration itself, and any entry still naming a removed flag (--b64, --filter-files) or a stale Docker image ref is rewritten. A stale entry naming a removed flag is not cosmetic — the script would exit non-zero on it, and for PreToolUse that blocks every Bash call.

--help

Print a more detailed help message.

ENVIRONMENT

MCP_RUN_COMPRESS_INSTALL_MODE

native (default) or docker. Controls the hook command written by --install-claude.

MCP_RUN_COMPRESS_IMAGE

Image reference used by the docker-mode hook. Defaults to raudssus/mcp-run-compress:latest. The shipped Docker image pins this to the exact version that built it, so --install-claude from inside the container writes a versioned hook.

MCP_RUN_COMPRESS_MAX_BYTES

Byte ceiling for the replaced output, default 29000. Exists because the ~30000 byte limit it stays below belongs to Claude Code, not to this distribution, and a changed harness would otherwise leave no lever.

MCP_RUN_COMPRESS_NO_CO_AUTHORED

Disables the Co-Authored-By rewrite in the PreToolUse hook, and is the only thing that does. Not to be confused with CO_AUTHORED_BY, which replaces the value rather than switching the trailer off.

CO_AUTHORED_BY

Replacement value for the Co-Authored-By trailer.

SEE ALSO

MCP::Run::Compress, MCP::Run::Bash, mcp-run-bash

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-mcp-run/issues.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <getty@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.