Show Claude Code file edits as one-line summaries instead of full diffs

<strong>Follow what Claude did, not every line it changed.</strong><br> A Claude Code mod that replaces the full diff under each file edit with a one-line summary.
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/Version-1.2.0-green.svg" alt="Version 1.2.0"></a> <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License"></a> <a href="https://code.claude.com/docs"><img src="https://img.shields.io/badge/Platform-Claude%20Code-D97757.svg" alt="Platform: Claude Code"></a> <a href="hooks/register.tsx"><img src="https://img.shields.io/badge/Type-Function%20hooks%20mod-6E7681.svg" alt="Function hooks mod"></a> <a href="hooks/register.test.tsx"><img src="https://img.shields.io/badge/Tests-6%20passing-3FB950.svg" alt="6 tests passing"></a>
<a href="#features">Features</a> · <a href="#install">Install</a> · <a href="#usage">Usage</a> · <a href="#development">Development</a> · <a href="CONTRIBUTING.md">Contributing</a> · <a href="CHANGELOG.md">Changelog</a>
Illustration of the transcript before and after.
When Claude Code edits files, each Edit or Write step, and each shell command that changes files, prints the whole diff into the transcript. Over a long task, those diffs push the steps and Claude's explanations off the screen. quiet-diffs keeps every step visible and turns each diff into a single line with the file path and its added and removed line counts. The change only affects what the transcript draws: the stored tool result, and what Claude reads, stay the same.
[!NOTE] quiet-diffs is built on Claude Code's function-hooks mod API, which is in early access and may change between releases.
Edit, Write and NotebookEdit results show the path and +added −removed instead of the diff./quiet-diffs brings the full diff back for the session; run it again to return to summaries.| Tool | Shown instead of the diff |
|---|---|
Edit | Edited src/app.ts (+2 −1) |
Write, new file | Created docs/notes.md (40 lines) |
Write, overwrite | Overwrote config.json (+5 −2) |
NotebookEdit | Edited analysis.ipynb (cell abc123, replace) |
Bash that changes files | Command output, then Edited notes.txt (+2 −1), Created new.txt (+1 −0) or Deleted old.txt (+0 −2) per file |
In a Claude Code terminal session, run:
/plugin install quiet-diffs --marketplace AnnihilationWizard/quiet-diffs
y to add the github:AnnihilationWizard/quiet-diffs marketplace.Installed quiet-diffs. Plugin is now active. The mod works in the current session without a restart.To update later, run claude plugin update, then /reload-plugins.
git clone https://github.com/AnnihilationWizard/quiet-diffs.git
claude --plugin-dir ./quiet-diffs
To load a clone in every session, add it to the env block of ~/.claude/settings.json:
{
"env": { "CLAUDE_CODE_PLUGIN_DIRS": "/path/to/quiet-diffs" }
}
Once installed, there is nothing to configure. Ask Claude to change a file and the transcript shows the summary line.
| Command | Effect |
|---|---|
/quiet-diffs | Show full diffs for this session |
/quiet-diffs again | Return to one-line summaries |
Ctrl+O | Open the detailed transcript (also summarized while the mod is on) |
Two Claude Code settings complete the quiet view. Add them to ~/.claude/settings.json:
{
"viewMode": "default",
"bashEditDiffEnabled": true
}
| Setting | Why |
|---|---|
viewMode: "default" | Shows each step. Focus view hides steps and only shows the final reply. |
bashEditDiffEnabled: true | Records which files a shell command changed in every permission mode, so quiet-diffs can list them. Without it, Claude Code records them only in auto and bypassPermissions modes. Set it to false to hide shell edits entirely. |
Ctrl+O transcript, so summaries apply there too. Use /quiet-diffs when you need the full diff./plugin install command is available in terminal sessions only. A user-scope install also loads in the desktop app's Code tab.claude plugin validate . # manifest, marketplace file and hooks module
claude plugin test . # hooks/*.test.tsx against the engine
After Claude Code first loads the mod, it writes the API types to .claude-plugin/types/ (git-ignored). Type-check against them with TypeScript 5.4 or newer:
npx -p typescript@5 tsc -p .
Run claude --plugin-dir . to try changes. In an interactive session the folder is watched, and saving a file reloads the mod.
.claude-plugin/ plugin.json manifest and marketplace.json
hooks/ hooks.json, the hooks module and its tests
types/ state contract for the /quiet-diffs toggle
docs/images/ README illustration
Bug reports and ideas are welcome. Read CONTRIBUTING.md before opening an issue or pull request, and follow SECURITY.md for security reports.
MIT License. Copyright © 2026 AnnihilationWizard.
hooks/register.tsx 112 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { ShowDiffs } from '../types'
5
6const showDiffs = atom({ plugin: 'quiet-diffs', key: 'showDiffs' } as const, false as ShowDiffs)
7
8type Hunk = { lines: string[] }
9
10const countLines = (hunks: readonly Hunk[]) => {
11 let added = 0
12 let removed = 0
13 for (const hunk of hunks) {
14 for (const line of hunk.lines) {
15 if (line.startsWith('+')) added += 1
16 else if (line.startsWith('-')) removed += 1
17 }
18 }
19
20 return { added, removed }
21}
22
23const summarize = (tool: string, output: unknown): string | undefined => {
24 if (typeof output !== 'object' || output === null) return undefined
25 const o = output as Record<string, unknown>
26 if (o.staged === true) return undefined
27
28 if (tool === 'Edit' && typeof o.filePath === 'string' && Array.isArray(o.structuredPatch)) {
29 const { added, removed } = countLines(o.structuredPatch as Hunk[])
30 return `Edited ${o.filePath} (+${added} −${removed})`
31 }
32 if (tool === 'Write' && typeof o.filePath === 'string') {
33 if (o.type === 'create') {
34 const text = typeof o.content === 'string' ? o.content.replace(/\n$/, '') : ''
35 const lines = text === '' ? 0 : text.split('\n').length
36 return `Created ${o.filePath} (${lines} ${lines === 1 ? 'line' : 'lines'})`
37 }
38 const hunks = Array.isArray(o.structuredPatch) ? (o.structuredPatch as Hunk[]) : []
39 const { added, removed } = countLines(hunks)
40 return `Overwrote ${o.filePath} (+${added} −${removed})`
41 }
42 if (tool === 'NotebookEdit' && typeof o.notebook_path === 'string' && !o.error) {
43 return `Edited ${o.notebook_path} (cell ${String(o.cell_id ?? '?')}, ${String(o.edit_mode)})`
44 }
45
46 return undefined
47}
48
49type BashFile = { filePath: string; hunks: Hunk[]; created?: true; deleted?: true }
50
51// Splits a Bash result into the output without its file-edit diff and one
52// line per changed file. Undefined when the command changed no files.
53const splitBashEdits = (output: unknown) => {
54 if (typeof output !== 'object' || output === null) return undefined
55 const { bashEditDiff, ...rest } = output as Record<string, unknown>
56 if (typeof bashEditDiff !== 'object' || bashEditDiff === null) return undefined
57 const diff = bashEditDiff as { files?: unknown; moreFiles?: unknown }
58 if (!Array.isArray(diff.files) || diff.files.length === 0) return undefined
59
60 const lines = (diff.files as BashFile[]).map(file => {
61 const { added, removed } = countLines(file.hunks ?? [])
62 const verb = file.created ? 'Created' : file.deleted ? 'Deleted' : 'Edited'
63 return `${verb} ${file.filePath} (+${added} −${removed})`
64 })
65 const more = typeof diff.moreFiles === 'number' ? diff.moreFiles : 0
66 if (more > 0) lines.push(`and ${more} more ${more === 1 ? 'file' : 'files'}`)
67
68 return { output: rest, lines }
69}
70
71export const register: Register = on => {
72 on('session.start', async ($, e, next) => {
73 await $.command.register({
74 name: 'quiet-diffs',
75 description: 'Toggle between full diffs and one-line summaries for file edits',
76 })
77
78 return next(e)
79 })
80
81 on('command.run', { command: 'quiet-diffs' }, async $ => {
82 const shown = await update($, showDiffs, v => !v)
83
84 return { text: shown ? 'Showing full diffs' : 'Showing one-line summaries' }
85 })
86
87 on('ui.render', { component: 'ToolResult' }, async ($, e, next) => {
88 if (e.props.isErrored || (await read($, showDiffs))) return next(e)
89 const { Box, Text } = $.ui.resolve(e)
90
91 if (e.props.tool === 'Bash') {
92 const split = splitBashEdits(e.props.output)
93 if (split === undefined) return next(e)
94 const drawn = await next({ ...e, props: { ...e.props, output: split.output } })
95
96 return (
97 <Box flexDirection="column">
98 {drawn}
99 {split.lines.map(line => (
100 <Text dimColor>{line}</Text>
101 ))}
102 </Box>
103 )
104 }
105
106 const line = summarize(e.props.tool, e.props.output)
107 if (line === undefined) return next(e)
108
109 return <Text dimColor>{line}</Text>
110 })
111}
112types/index.d.ts 8 lines1export type ShowDiffs = boolean
2
3declare module 'claude-code' {
4 interface PluginState {
5 'quiet-diffs': { showDiffs: ShowDiffs }
6 }
7}
8