Skip to content

fix(state): one store per project; hooks never create one; bounded touch trail - #42

Merged
Megaprompting merged 1 commit into
mainfrom
fix/store-split
Oct 27, 2026
Merged

Megaprompting merged 1 commit into
mainfrom
fix/store-split

Conversation

@Megaprompting

Copy link
Copy Markdown
Owner

What was wrong

One project was getting two stores. baseDir() honored $CLAUDE_PLUGIN_DATA. Claude Code sets that variable for hooks and the MCP server, but never for the model's own shell. As a result:

  • every hook write went to a shadow store that the CLI never reads;
  • the stop-check nag also read the shadow store, so /ratchet:compile could never clear it.

Two more problems came from the same hooks.

  • Hooks created stores everywhere. They created a store in every directory a session opened in.
  • touchedFiles had no bound. It only ever grew, and every edit rewrote the whole list.

Readback from one live install, 2026-10-27:

What Count
Stores under the plugin data dir 992
Those holding anything besides hook touch rows 0
Rows in one store's touchedFiles 22,761 (4.4 MB)

What this changes

  • One base for every caller. The base is now $RATCHET_DATA_DIR, else ~/.ratchet, and $CLAUDE_PLUGIN_DATA is ignored (src/state.js). Stores already under it are no longer read, and nothing moves or deletes them.
  • Hooks never create a store. session-start, post-edit and stop-check act only on a store that already exists, so a project gets a store only where someone ran ratchet (src/cli.js).
  • A bounded trail. The new verbs.recordTouch keeps only the newest touch of each path, newest last, up to TOUCHED_FILES_MAX (500) (src/verbs.js).
    • The cap covers the hook, ratchet touch and state append touchedFiles.
    • An oversized trail collapses on its next touch.
    • The receipt's "touched since compile" set is unchanged, because it only ever read each path's newest touch.
  • Docs. README State location, SECURITY.md and a CHANGELOG entry under Unreleased → Fixed.

Proof

  • New tests. test/cli.test.js adds three, and each went red on the unpatched tree:
    • hooks and the CLI resolve one store;
    • hooks create no store and print nothing in a project without one;
    • touchedFiles stays bounded and keeps one row per path.
  • Adjusted test. The existing propose-only hook test now creates its store first. Without a store there is no write for the guard to refuse.
  • Full suite. npm test passes all 16 suites on this branch's tree, with exit 0.
  • Real data. On a copy of the real 4.4 MB store, the first touch shrinks it from 22,782 rows to 500 rows and 96 KB. The pre-fix build kept every row.

Trade-offs

  • The cap can drop paths from the receipt. 500 distinct paths is a judgement call. A project that touches more than 500 distinct files between compiles loses the oldest from its "touched since compile" list.
  • Old stores are only reachable by hand. A user whose records exist only under $CLAUDE_PLUGIN_DATA must set RATCHET_DATA_DIR to that path to read them.

🤖 Generated with Claude Code

…uch trail

baseDir() honored CLAUDE_PLUGIN_DATA. Claude Code sets it for hooks and
the MCP server but never for the model's own shell, so hook writes went
to a shadow store that the CLI never read. The stop-check nag read the
shadow too, so /ratchet:compile could never clear it. The base is now
RATCHET_DATA_DIR, else ~/.ratchet, for every caller.

Hooks now act only on a store that already exists. session-start and
stop-check used to create a store in every directory a session opened
in: one machine had 992 empty stores.

touchedFiles keeps only the newest touch of each path, newest last, up to
TOUCHED_FILES_MAX (500). The cap covers the hook, ratchet touch and
state append. One store had reached 22,761 rows (4.4 MB), and every
edit rewrote all of it. On a copy of that store, the first touch shrinks
it to 96 KB.

Three new tests in test/cli.test.js went red on the unpatched tree.
npm test passes all 16 suites on an export of this commit's index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Megaprompting
Megaprompting merged commit ed6ed86 into main Oct 27, 2026
6 checks passed
Megaprompting pushed a commit that referenced this pull request Oct 27, 2026
CHANGELOG [Unreleased] -> Fixed conflict only: keep both entries (discover
probe first, #42 store split second); no other text changed.

Co-Authored-By: Claude Opus 5.5 <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.

2 participants