Skip to content

fix(suggest-compact): clean up old counter temp files - #2159

Merged
affaan-m merged 2 commits into
affaan-m:mainfrom
gaurav0107:fix/2156-suggest-compact-counter-temp-files-never
Jun 7, 2026
Merged

fix(suggest-compact): clean up old counter temp files#2159
affaan-m merged 2 commits into
affaan-m:mainfrom
gaurav0107:fix/2156-suggest-compact-counter-temp-files-never

Conversation

@gaurav0107

@gaurav0107 gaurav0107 commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • claude-tool-count-<sessionId> files written by scripts/hooks/suggest-compact.js were never removed, accumulating one orphan per session in the OS temp dir indefinitely (concern 1 of suggest-compact: counter temp files never cleaned up, and count resets every /compact #2156).
  • Add cleanupOldCounters() that sweeps stale counter files (older than COMPACT_STATE_TTL_DAYS days, default 14) at the top of main() before the active counter is opened. The active session's counter file is preserved unconditionally; sweep failures are swallowed to keep the always-exit 0 hook contract intact.
  • Env-var validation mirrors the existing COMPACT_THRESHOLD parser: zero, negative, and non-numeric values fall back to the default.
  • Scope-limited to concern 1. Concern 2 (counter resets on /compact because session_id rotates) changes user-visible counter semantics and is better as a separate PR.

Verification

node tests/hooks/suggest-compact.test.js   # 31/31 (24 existing + 7 new)
node tests/run-all.js                      # 2626/2626
npx eslint scripts/hooks/suggest-compact.js tests/hooks/suggest-compact.test.js  # clean
node scripts/ci/validate-no-personal-paths.js   # clean
node scripts/ci/validate-hooks.js          # 28 hook matchers validated

New test cases pin the contract:

  • removes counter files older than the retention window
  • preserves counter files within the retention window
  • preserves the active session's counter file even if old
  • honours COMPACT_STATE_TTL_DAYS env var
  • falls back to default for invalid COMPACT_STATE_TTL_DAYS (0, -5, abc)
  • does not touch unrelated temp files (no claude-tool-count- prefix)
  • exit-0 invariant holds under a populated temp dir

Fixes #2156


Summary by cubic

Clean up stale claude-tool-count-<sessionId> temp files to prevent unbounded buildup. Adds a TTL-based sweep (default 14 days) that runs before opening the active counter and preserves files at the TTL boundary; keeps the hook’s exit-0 behavior. Fixes #2156.

  • Bug Fixes
    • Sweep old counter files in the OS temp dir at startup; active session file is always preserved.
    • Use strict "older than" semantics and keep files at the TTL cutoff.
    • TTL configurable via COMPACT_STATE_TTL_DAYS; invalid values fall back to 14 days.
    • Cleanup failures are logged and ignored to maintain exit-0; 8 tests cover retention, boundary, env parsing, unrelated files, and exit behavior.

Written for commit 1e0d835. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Automatic cleanup of stale per-session counter files in the OS temp directory with a configurable retention period (default 14 days).
    • Preserves active session files and leaves unrelated temp files untouched.
    • Sweep failures are handled gracefully without impacting normal operation.
  • Tests

    • Added tests validating cleanup behavior, TTL overrides, invalid TTL fallbacks, and exit-code stability.

claude-tool-count-<sessionId> files were written into the OS temp dir
on every hook run and never removed, accumulating one orphan per
session indefinitely.

Sweep stale counter files at the top of main() before opening the
active counter. Retention is env-tunable via COMPACT_STATE_TTL_DAYS
(default 14 days); invalid values fall back to the default. The
active session's counter file is preserved unconditionally even if
its mtime is past the cutoff. Failures during the sweep are swallowed
to preserve the always-exit-0 hook contract.

Adds 7 regression tests covering the sweep, env-var validation, and
the always-exit-0 invariant under a populated temp dir.

Fixes affaan-m#2156
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bbab7b0c-d69b-41ca-83c3-bd157904acf9

📥 Commits

Reviewing files that changed from the base of the PR and between 30f4726 and 1e0d835.

📒 Files selected for processing (2)
  • scripts/hooks/suggest-compact.js
  • tests/hooks/suggest-compact.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • scripts/hooks/suggest-compact.js
  • tests/hooks/suggest-compact.test.js

📝 Walkthrough

Walkthrough

Adds configurable retention (COMPACT_STATE_TTL_DAYS, default 14) and a cleanupOldCounters routine to sweep and remove stale claude-tool-count-* files from the OS temp directory; integrates the sweep into suggest-compact's startup and adds tests covering retention, TTL parsing, and preservation of active/fresh files.

Changes

Counter Cleanup and Retention

Layer / File(s) Summary
Cleanup implementation and integration
scripts/hooks/suggest-compact.js
Introduces COUNTER_FILE_PREFIX, getCounterRetentionDays() to parse/validate COMPACT_STATE_TTL_DAYS, and cleanupOldCounters() to sweep and delete stale claude-tool-count-* files while preserving the active session's counter and swallowing/logging filesystem errors. Updates main() to construct counterFile in the shared tempDir and invoke cleanup before counter operations.
Cleanup test coverage
tests/hooks/suggest-compact.test.js
Adds helper to set file timestamps and a test suite that verifies stale counter removal, preservation of fresh and active session files, TTL override behavior and invalid-ttl fallback to default, non-interference with unrelated temp files, cutoff-boundary behavior, and that the hook exits with code 0 on mixed temp contents.

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰
The temp dir hums, I sweep the trail,
Old counters folded like a snail,
Fourteen days I wink and wait,
Fresh ones stay, stale ones vacate,
Compact and tidy — hop, celebrate!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding cleanup logic for old counter temp files in the suggest-compact hook.
Linked Issues check ✅ Passed The PR successfully addresses the primary coding objective from #2156: sweeping and deleting stale counter files older than COMPACT_STATE_TTL_DAYS with proper TTL validation, preservation of active session files, and maintaining exit-0 behavior.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the temp-file cleanup feature; the PR intentionally defers the separate counter-reset-on-/compact concern to another PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gaurav0107
gaurav0107 marked this pull request as ready for review June 5, 2026 19:18
@gaurav0107
gaurav0107 requested a review from affaan-m as a code owner June 5, 2026 19:18

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/hooks/suggest-compact.js`:
- Line 78: The TTL cutoff comparison is off-by-one: in the loop that checks file
age using stats.mtimeMs and cutoffMs (line with "if (stats.mtimeMs > cutoffMs)
continue;") change the comparison to use >= so files exactly at the cutoff are
treated as within retention (i.e. use if (stats.mtimeMs >= cutoffMs)
continue;)—update the condition where stats.mtimeMs and cutoffMs are compared to
enforce "older than" semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ade602ab-9d20-4cc4-b9ec-90a4d643b8c5

📥 Commits

Reviewing files that changed from the base of the PR and between bc8e12b and 30f4726.

📒 Files selected for processing (2)
  • scripts/hooks/suggest-compact.js
  • tests/hooks/suggest-compact.test.js

Comment thread scripts/hooks/suggest-compact.js Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/hooks/suggest-compact.js">

<violation number="1" location="scripts/hooks/suggest-compact.js:113">
P2: Cleanup scans the full OS temp directory synchronously on every PreToolUse invocation, adding potentially unbounded blocking I/O before each tool call.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


// Sweep stale counter files (concern 1 of #2156). Cheap, swallows errors,
// skips the active session's file. See cleanupOldCounters for details.
cleanupOldCounters(tempDir, getCounterRetentionDays(), counterFile);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Cleanup scans the full OS temp directory synchronously on every PreToolUse invocation, adding potentially unbounded blocking I/O before each tool call.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/hooks/suggest-compact.js, line 113:

<comment>Cleanup scans the full OS temp directory synchronously on every PreToolUse invocation, adding potentially unbounded blocking I/O before each tool call.</comment>

<file context>
@@ -43,7 +105,13 @@ async function main() {
+
+  // Sweep stale counter files (concern 1 of #2156). Cheap, swallows errors,
+  // skips the active session's file. See cleanupOldCounters for details.
+  cleanupOldCounters(tempDir, getCounterRetentionDays(), counterFile);
+
   const rawThreshold = parseInt(process.env.COMPACT_THRESHOLD || '50', 10);
</file context>
Suggested change
cleanupOldCounters(tempDir, getCounterRetentionDays(), counterFile);
+ // Only sweep occasionally to avoid blocking every tool call; use a sentinel file
+ // to track the last sweep time without scanning the full temp dir each run.
+ const sweepMarker = path.join(tempDir, `${COUNTER_FILE_PREFIX}.last-sweep`);
+ let shouldSweep = false;
+ try {
+ const sweepStats = fs.statSync(sweepMarker);
+ shouldSweep = Date.now() - sweepStats.mtimeMs > 24 * 60 * 60 * 1000; // once per day
+ } catch {
+ shouldSweep = true; // no marker yet
+ }
+ if (shouldSweep) {
+ cleanupOldCounters(tempDir, getCounterRetentionDays(), counterFile);
+ try { fs.writeFileSync(sweepMarker, ''); } catch { /* swallow */ }
+ }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Considered but not adopting: the sentinel-file approach trades one orphan class for another (the marker itself, plus extra writes per invocation), and the original concern (#2156) was specifically about temp files being created on every PreToolUse and never cleaned. On a typical OS temp dir the readdirSync + statSync sweep runs in well under the hook's <200ms budget, and cleanupOldCounters swallows all I/O errors so a slow filesystem cannot block tool execution. If profiling later shows real overhead, gating the sweep by frequency (e.g. once per process) would be cheaper than a sentinel and keeps the cleanup self-contained.

Comment thread scripts/hooks/suggest-compact.js Outdated
The cleanup sweep used `mtimeMs > cutoffMs` to short-circuit, which
matched files whose mtime sits exactly on the cutoff boundary and
deleted them. The cleanupOldCounters docstring promises only files
*older than* retentionDays are removed; a file at age == retentionDays
is not older than retentionDays, so it must survive.

Switch the comparison to `>=` so only strictly older files fall
through to deletion. Add a regression test that pins boundary-aged
files (mtimeMs sitting just past the projected cutoff) are preserved.

Refs affaan-m#2156
@affaan-m
affaan-m merged commit a08445a into affaan-m:main Jun 7, 2026
40 checks passed
syarfandi pushed a commit to syarfandi/ECC that referenced this pull request Jun 9, 2026
* fix(suggest-compact): clean up old counter temp files

claude-tool-count-<sessionId> files were written into the OS temp dir
on every hook run and never removed, accumulating one orphan per
session indefinitely.

Sweep stale counter files at the top of main() before opening the
active counter. Retention is env-tunable via COMPACT_STATE_TTL_DAYS
(default 14 days); invalid values fall back to the default. The
active session's counter file is preserved unconditionally even if
its mtime is past the cutoff. Failures during the sweep are swallowed
to preserve the always-exit-0 hook contract.

Adds 7 regression tests covering the sweep, env-var validation, and
the always-exit-0 invariant under a populated temp dir.

Fixes affaan-m#2156

* fix(suggest-compact): preserve counter files at the TTL cutoff boundary

The cleanup sweep used `mtimeMs > cutoffMs` to short-circuit, which
matched files whose mtime sits exactly on the cutoff boundary and
deleted them. The cleanupOldCounters docstring promises only files
*older than* retentionDays are removed; a file at age == retentionDays
is not older than retentionDays, so it must survive.

Switch the comparison to `>=` so only strictly older files fall
through to deletion. Add a regression test that pins boundary-aged
files (mtimeMs sitting just past the projected cutoff) are preserved.

Refs affaan-m#2156
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

suggest-compact: counter temp files never cleaned up, and count resets every /compact

2 participants