Local engine / Receipts
Receipts for Claude Code turns
After each Claude Code turn, see what changed, what it changed that you didn’t ask for and what to try by hand. Built on your machine from git and the code graph.
On this page
What a receipt shows
With receipts on, Arbor writes a short account after each Claude Code turn that changes files in a git repository. It lists the files and functions the turn changed, flags changed files your request didn't mention, counts what else uses the changed code and suggests what to try by hand. Arbor builds it from git and the local graph. No model is called and nothing is sent over the network.
Arbor receipt · "make tokenize skip blank lines"
Changed 2 files · 4 functions
⚠ Not in your request (1):
src/auth.py · Sign in · verify_session, sign_in
Undo it: `arbor receipt undo 20260929T235026-959cc81d --unasked`
Could affect: Sign in · 1 other function uses this code
Test before you ship:
1. Sign out, then sign back in
Saved as 20260929T235026-959cc81d · `arbor receipt show`Changed- Files the turn changed and the functions in the changed lines. Edits you made before sending the request are left out, even in a dirty tree. A function within three lines of an edit (git’s default diff context) also counts, so this receipt counts tokenize and parse_file, which starts three lines below the edit, and both functions in src/auth.py.
Not in your request- Changed files that share no word with your request and aren’t one call away from a file that does. Test files and common package manifests and lockfiles (package.json, Cargo.toml, pyproject.toml, requirements.txt, go.mod, Gemfile, composer.json, pubspec.yaml and their lockfiles) are never flagged. The heading turns into “⚠ Not in your request” when one of these files touches sign in, payments, the database or app settings.
Could affect- Pages and API routes from file-based routers (Next.js, SvelteKit, Remix, Nuxt), areas such as sign in, payments, the database, emails, file uploads and admin, and how many other functions reach the changed code within four calls. Routes declared in code, as in Express or FastAPI, are not named. With --json, affects.entry_points lists functions Arbor guesses are entry points from their name and file, such as main or a *_handler in a routes file; others can be missing.
Test before you ship- Up to four things to try by hand. Areas touched by files outside your request come first. It is a short starting list, not a test plan; run your tests as well.
Note- What the receipt couldn’t check. Without an index it lists files only. A request that names nothing it can match, such as “fix the bug”, flags nothing and says so.
Turn receipts on
Receipts need a git repository and, for function-level detail, an index. Run these from the project root:
cd your-project
arbor setup
arbor hook claude ✓ created …/your-project/CLAUDE.md
✓ settings written to …/your-project/.claude/settings.json
✓ Arbor wired into Claude Code at …/your-projectRunning it again reports already up to date and adds nothing twice. With --global, it writes ~/.claude/CLAUDE.md and ~/.claude/settings.json instead of the project's files. Either way, it changes two files:
CLAUDE.md- A marked “Code Navigation (MANDATORY)” block that tells Claude to explore with arbor commands. Arbor edits the root CLAUDE.md, else .claude/CLAUDE.md, else creates one at the root. Running the command again replaces the block in place.
settings.json · UserPromptSubmit- arbor receipt begin snapshots the working tree when you send a request. It prints nothing unless it fails.
settings.json · Stop- arbor receipt end --hook saves the receipt when Claude finishes. Claude Code shows it to you.
settings.json · PreToolUse- Two Bash hooks meant to run arbor init and to block rg, recursive grep and find. Neither takes effect in v3.0.3; see the known problems below.
settings.json · PostToolUse- Once a day, after a Bash call, prints arbor map . --exclude-test and leaves a .arbor/.map-injected-YYYYMMDD flag file.
settings.json · permissions- Claude may run arbor query, file-graph, callers, callees, map, path, inspect, diff, entry-points, refactor, export, receipt list and receipt show without asking. undo is not on the list, so Claude Code asks you first.
The hook does not register the MCP server. For that, see Claude Code in the MCP guide.
List, show and undo
arbor receipt list
arbor receipt show
arbor receipt show 20260929T235026 --json
arbor receipt undo 20260929T235026 --unasked20260929T235026-959cc81d 2026-09-29 23:50 "make tokenize skip blank lines" · 2 files · ⚠ 1 file not asked forreceipt list- Newest first, 20 by default, with times in UTC. --limit N changes the count and --json prints the saved records. With none saved, it tells you to run arbor hook claude and ask your agent for a change.
receipt show [id]- One receipt in full, the latest when you give no id. A prefix of the id is enough; if several match, the newest wins. Pass a project with --path, not as a positional argument.
receipt undo <id> [files]- Puts files back as they were before the turn: all of them, the files you name, or --unasked for only those outside your request. New files are removed, deleted ones return and renames are reversed. Only the working tree changes; staged changes and commits stay.
receipt begin / receipt end- Run by the hooks. end without --hook prints the receipt in the terminal.
Undo won't throw away later work. If any file it would touch changed after the turn, it changes nothing. In the example below, the first command puts back src/auth.py, the file outside the request, and src/parser.py was edited after the turn. Both now differ from how the turn left them, so undoing the whole turn is refused:
$ arbor receipt undo 20260929T235026 --unasked
Put back src/auth.py
$ arbor receipt undo 20260929T235026
error: src/auth.py, src/parser.py changed after this turn, so undoing would lose that work. Nothing was undone; add --force to undo anyway.--force undoes anyway and discards those later edits. Undo also needs the turn's snapshots. Nothing references them, so git gc removes them after about two weeks; the receipt stays readable but can't be undone.
What is stored, and where
Receipts stay in your project, and Arbor sends them nowhere. Claude Code shows you each one when a turn ends. Because arbor hook claude lets Claude run arbor receipt list and arbor receipt show without asking, Claude can read them too, including your earlier requests. What those commands print reaches the model Claude Code uses, like any other command output.
.arbor/receipts/<id>.json- One JSON file per receipt: your request text (first 2,000 characters), the agent, start and finish times, each changed file with its functions, areas, line counts and scope flag, what it could affect, the things to try, notes, and two snapshot ids for undo. No command deletes them; remove old files yourself.
.arbor/receipts/turns/<session>.json- The turn in progress: a snapshot id, your request text, the start time and the agent. Deleted when the Stop hook runs, whether or not a receipt is written.
.git/objects- Each snapshot writes the working tree into your repository’s object store through a private index. That includes untracked files that aren’t gitignored, such as an unignored .env. Your index, branches and stash are untouched, and git gc removes these unreferenced objects after about two weeks.
.arbor/ graph files- config.json, graph.json, graph.bin, graph.head, graph.builder and cache/ come from indexing. The graph holds names, paths, signatures and docstrings from your source.
The request text deserves the most care: it is whatever you typed to Claude Code, and v3.0.3 does not add .arbor/ to your .gitignore.
Known problems in v3.0.3
- The hooks run whichever
arborcomes first on PATH (#232). An older build, such as one left in~/.cargo/binby an earliercargo install, has noreceiptcommand. It fails witherror: unrecognized subcommand 'receipt'and no receipt is written. Runarbor --versionin the shell Claude Code uses, then remove the old executable or put v3.0.3 first on PATH. .arbor/is not gitignored (#233).git statuslists?? .arbor/, sogit add -Awould commit your graph and your request text. Add.arbor/to.gitignore. Receipts keep working, because snapshots already leave.arbor/out.- Nothing is actually blocked (#231). The CLAUDE.md block tells Claude that grep, rg and find are blocked by hooks. The PreToolUse hooks behind that line read
$CLAUDE_TOOL_INPUTand exit with status 1. Claude Code passes hook input on standard input, not in that variable, and treats exit status 1 as a non-blocking error. Treat the line as guidance. - An unreadable
CLAUDE.mdorsettings.jsonis replaced. If either file exists but can't be read as UTF-8 text, for example because it was saved as UTF-16,arbor hook claudetreats it as empty and writes Arbor's content in its place. Back up both files before you run it. A fix that leaves such files untouched is on the engine's main branch and not yet released. - Outside a git repository, nothing is recorded.
arbor receipt beginreturns without a receipt or a reason. A fix that states the reason is on the engine's main branch and not yet released. - Scope matching is by words. A request that names a page, file or function works best. For “make it better”, the receipt notes that it couldn't match your request and flags nothing.