Cuts a long successful Bash output to its first 2,000 and last 3,000 characters with a count of the cut lines; read-like commands, failures and saved outputs…

A mod (a plugin that adds a hook to Claude Code) that cuts a long successful Bash output to its start and its end. Long sessions then carry less dead context.
Claude Code keeps a successful Bash output in context whole, up to about 30,000 characters. Each later call sends that output again. A test run, a build log or a long grep usually matters only at its start and its end.
bash-trim keeps the first 2,000 and the last 3,000 characters of such an output, and says how much it cut.
/plugin marketplace add cpeaustriajc/cj-stack
/plugin install bash-trim@cj-stack
To see that it is on, run claude plugin list and look for bash-trim. The mod is optional: cj-stack does not install it.
npm test. The command passes and prints 18,000 characters.[… 217 lines, 13,000 characters cut …], and the end with "26 passed, 0 failed".| Bash result | Decision |
|---|---|
| Successful, over 10,000 characters | Keep the first 2,000 and last 3,000 characters, with a line that counts the cut lines and characters. |
| 10,000 characters or less | Pass whole. |
| Failed or interrupted | Pass whole. Claude Code already shows a part of a failed output. |
| Saved to a file by Claude Code, an image, or a background task | Pass whole. |
A read-like command: cat, sed -n, head, tail, jq, git diff, git show, git log -p, gh pr, gh api, gh issue | Pass whole, also after a leading cd … &&. |
The mod cuts only the standard output. The error output passes whole.
No settings. The limits are constants in hooks/register.ts.
To turn the mod off, run claude plugin disable bash-trim@cj-stack.
sed -n or grep.npm test && cat log is trimmed.hooks/register.ts 46 lines1import type { Register } from 'claude-code'
2
3const MOD = 'bash-trim'
4const TRIGGER = 10_000
5const HEAD = 2_000
6const TAIL = 3_000
7
8const READ_LIKE = [
9 /^(cat|head|tail|jq)\b/,
10 /^sed\s+(-\S+\s+)*-n\b/,
11 /^git\s+(diff|show)\b/,
12 /^git\s+log\b.*\s-p\b/,
13 /^gh\s+(pr|api|issue)\b/,
14]
15
16const group = (n: number) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
17
18export function isReadLike(command: string): boolean {
19 let rest = command.trim()
20 for (let m; (m = /^cd\s+[^;&|]*&&\s*/.exec(rest)); ) rest = rest.slice(m[0].length)
21 return READ_LIKE.some(re => re.test(rest))
22}
23
24export function trim(text: string): string {
25 if (text.length <= TRIGGER) return text
26 const cut = text.slice(HEAD, text.length - TAIL)
27 const lines = cut.split('\n').length - 1
28 return `${text.slice(0, HEAD)}\n[… ${group(lines)} lines, ${group(cut.length)} characters cut …]\n${text.slice(-TAIL)}`
29}
30
31export const register: Register = on => {
32 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
33 const ran = await next(e)
34 if (ran.deny !== undefined || ran.isError) return ran
35 try {
36 const out = ran.result
37 if (out.interrupted || out.isImage || out.persistedOutputPath || out.backgroundTaskId || isReadLike(e.command)) return ran
38 const stdout = trim(out.stdout)
39 return stdout === out.stdout ? ran : { result: { ...out, stdout }, context: ran.context }
40 } catch (err) {
41 $.ui.toast(`${MOD}: left this output whole (${(err as Error).message})`)
42 return ran
43 }
44 }).catch(($, e, next) => next(e))
45}
46