feat: stderr status line + show-cache command for post-compact visibility#31
feat: stderr status line + show-cache command for post-compact visibility#31claude[bot] merged 9 commits intomainfrom
Conversation
…lity - uncompact-hook.sh: emit [uncompact] Context injected (~N tokens) to stderr so the user sees a status line in the terminal during compact - cmd/showcache.go: new `uncompact show-cache` command that reads the cached context bomb from the temp file and emits it to stdout (one-shot), then deletes the cache - hooks/hooks.json: update UserPromptSubmit hook to use `uncompact show-cache` instead of the bash wrapper script - internal/hooks/hooks.go: add UserPromptSubmit → uncompact show-cache to the hooks installed by `uncompact install`; refactor mergeHooks to be idempotent (deduplicates by command substring) and isAlreadyInstalled to check both Stop and UserPromptSubmit hooks - cmd/install.go: update success message to describe both installed hooks Co-Authored-By: Grey Newell <greyshipscode@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a one-shot Changes
Sequence DiagramsequenceDiagram
participant User
participant StopHook as Stop Hook\n(`uncompact-hook.sh`)
participant Cache as Temp File\n(`/tmp/uncompact-display-<uid>.txt`)
participant UserPrompt as UserPromptSubmit Hook
participant ShowCmd as show-cache Command\n(`cmd/showcache`)
participant Claude
User->>StopHook: finishes session -> Stop hook runs
StopHook->>Cache: write transcript + "[uncompact] Context restored (~N tokens)"
User->>UserPrompt: sends next message -> UserPromptSubmit triggers
UserPrompt->>ShowCmd: invokes `show-cache`
ShowCmd->>Cache: read file, delete file
ShowCmd->>Claude: output replayed to AI (prints context + token estimate)
Claude->>User: responds with restored context applied
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
cmd/showcache.go (2)
30-31: Consider logging read errors for debugging.Right now, any read error (permission denied, I/O error, etc.) silently exits. That's fine for production, but it could make debugging tricky if something goes wrong.
Maybe stash the error somewhere (a debug log, env-gated stderr) so you can troubleshoot later? Not blocking, just a thought for future-you.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/showcache.go` around lines 30 - 31, The read-error branch in cmd/showcache.go currently swallows errors (if err != nil { return nil }); change it to record the error for debugging before returning so issues like permission or I/O failures are visible. Update the same function to log the error (e.g., using the existing logger or log.Printf) and gate noisy output behind a debug/verbose flag or an env var (e.g., DEBUG) so production behavior stays unchanged; keep the return value the same after logging. Reference the err variable and the read branch in cmd/showcache.go when making the change.
34-38: Minor: delete happens before print - tiny edge case.If
fmt.Printsomehow fails (rare but possible - broken pipe, etc.), the cache is already gone. The current order is:
- Delete file
- Print content
If you flipped it:
- Print content
- Delete file
...a failed print would leave the cache intact for retry. Probably doesn't matter in practice since
fmt.Printto stdout rarely fails, but figured I'd mention it!🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/showcache.go` around lines 34 - 38, The deletion happens before printing (os.Remove(cachePath) then fmt.Print), which can leave cache gone if printing fails; change the order in the cmd/showcache.go flow to print the cached data first (use fmt.Print and check its returned error), and only remove the cachePath afterward (call os.Remove(cachePath) after a successful print, and optionally handle/log Remove errors); update the code around the print/remove sequence accordingly (refer to the os.Remove(cachePath) call and the fmt.Print(string(data)) call).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cmd/showcache.go`:
- Around line 23-24: showCacheHandler constructs cachePath using os.Getuid(),
which returns -1 on Windows and leads to mismatched filenames; update
showCacheHandler to detect when os.Getuid() == -1 and fall back to a
Windows-safe identifier (e.g., the current username from
os/user.Current().Username or a fixed literal like "windows") before calling
fmt.Sprintf and filepath.Join so the generated filename matches what hooks
expect cross-platform; reference symbols: showCacheHandler, cachePath,
os.Getuid, fmt.Sprintf, filepath.Join, and consider using os/user.Current() for
the fallback.
---
Nitpick comments:
In `@cmd/showcache.go`:
- Around line 30-31: The read-error branch in cmd/showcache.go currently
swallows errors (if err != nil { return nil }); change it to record the error
for debugging before returning so issues like permission or I/O failures are
visible. Update the same function to log the error (e.g., using the existing
logger or log.Printf) and gate noisy output behind a debug/verbose flag or an
env var (e.g., DEBUG) so production behavior stays unchanged; keep the return
value the same after logging. Reference the err variable and the read branch in
cmd/showcache.go when making the change.
- Around line 34-38: The deletion happens before printing (os.Remove(cachePath)
then fmt.Print), which can leave cache gone if printing fails; change the order
in the cmd/showcache.go flow to print the cached data first (use fmt.Print and
check its returned error), and only remove the cachePath afterward (call
os.Remove(cachePath) after a successful print, and optionally handle/log Remove
errors); update the code around the print/remove sequence accordingly (refer to
the os.Remove(cachePath) call and the fmt.Print(string(data)) call).
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
cmd/install.gocmd/showcache.gohooks/hooks.jsoninternal/hooks/hooks.goscripts/uncompact-hook.sh
…us on replay Hooks run with a restricted environment where ~/go/bin is not in PATH, causing bare 'uncompact show-cache' to fail with "command not found". - hooks.json: UserPromptSubmit → show-hook.sh (no binary PATH dependency) - hooks.go: wrap install-time hook commands in bash with explicit PATH - show-hook.sh + showcache.go: emit '[uncompact] Context replayed' to stderr so the user sees confirmation when context is replayed on UserPromptSubmit Co-Authored-By: Grey Newell <greyshipscode@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
claude-auto-assign.yml posts @claude comments as claude[bot], which then triggers claude.yml. The action rejected these with "non-human actor" error because allowed_bots was not configured. Co-Authored-By: Grey Newell <greyshipscode@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/hooks/hooks.go`:
- Around line 201-211: Dedup logic misses legacy/variant forms (e.g., "uncompact
run" vs "bash -c '...; uncompact run'") so Stop hooks can be appended twice;
update the check used when iterating hooks in the loop (the call to
commandExistsInHooks(result[event], cmd.Command)) to normalize/compare commands
by their canonical operation (e.g., strip wrapping shells and trailing prefixes,
unquote, or compare the final command tokens) or extend commandExistsInHooks to
detect legacy variants (check for contains/endsWith of the core command like
"uncompact run" inside the candidate command); keep using the same symbols
hook.Hooks, cmd.Command and result[event] but ensure the comparison finds
semantic matches so duplicates are not appended.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
.github/workflows/claude.ymlcmd/showcache.gointernal/hooks/hooks.goscripts/show-hook.sh
🚧 Files skipped from review as they are similar to previous changes (1)
- cmd/showcache.go
- uncompact-hook.sh: emit context + status via printf to stdout; remove cache write (SessionStart:compact handles injection directly); rm -f display cache to prevent UserPromptSubmit double-injection - show-hook.sh: move status from stderr to stdout, use printf - showcache.go: consolidate output into single printf to stdout Hook stderr is silently discarded by Claude Code — only stdout is injected into the AI's context. Status line now appears in context on SessionStart:compact without the user needing to send a message. Co-Authored-By: Grey Newell <greyshipscode@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
scripts/show-hook.sh (1)
15-17:⚠️ Potential issue | 🟡 MinorComment claims atomicity, but
cat+rmisn't atomic.Hey! Quick heads up: the comment says "Read and remove atomically" but these are actually two separate operations. If two instances of this script ran at the exact same moment (super rare, but possible), they could both
catthe file before either does therm.Think of it like this: imagine two people both reaching for the last cookie on a plate. One grabs it, but before they can pull the plate away, the other person also grabs... air, because the cookie's gone. Here, both could grab the cookie.
In practice this is unlikely since UserPromptSubmit shouldn't fire concurrently, and the worst case is just seeing the context twice—not a big deal. But the comment is a bit misleading.
If you want true atomicity, you could
mvthe file first:TEMP_READ="$(mktemp)" mv "$DISPLAY_CACHE" "$TEMP_READ" 2>/dev/null || exit 0 OUTPUT="$(cat "$TEMP_READ")" rm -f "$TEMP_READ"Or just update the comment to be more accurate. Up to you!
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/show-hook.sh` around lines 15 - 17, The comment claims atomic read+remove but the current sequence (reading DISPLAY_CACHE into OUTPUT then rm) is not atomic; fix by moving DISPLAY_CACHE to a temporary file before reading to ensure only one process can take the file: create a temp with mktemp, mv DISPLAY_CACHE to that temp (if mv fails, exit 0), then read the temp into OUTPUT and remove the temp; reference DISPLAY_CACHE and OUTPUT in the update and replace the existing cat + rm sequence with this move-then-read pattern (or alternatively update the comment to remove the “atomically” claim if you prefer not to change behavior).
🧹 Nitpick comments (1)
scripts/uncompact-hook.sh (1)
48-49: Smart defensive cleanup—prevents double-injection.This is a nice touch: even if there's a stale cache file from a previous run that didn't clean up properly, this
rm -fensures UserPromptSubmit won't accidentally replay old context.One tiny nitpick: the file doesn't end with a newline after line 49. POSIX technically says text files should end with a newline, and some tools can be grumpy about it. Not a big deal, but easy to fix if you want to be squeaky clean.
,
🧹 Optional: add trailing newline
# Clean up any stale display cache so UserPromptSubmit doesn't double-inject. rm -f "$DISPLAY_CACHE" +🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/uncompact-hook.sh` around lines 48 - 49, The file ends without a trailing newline after the final rm -f "$DISPLAY_CACHE" line; open scripts/uncompact-hook.sh and add a single newline at EOF so the file terminates with a newline character (preserving the existing rm -f "$DISPLAY_CACHE" line and any surrounding comments) to satisfy POSIX text-file conventions and avoid tool warnings related to DISPLAY_CACHE/UserPromptSubmit handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@scripts/show-hook.sh`:
- Around line 15-17: The comment claims atomic read+remove but the current
sequence (reading DISPLAY_CACHE into OUTPUT then rm) is not atomic; fix by
moving DISPLAY_CACHE to a temporary file before reading to ensure only one
process can take the file: create a temp with mktemp, mv DISPLAY_CACHE to that
temp (if mv fails, exit 0), then read the temp into OUTPUT and remove the temp;
reference DISPLAY_CACHE and OUTPUT in the update and replace the existing cat +
rm sequence with this move-then-read pattern (or alternatively update the
comment to remove the “atomically” claim if you prefer not to change behavior).
---
Nitpick comments:
In `@scripts/uncompact-hook.sh`:
- Around line 48-49: The file ends without a trailing newline after the final rm
-f "$DISPLAY_CACHE" line; open scripts/uncompact-hook.sh and add a single
newline at EOF so the file terminates with a newline character (preserving the
existing rm -f "$DISPLAY_CACHE" line and any surrounding comments) to satisfy
POSIX text-file conventions and avoid tool warnings related to
DISPLAY_CACHE/UserPromptSubmit handling.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
cmd/showcache.goscripts/show-hook.shscripts/uncompact-hook.sh
🚧 Files skipped from review as they are similar to previous changes (1)
- cmd/showcache.go
…ranscript SessionStart:compact stdout only reaches Claude as a system-reminder (not visible to the user in the chat transcript). Writing to the display cache instead means the UserPromptSubmit hook will show the context bomb as a visible hook output block in the transcript on the user's next message. Co-Authored-By: Grey Newell <greyshipscode@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fall back to username when os.Getuid() returns -1 (Windows) so the cache filename matches what the hook script expects cross-platform. Co-Authored-By: Grey Newell <greyshipscode@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extend mergeHooks dedup to also match legacy/variant command forms (e.g. bare "uncompact run") so upgrading from an old install does not append a duplicate Stop or UserPromptSubmit hook. Co-Authored-By: Grey Newell <greyshipscode@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/hooks/hooks.go`:
- Around line 200-210: The loop that builds dedupe matches inside the toAdd ->
for event, hooks -> for _, hook -> for _, cmd range is treating legacy commands
(e.g. "show-hook.sh"/"uncompact-hook.sh") as equivalents which causes the new
canonical commands not to be written; fix by normalizing/migrating legacy values
before creating the matches list: inside the loop over hook.Hooks (the
cmd.Command handling), map legacy names ("show-hook.sh" -> "uncompact
show-cache", "uncompact-hook.sh" -> "uncompact run") to their canonical commands
(or assign to a normalized variable) and then build matches/dedupe only around
the canonical form (adding any legacy names only for migration logging if
desired) so the new "UserPromptSubmit" command is added and legacy entries are
replaced rather than causing skip=true.
- Around line 36-43: The hardcoded bash wrapper for Command entries (e.g., the
auto-install hooks under "UserPromptSubmit" that create Command{Type: "command",
Command: `bash -c '...; uncompact ...'`}) will fail on Windows; update the code
that builds these Command.Command strings to branch on runtime.GOOS: for
"windows" emit a PowerShell-style invocation (e.g., use powershell.exe -Command
"<set $env:PATH or just call uncompact ...>") or, simpler, emit plain "uncompact
run" and "uncompact show-cache" for all platforms to avoid shell-specific
wrappers. Modify the hook construction logic that generates those Command
entries (look for where the Command structs are created for "UserPromptSubmit" /
auto-install hooks) to use runtime.GOOS to choose the appropriate command string
instead of always using bash -c.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
cmd/showcache.gointernal/hooks/hooks.go
🚧 Files skipped from review as they are similar to previous changes (1)
- cmd/showcache.go
| {Type: "command", Command: `bash -c 'export PATH="$HOME/go/bin:$HOME/.local/bin:/usr/local/bin:/opt/homebrew/bin:$PATH"; uncompact run'`}, | ||
| }, | ||
| }, | ||
| }, | ||
| "UserPromptSubmit": { | ||
| { | ||
| Hooks: []Command{ | ||
| {Type: "command", Command: `bash -c 'export PATH="$HOME/go/bin:$HOME/.local/bin:/usr/local/bin:/opt/homebrew/bin:$PATH"; uncompact show-cache'`}, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's look at the file structure and understand the project
cd internal/hooks && pwd && ls -laRepository: supermodeltools/Uncompact
Length of output: 271
🏁 Script executed:
# Read the hooks.go file to understand the hook execution mechanism
cat -n internal/hooks/hooks.go | head -100Repository: supermodeltools/Uncompact
Length of output: 3454
🏁 Script executed:
# Check if there's any information about how hooks are executed (search for Command type usage)
rg -A 5 -B 5 "Type.*command" internal/hooks/hooks.goRepository: supermodeltools/Uncompact
Length of output: 605
🏁 Script executed:
# Look for Windows-specific handling in the codebase
rg -i "windows|powershell|cmd\\.exe|shell|bash" --type-list | head -5
rg -i "windows|powershell|cmd" . --type goRepository: supermodeltools/Uncompact
Length of output: 4942
🏁 Script executed:
# Search for how the Command type is defined and used
rg "type Command struct" -A 10 .Repository: supermodeltools/Uncompact
Length of output: 723
🏁 Script executed:
# Check if there's documentation about this tool
fd -e md | xargs grep -l "hook" | head -5Repository: supermodeltools/Uncompact
Length of output: 78
🏁 Script executed:
# Check the README for information about hooks and Windows support
head -200 README.mdRepository: supermodeltools/Uncompact
Length of output: 6000
🏁 Script executed:
# Search for any additional context about hook execution or Windows compatibility
rg -i "hook|windows|bash|shell|execute" internal/hooks/hooks.go -B 2 -A 2Repository: supermodeltools/Uncompact
Length of output: 4417
🏁 Script executed:
# Look at the full hooks.go file to understand if there's any Windows-specific logic for hooks
tail -n +100 internal/hooks/hooks.goRepository: supermodeltools/Uncompact
Length of output: 5070
🌐 Web query:
Claude Code hooks guide how command type hooks executed Windows
💡 Result:
How type: "command" hooks are executed (incl. Windows)
- They run as OS shell commands: a command hook is defined with
{"type":"command","command":"..."}and Claude Code executes that shell command when the hook event fires. (docs.anthropic.com) - Working dir + env: handlers run in the current directory with Claude Code’s environment (and you can rely on env vars like
$CLAUDE_PROJECT_DIRwhen Claude Code spawns the hook). (docs.anthropic.com) - Input/Output contract:
- JSON is passed on stdin to your command hook. (docs.anthropic.com)
- Your hook can signal outcomes via exit codes and/or return structured JSON on stdout (for events that support decisions). (docs.anthropic.com)
Windows: what to put in command
Claude Code doesn’t require “PowerShell hooks” specifically—you just run PowerShell by calling it explicitly in the command string, e.g.:
{
"type": "command",
"command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""
}This example is shown in the official hooks guide under Windows (PowerShell). (docs.anthropic.com)
(Similarly, on Windows you can use cmd.exe /c ... or bash -c ... if those are available on your PATH—the key is: whatever you write in command is what gets executed as the shell command.) (docs.anthropic.com)
Citations:
- 1: https://docs.anthropic.com/en/docs/claude-code/hooks
- 2: https://docs.anthropic.com/en/docs/claude-code/hooks
- 3: https://docs.anthropic.com/en/docs/claude-code/hooks
- 4: https://docs.anthropic.com/en/docs/claude-code/hooks
- 5: https://docs.anthropic.com/en/docs/claude-code/hooks-guide
- 6: https://docs.anthropic.com/en/docs/claude-code/hooks
🌐 Web query:
Claude Code documentation hook command execution shell Windows
💡 Result:
Claude Code hooks are configured in ~/.claude/settings.json or .claude/settings.json and each hook handler can run a "type": "command" with a "command" string (the hook receives JSON on stdin). [1]
On Windows, the docs show you should invoke the shell you want explicitly—e.g. run PowerShell by setting command to powershell.exe -Command "..." (example shown for a Notification hook). [2]
Example (Windows / PowerShell):
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""
}
]
}
]
}
}You can also reference project-local scripts using CLAUDE_PROJECT_DIR in your command. [1]
Sources: Hooks reference [1], Hooks guide (Windows/PowerShell example) [2].
Generate platform-aware hook commands for Windows compatibility.
Lines 36 and 43 hardcode bash -c '...' in the auto-install code, but bash may not be available on Windows systems by default (unless Git Bash, WSL, or similar is installed). When users run uncompact install on Windows, these hooks will fail unless bash exists on their PATH.
Since Claude Code's official documentation shows users must invoke the shell explicitly (e.g., powershell.exe -Command "..." on Windows), use runtime.GOOS (already imported) to generate the appropriate shell command for each OS:
- Linux/macOS:
bash -c 'export PATH=...; uncompact ...' - Windows:
powershell.exe -Command "$env:PATH = ...; uncompact ..."(or justuncompact runif PATH isn't needed on Windows)
Alternatively, simplify both platforms to just uncompact run and uncompact show-cache (as shown in your README manual installation), since the PATH override may not be necessary when users already have these binaries in their PATH or use the plugin system.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/hooks/hooks.go` around lines 36 - 43, The hardcoded bash wrapper for
Command entries (e.g., the auto-install hooks under "UserPromptSubmit" that
create Command{Type: "command", Command: `bash -c '...; uncompact ...'`}) will
fail on Windows; update the code that builds these Command.Command strings to
branch on runtime.GOOS: for "windows" emit a PowerShell-style invocation (e.g.,
use powershell.exe -Command "<set $env:PATH or just call uncompact ...>") or,
simpler, emit plain "uncompact run" and "uncompact show-cache" for all platforms
to avoid shell-specific wrappers. Modify the hook construction logic that
generates those Command entries (look for where the Command structs are created
for "UserPromptSubmit" / auto-install hooks) to use runtime.GOOS to choose the
appropriate command string instead of always using bash -c.
There was a problem hiding this comment.
Acknowledged. The bash -c wrapper is intentional for now — the README documents Linux/macOS as the primary platform and bash availability is assumed. Generating platform-specific PowerShell commands is a larger design change. Leaving for a follow-up issue if Windows support becomes a priority.
There was a problem hiding this comment.
Skipped: comment is from another GitHub bot.
| for event, hooks := range toAdd { | ||
| result[event] = append(result[event], hooks...) | ||
| for _, hook := range hooks { | ||
| skip := false | ||
| for _, cmd := range hook.Hooks { | ||
| matches := []string{cmd.Command} | ||
| switch event { | ||
| case "Stop": | ||
| matches = append(matches, "uncompact run", "uncompact-hook.sh") | ||
| case "UserPromptSubmit": | ||
| matches = append(matches, "uncompact show-cache", "show-hook.sh") | ||
| } |
There was a problem hiding this comment.
Legacy show-hook.sh dedupe blocks migration to uncompact show-cache.
At Line 209, treating "show-hook.sh" as an installed equivalent makes skip=true for legacy configs, so the new UserPromptSubmit command is never written. This leaves stale hook commands after upgrade.
💡 Suggested fix (migrate legacy command, then dedupe on current form)
+func replaceLegacyShowHook(hooks []Hook, replacement string) ([]Hook, bool) {
+ replaced := false
+ for i := range hooks {
+ for j := range hooks[i].Hooks {
+ cmd := hooks[i].Hooks[j].Command
+ if strings.Contains(cmd, "show-hook.sh") && !strings.Contains(cmd, "uncompact show-cache") {
+ hooks[i].Hooks[j].Command = replacement
+ replaced = true
+ }
+ }
+ }
+ return hooks, replaced
+}
+
func mergeHooks(existing, toAdd map[string][]Hook) map[string][]Hook {
result := make(map[string][]Hook)
for k, v := range existing {
result[k] = v
}
for event, hooks := range toAdd {
for _, hook := range hooks {
skip := false
for _, cmd := range hook.Hooks {
+ if event == "UserPromptSubmit" {
+ if updated, replaced := replaceLegacyShowHook(result[event], cmd.Command); replaced {
+ result[event] = updated
+ skip = true
+ break
+ }
+ }
matches := []string{cmd.Command}
switch event {
case "Stop":
matches = append(matches, "uncompact run", "uncompact-hook.sh")
case "UserPromptSubmit":
- matches = append(matches, "uncompact show-cache", "show-hook.sh")
+ matches = append(matches, "uncompact show-cache")
}
if commandExistsInHooks(result[event], matches...) {
skip = true
break
}
}
if !skip {
result[event] = append(result[event], hook)
}
}
}
return result
}Also applies to: 211-218
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/hooks/hooks.go` around lines 200 - 210, The loop that builds dedupe
matches inside the toAdd -> for event, hooks -> for _, hook -> for _, cmd range
is treating legacy commands (e.g. "show-hook.sh"/"uncompact-hook.sh") as
equivalents which causes the new canonical commands not to be written; fix by
normalizing/migrating legacy values before creating the matches list: inside the
loop over hook.Hooks (the cmd.Command handling), map legacy names
("show-hook.sh" -> "uncompact show-cache", "uncompact-hook.sh" -> "uncompact
run") to their canonical commands (or assign to a normalized variable) and then
build matches/dedupe only around the canonical form (adding any legacy names
only for migration logging if desired) so the new "UserPromptSubmit" command is
added and legacy entries are replaced rather than causing skip=true.
There was a problem hiding this comment.
Fixed in commit cef8835 — removed "show-hook.sh" from the UserPromptSubmit dedup matches. There is no legacy show-hook.sh script, so it should not block the new uncompact show-cache hook from being installed.
There was a problem hiding this comment.
Skipped: comment is from another GitHub bot.
UserPromptSubmit hook stdout is never visible in the Claude Code chat UI — it only injects context for Claude as a system-reminder. To give users visible confirmation after compact, add --post-compact to uncompact run. When --post-compact is set, the context bomb includes a brief instruction asking Claude to open its first post-compact response with: "✓ Uncompact: context restored (~N tokens)" This is the only reliable user-visible signal available: Claude's response is shown in the transcript; hook stdout is not. Co-Authored-By: Grey Newell <greyshipscode@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove "show-hook.sh" from the UserPromptSubmit dedup matches: there is no legacy show-hook.sh script, so treating it as equivalent to "uncompact show-cache" would incorrectly block the new hook from being installed on clean systems. Co-Authored-By: Grey Newell <greyshipscode@gmail.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
uncompact-hook.shnow writes[uncompact] Context injected (~N tokens)to stderr during compact, visible in the terminaluncompact show-cachecommand: new subcommand that reads the cached context bomb and emits it to stdout (one-shot, deletes cache after), replacing the bashshow-hook.shwrapperuncompact install:installnow also adds aUserPromptSubmit → uncompact show-cachehook to settings.json so the context bomb is replayed into AI context on the first message after compactmergeHooksnow deduplicates by command substring so re-runninguncompact installis safe regardless of partial prior installsWhy UserPromptSubmit must be in settings.json
Plugin
hooks.jsondoes not fireUserPromptSubmithooks — onlySessionStart,PreCompact, andPostToolUsework from the plugin.uncompact installnow writes the hook directly to settings.json.Test plan
/compact, observe[uncompact] Context injected (~N tokens)in the terminal outputuncompact show-cachereplays the context bomb into AI contextuncompact installon a clean settings.json, verify both Stop and UserPromptSubmit hooks are writtenuncompact installagain on an already-installed settings.json, verify no duplicates@claude please implement this
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Chores