Skip to content

fix(tracking): record stats for hook-rewritten commands (#1082)#2483

Open
ousamabenyounes wants to merge 5 commits into
rtk-ai:developfrom
ousamabenyounes:fix/issue-1082
Open

fix(tracking): record stats for hook-rewritten commands (#1082)#2483
ousamabenyounes wants to merge 5 commits into
rtk-ai:developfrom
ousamabenyounes:fix/issue-1082

Conversation

@ousamabenyounes

Copy link
Copy Markdown
Contributor

Fixes #1082.

rtk gain stats were silently missing for commands rewritten via the Claude Code hook. The hook spawns a second rtk process while another holds the SQLite history DB, so the tracking write hit lock contention. Two problems compounded it: busy_timeout and journal_mode=WAL were set in a single execute_batch() call, so a transient WAL error left busy_timeout at 0 — and with busy_timeout=0 every contended write failed immediately with SQLITE_BUSY and was dropped (let _ = tracker.record(...)).

Fix

Set PRAGMA busy_timeout=5000 separately from journal_mode=WAL so the timeout is always applied, and add a bounded retry around the record path. A contended write now waits for the lock instead of being lost.

Test verification (RED → GREEN)

test_record_waits_for_concurrent_write_lock reproduces real contention: a background thread holds the write lock (BEGIN IMMEDIATE) while the tracker attempts its INSERT.

RED — busy_timeout reverted to 0 (the bug):

test core::tracking::tests::test_record_waits_for_concurrent_write_lock ... FAILED
record must wait for the concurrent lock and succeed (busy_timeout): database is locked
test result: FAILED. 0 passed; 1 failed

GREEN — with busy_timeout=5000:

test core::tracking::tests::test_record_waits_for_concurrent_write_lock ... ok
test result: ok. 1 passed; 0 failed

Full core::tracking module: 15 passed, 0 failed (no baseline regression).

(Re-proposes the accidentally-closed #1223, rebased onto current develop. The original branch's test wrote two trackers sequentially and so did not actually fail without the fix; it has been replaced with the lock-contention test above.)

ousamabenyounes and others added 5 commits June 17, 2026 22:38
When Claude Code rewrites `git status` → `rtk git status` via its
PreToolUse hook, the hook first runs `rtk rewrite` which spawns a
background telemetry thread holding a brief DB connection. If that
process exits before the thread finishes, SQLite's WAL shared-memory
file (.db-shm) may be inconsistent for a few milliseconds.

Because `execute_batch()` stops on first error, bundling
`PRAGMA journal_mode=WAL` and `PRAGMA busy_timeout=5000` in one call
meant that a transient WAL-mode error silently left `busy_timeout=0`.
With no wait budget, any lock-contention attempt in `Tracker::new()`
failed immediately, silently dropping the tracking record.

Fix two things:
1. Run `PRAGMA busy_timeout=5000` in its own `execute_batch()` call so
   it is always applied, regardless of whether the WAL pragma succeeds.
2. Add a single retry (10 ms sleep) in `TimedExecution::track()` and
   `track_passthrough()` to outlast the transient .db-shm window.

Add regression tests for concurrent-open correctness and for the full
hook-rewrite tracking path.

Fixes rtk-ai#1082

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous test_busy_timeout_set_independently_of_wal_mode opened two
trackers but wrote to them sequentially, so it passed even with the
busy_timeout pragma removed — it did not actually guard the fix.

Replace it with test_record_waits_for_concurrent_write_lock: a background
thread holds the SQLite write lock (BEGIN IMMEDIATE) while the tracker
attempts its INSERT. With busy_timeout=5000 the write waits and succeeds;
with busy_timeout=0 (the rtk-ai#1082 bug) it fails immediately with SQLITE_BUSY.
Add a shared DB_ENV_LOCK so RTK_DB_PATH-mutating tests don't race.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lock-contention test set the process-global RTK_DB_PATH, which raced
the other tracking tests that use the default DB under `cargo test --all`
(test_timed_execution_records_time failed intermittently in CI).

Extract Tracker::open_at_path(&Path) — shared by new() — so the test can
target an isolated temp file without mutating any global env var.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lint

The semgrep filesystem-deletion rule flagged the explicit fs::remove_file
cleanup. Switch to tempfile::tempdir() (the repo's existing convention),
which auto-removes on drop without a literal deletion call.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

rtk gain stats not recorded when commands are rewritten via Claude Code hook

1 participant