Shows the first 8 lines of each file a Bash command changed, instead of 40

Stop Bash file diffs from flooding your Claude Code transcript.
(+N -M) count, and adds … N more lines under it.<table> <tr> <th>Without the mod</th> <th>With the mod</th> </tr> <tr> <td valign="top"><img src="docs/without-mod.png" alt="A Bash call creating three 100-line files: each file's diff runs 40 lines before '… 60 more lines'" width="400"></td> <td valign="top"><img src="docs/with-mod.png" alt="The same Bash call with the mod: each file shows 'Created demo/alpha.txt (+100 -0)', its first 8 lines, then '… 92 more lines'" width="400"></td> </tr> <tr> <td colspan="2">The same command, creating three 100-line files.</td> </tr> </table>
Only the drawing changes: what the model sees is untouched.
To keep each file's count right, the mod draws a cut row's files itself rather than handing Claude Code a shortened diff (which it would count from). On those rows:
/fix-bash-diffs off does.At the prompt of a Claude Code terminal session:
/plugin install fix-bash-diffs --marketplace adrienbrault/claude-fix-bash-diffs
Answer y to add the marketplace, then pick a scope.
Or from a shell:
claude plugin marketplace add adrienbrault/claude-fix-bash-diffs
claude plugin install fix-bash-diffs@fix-bash-diffs
Either way, it loads in the next session you start.
claude plugin marketplace update fix-bash-diffs
claude plugin update fix-bash-diffs@fix-bash-diffs # then restart the session
claude plugin uninstall fix-bash-diffs@fix-bash-diffs
/fix-bash-diffs turns the cut off and on again for the current session, so you can compare. Rows already on screen redraw.
Claude Code only draws Bash file diffs when the session runs in a git repository, so that is where the mod has something to cut.
To change the limit, edit MAX_LINES in hooks/register.tsx.
Built and tested on Claude Code 2.1.303. It uses the plugin hooks API, which is early access, and reads the per-file diff Claude Code attaches to a Bash result, an internal field. Either can change in a later release; if Bash rows stop being cut, that is the likely reason.
claude --plugin-dir /path/to/claude-fix-bash-diffs
claude plugin validate /path/to/claude-fix-bash-diffs
claude plugin test /path/to/claude-fix-bash-diffs
MIT
hooks/register.tsx 105 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4// Claude Code draws up to 40 lines a file under a Bash call; this keeps the first few.
5export const MAX_LINES = 8
6
7const isOff = atom({ plugin: 'fix-bash-diffs', key: 'isOff' } as const, false)
8
9type Hunk = { oldStart: number; oldLines: number; newStart: number; newLines: number; lines: string[] }
10type FileDiff = { filePath: string; hunks: Hunk[]; created?: true; deleted?: true }
11type BashEditDiff = { files: FileDiff[]; moreFiles: number; skipped?: true; unavailable?: true; shared?: true }
12type BashOutput = { stdout?: string; stderr?: string; bashEditDiff?: BashEditDiff }
13
14const marked = (hunks: Hunk[], mark: string) => hunks.reduce((n, h) => n + h.lines.filter(l => l[0] === mark).length, 0)
15
16// Unified-diff text, which a Code element draws with Claude Code's own gutters and colours.
17const toSource = (hunks: Hunk[]) =>
18 hunks.map(h => [`@@ -${h.oldStart},${h.oldLines} +${h.newStart},${h.newLines} @@`, ...h.lines].join('\n')).join('\n')
19
20const relative = (path: string, cwd: string) => (path.startsWith(`${cwd}/`) ? path.slice(cwd.length + 1) : path)
21
22const counts = (lines: string[], skip: string) => lines.filter(l => l[0] !== skip && l[0] !== '\\').length
23
24// A "\ No newline at end of file" marker is no line of the file.
25const fileLines = (lines: string[]) => lines.filter(l => l[0] !== '\\').length
26
27// Keeps a file's first `max` diff lines, cutting the hunk they end in.
28export function head(hunks: Hunk[], max = MAX_LINES): { hunks: Hunk[]; hidden: number } {
29 const kept: Hunk[] = []
30 let room = max
31 let total = 0
32 for (const h of hunks) {
33 total += fileLines(h.lines)
34 if (room <= 0) continue
35 const lines = h.lines.slice(0, room)
36 room -= fileLines(lines)
37 kept.push({ ...h, lines, oldLines: counts(lines, '+'), newLines: counts(lines, '-') })
38 }
39
40 return { hunks: kept, hidden: total - (max - room) }
41}
42
43export const register: Register = on => {
44 on('session.start', async ($, e, next) => {
45 await $.command.register({
46 name: 'fix-bash-diffs',
47 description: 'Turn the cut of Bash file diffs on or off',
48 })
49
50 return next(e)
51 })
52
53 on('command.run', { command: 'fix-bash-diffs' }, async $ => {
54 const off = await update($, isOff, was => !was)
55
56 return { text: off ? 'off, Bash diffs drawn as Claude Code draws them (40 lines a file).' : 'on, 8 lines a file.' }
57 })
58
59 on('ui.render', { component: 'ToolResult' }, async ($, e, next) => {
60 // Read on every draw, so a toggle redraws the rows already on screen.
61 if (await read($, isOff)) return next(e)
62
63 const output = e.props.output as BashOutput | undefined
64 const diff = e.props.tool === 'Bash' && !e.props.isErrored ? output?.bashEditDiff : undefined
65 // A skipped, unavailable or shared diff carries notes of Claude Code's own: leave it to Claude Code.
66 if (!output || !diff || !Array.isArray(diff.files) || diff.skipped || diff.unavailable || diff.shared) return next(e)
67
68 const cut = diff.files.map(f => ({ file: f, ...head(f.hunks) }))
69 if (!cut.some(c => c.hidden > 0)) return next(e)
70
71 // Claude Code counts a file's (+N -M) from the hunks it draws, so a cut diff
72 // handed to it miscounts: the files are drawn here, counted from the whole diff.
73 // Handed no diff and no text, Claude Code would draw "(No output)": the text goes alone, or nothing.
74 const { bashEditDiff: _, ...rest } = output
75 const hasText = `${output.stdout ?? ''}${output.stderr ?? ''}`.trim() !== ''
76 const drawn = hasText ? await next({ ...e, props: { ...e.props, output: rest } }) : null
77 const cwd = await $.session.cwd()
78 const { Box, Text, Code } = $.ui.resolve(e)
79
80 return (
81 <Box flexDirection="column">
82 {drawn}
83 {cut.map(({ file, hunks, hidden }) => (
84 <Box key={file.filePath} flexDirection="row">
85 <Text dimColor>{' ⎿ '}</Text>
86 <Box flexDirection="column">
87 <Text>
88 {file.created ? 'Created' : file.deleted ? 'Deleted' : 'Updated'} <Text bold>{relative(file.filePath, cwd)}</Text>{' '}
89 <Text dimColor>
90 (+{marked(file.hunks, '+')} -{marked(file.hunks, '-')})
91 </Text>
92 </Text>
93 {hunks.length > 0 ? <Code format="diff" source={toSource(hunks)} path={file.filePath} /> : null}
94 {hidden > 0 ? <Text dimColor>… {hidden} more lines</Text> : null}
95 </Box>
96 </Box>
97 ))}
98 {diff.moreFiles > 0 ? (
99 <Text dimColor>{` ⎿ … ${diff.moreFiles} more ${diff.moreFiles === 1 ? 'file' : 'files'} changed`}</Text>
100 ) : null}
101 </Box>
102 )
103 })
104}
105types/index.d.ts 8 lines1export type IsOff = boolean
2
3declare module 'claude-code' {
4 interface PluginState {
5 'fix-bash-diffs': { isOff: IsOff }
6 }
7}
8