From 488fc200d4f646968bbd5b266241c6c358ebd18a Mon Sep 17 00:00:00 2001 From: Grey Newell Date: Thu, 26 Feb 2026 23:15:52 -0500 Subject: [PATCH 1/5] fix: remove double [uncompact] Context restored line from display cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uncompact-hook.sh was baking the status line into the display cache, then show-hook.sh / show-cache added it again on readback — producing two identical lines. Write only the raw context bomb to the cache; the display hook is responsible for appending the status line. Co-Authored-By: Grey Newell Co-Authored-By: Claude Sonnet 4.6 --- scripts/uncompact-hook.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/uncompact-hook.sh b/scripts/uncompact-hook.sh index cd1885f..d83fbbc 100644 --- a/scripts/uncompact-hook.sh +++ b/scripts/uncompact-hook.sh @@ -38,10 +38,7 @@ OUTPUT="$("$UNCOMPACT" run --fallback --post-compact)" DISPLAY_CACHE="${TMPDIR:-/tmp}/uncompact-display-${UID:-$(id -u)}.txt" if [ -n "$OUTPUT" ]; then - CHAR_COUNT="${#OUTPUT}" - APPROX_TOKENS=$(( CHAR_COUNT / 4 )) - # Write to display cache — UserPromptSubmit hook will show this as a visible # transcript message on the user's next message. - printf '%s\n\n[uncompact] Context restored (~%d tokens)\n' "$OUTPUT" "$APPROX_TOKENS" > "$DISPLAY_CACHE" + printf '%s\n' "$OUTPUT" > "$DISPLAY_CACHE" fi From e99032cad27a088699647fa4ea0db097ba48ef8e Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 04:25:57 +0000 Subject: [PATCH 2/5] fix: address CodeRabbit comment on cmd/run.go Pass postCompact flag through to runWithoutCache so the acknowledgment instruction is not lost on the no-cache fallback path. Co-Authored-By: Grey Newell Co-Authored-By: Claude Sonnet 4.6 --- cmd/run.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 7b2b20e..5077a72 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -68,13 +68,13 @@ func runHandler(cmd *cobra.Command, args []string) error { dbPath, err := config.DBPath() if err != nil { logFn("[warn] cannot open cache: %v", err) - return runWithoutCache(cfg, proj, wm, logFn) + return runWithoutCache(cfg, proj, wm, postCompact, logFn) } store, err := cache.Open(dbPath) if err != nil { logFn("[warn] cache open error: %v", err) - return runWithoutCache(cfg, proj, wm, logFn) + return runWithoutCache(cfg, proj, wm, postCompact, logFn) } defer store.Close() @@ -192,7 +192,7 @@ func runHandler(cmd *cobra.Command, args []string) error { } // runWithoutCache attempts an API fetch with no cache fallback. -func runWithoutCache(cfg *config.Config, proj *project.Info, wm *project.WorkingMemory, logFn func(string, ...interface{})) error { +func runWithoutCache(cfg *config.Config, proj *project.Info, wm *project.WorkingMemory, postCompact bool, logFn func(string, ...interface{})) error { ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) defer cancel() @@ -218,7 +218,7 @@ func runWithoutCache(cfg *config.Config, proj *project.Info, wm *project.Working return silentExit() } - opts := tmpl.RenderOptions{MaxTokens: maxTokens, WorkingMemory: wm} + opts := tmpl.RenderOptions{MaxTokens: maxTokens, WorkingMemory: wm, PostCompact: postCompact} output, _, err := tmpl.Render(graph, proj.Name, opts) if err != nil { logFn("[warn] render error: %v", err) From e30bd2cb2367a1f7c044afd6c55b7248a6fbd92b Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 04:26:19 +0000 Subject: [PATCH 3/5] fix: address CodeRabbit comment on internal/hooks/hooks.go Add show-hook.sh as an accepted wrapper form for the UserPromptSubmit hook in isAlreadyInstalled, mirroring the Stop hook check, to prevent false-negative results when the wrapper script form is installed. Co-Authored-By: Grey Newell Co-Authored-By: Claude Sonnet 4.6 --- internal/hooks/hooks.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/hooks/hooks.go b/internal/hooks/hooks.go index 1b121f4..582a04a 100644 --- a/internal/hooks/hooks.go +++ b/internal/hooks/hooks.go @@ -187,7 +187,7 @@ func commandExistsInHooks(hookList []Hook, matches ...string) bool { // isAlreadyInstalled checks if ALL uncompact hooks are present. func isAlreadyInstalled(hooks map[string][]Hook) bool { return commandExistsInHooks(hooks["Stop"], "uncompact run", "uncompact-hook.sh") && - commandExistsInHooks(hooks["UserPromptSubmit"], "uncompact show-cache") + commandExistsInHooks(hooks["UserPromptSubmit"], "uncompact show-cache", "show-hook.sh") } // mergeHooks adds hooks from toAdd into existing, skipping any whose commands From 27120b6b513ad0f0af25627acfb4d5f750ce869f Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 04:26:36 +0000 Subject: [PATCH 4/5] fix: address CodeRabbit comment on internal/template/render.go Reserve token budget for the post-compact acknowledgment note before enforcing the MaxTokens limit, so the final output never exceeds the configured token budget. Co-Authored-By: Grey Newell Co-Authored-By: Claude Sonnet 4.6 --- internal/template/render.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/internal/template/render.go b/internal/template/render.go index 87e5e67..3fb7e46 100644 --- a/internal/template/render.go +++ b/internal/template/render.go @@ -137,7 +137,27 @@ func Render(graph *api.ProjectGraph, projectName string, opts RenderOptions) (st } if opts.PostCompact { - note := fmt.Sprintf("\n\n> [uncompact] Context restored after compact (~%d tokens). Please acknowledge at the start of your response with exactly: \"✓ Uncompact: context restored (~%d tokens)\"", resultTokens, resultTokens) + buildNote := func(n int) string { + return fmt.Sprintf("\n\n> [uncompact] Context restored after compact (~%d tokens). Please acknowledge at the start of your response with exactly: \"✓ Uncompact: context restored (~%d tokens)\"", n, n) + } + note := buildNote(resultTokens) + noteTokens := countTokens(note) + if opts.MaxTokens > 0 && resultTokens+noteTokens > opts.MaxTokens { + budget := opts.MaxTokens - noteTokens + if budget < 1 { + budget = 1 + } + if tokens <= budget { + result, resultTokens = fullText, tokens + } else { + truncated, truncatedTokens, truncErr := truncateToTokenBudget(graph, projectName, budget, graph.Stats.CircularDependencyCycles, opts.WorkingMemory) + if truncErr != nil { + return "", 0, truncErr + } + result, resultTokens = truncated, truncatedTokens + } + note = buildNote(resultTokens) + } result += note resultTokens = countTokens(result) } From fad86a1a9d86080e805f7593be43ef4060d28b12 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 04:26:49 +0000 Subject: [PATCH 5/5] fix: address CodeRabbit comment on scripts/uncompact-hook.sh Use umask 077 + mktemp + atomic mv to write the display cache securely, preventing information leakage and TOCTOU races on multi-user systems. Co-Authored-By: Grey Newell Co-Authored-By: Claude Sonnet 4.6 --- scripts/uncompact-hook.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/uncompact-hook.sh b/scripts/uncompact-hook.sh index d83fbbc..f73445d 100644 --- a/scripts/uncompact-hook.sh +++ b/scripts/uncompact-hook.sh @@ -38,7 +38,9 @@ OUTPUT="$("$UNCOMPACT" run --fallback --post-compact)" DISPLAY_CACHE="${TMPDIR:-/tmp}/uncompact-display-${UID:-$(id -u)}.txt" if [ -n "$OUTPUT" ]; then - # Write to display cache — UserPromptSubmit hook will show this as a visible - # transcript message on the user's next message. - printf '%s\n' "$OUTPUT" > "$DISPLAY_CACHE" + # Write securely and atomically to avoid disclosure/races. + umask 077 + TMP_CACHE="$(mktemp "${DISPLAY_CACHE}.XXXXXX")" || exit 0 + printf '%s\n' "$OUTPUT" > "$TMP_CACHE" + mv -f "$TMP_CACHE" "$DISPLAY_CACHE" fi