diff --git a/docs/design/0001-evidence-ledger.md b/docs/design/0001-evidence-ledger.md index 294e5711..35cbe830 100644 --- a/docs/design/0001-evidence-ledger.md +++ b/docs/design/0001-evidence-ledger.md @@ -1259,7 +1259,7 @@ The domain rules are owned, specified and tested by the area design documents ([ See [Threat model](#threat-model) for who is defended against. -- **Agent isolation (EVD-R24).** Agents run as the owner's user, so file modes cannot keep them out. The barrier is Claude Code's, and three mechanisms carry it: the sandbox, which denies shell commands and their child processes reads and writes of Baley's home and its config folder; the `Read` and `Edit` deny rules, which cover its built-in file tools; and the guard's refusal of Read, Grep and Glob calls into either folder. Claude Code applies `Read` rules to Grep and Glob only on a best-effort basis, which is why the guard refusal is needed. An unavailable sandbox is reported, never passed over. The owner's own Claude Code settings can loosen the barrier, and `baley doctor` reports what an agent can reach. The settings content that carries the sandbox and the deny rules, and the per-tool coverage judgement over a settings document, are rendered in the library (`crates/baley/src/host_artifacts/security.rs:142-199`, `crates/baley/src/host_artifacts/coverage.rs:281-356`); Build 3 T15 applies the content and T12 measures what Claude Code does with it. With these in place, the ledger and global settings are out of an agent's reach. API keys stay in the owner's environment, outside Baley's folders and outside this protection ([0003](0003-configuration-and-routing.md), CFG-R24, [ADR 0039](../adr/0039-session-owned-provider-credentials.md)). Baley does not claim to hide the ledger from a process running as the owner outside the host. What Baley guarantees whether or not reads are denied: writes are refused, tampering is detected by the chain and the anchors, Baley records no credential values by design, sensitive returned content is kept in payloads, and payloads can be purged. The 2026-09-25 host matrix showed this for a stand-in home through Bash: the agent was blocked from reading and writing while Baley's server and hook still wrote. The extended probe adds the config folder and the built-in file tools. The guard also refuses file-tool writes into the home and the config folder, as a best-effort layer. +- **Agent isolation (EVD-R24).** Agents run as the owner's user, so file modes cannot keep them out. The barrier is Claude Code's, and three mechanisms carry it: the sandbox, which denies shell commands and their child processes reads and writes of Baley's home and its config folder; the `Read` and `Edit` deny rules, which cover its built-in file tools; and the guard's refusal of Read, Grep and Glob calls into either folder. Claude Code applies `Read` rules to Grep and Glob only on a best-effort basis, which is why the guard refusal is needed. An unavailable sandbox is reported, never passed over. The owner's own Claude Code settings can loosen the barrier, and `baley doctor` reports what an agent can reach. The settings content that carries the sandbox and the deny rules, and the per-tool coverage judgement over a settings document, are rendered in the library (`crates/baley/src/host_artifacts/security.rs:142-199`, `crates/baley/src/host_artifacts/coverage.rs:281-356`); Build 3 T15 applies the content, and the 2026-10-08 runs measured what Claude Code 2.1.294 does with it (see the host matrix below, first sheet `claude-live-2026-10-08-run1.md#bar.home.bash-read`). With these in place, the ledger and global settings are out of an agent's reach. API keys stay in the owner's environment, outside Baley's folders and outside this protection ([0003](0003-configuration-and-routing.md), CFG-R24, [ADR 0039](../adr/0039-session-owned-provider-credentials.md)). Baley does not claim to hide the ledger from a process running as the owner outside the host. What Baley guarantees whether or not reads are denied: writes are refused, tampering is detected by the chain and the anchors, Baley records no credential values by design, sensitive returned content is kept in payloads, and payloads can be purged. The 2026-09-25 host matrix showed this for a stand-in home through Bash: the agent was blocked from reading and writing while Baley's server and hook still wrote. The 2026-10-08 runs of the built binary added the config folder and the built-in file tools. Both folders behaved alike through Bash and its children, Read, Write and Edit naming a file there were refused by the deny rules before the hook ran, and Grep and Glob are not in Claude Code 2.1.294's default tool list, so those two runs could not test them (`claude-live-2026-10-08-run1.md#bar.config.bash-read`, `#bar.config.read-tool`, `#bar.config.write-tool`). The third run launched a session with `--allowedTools Grep,Glob`, which offers them. The guard refused both tools over each folder itself and over its parent, for the home and for the config folder, and nothing of the folder came back (`claude-live-2026-10-08-run3.md#bar.home.grep-folder`, `#bar.home.grep-parent`, `#bar.home.glob-folder`, `#bar.home.glob-parent`, `#bar.config.grep-folder`, `#bar.config.grep-parent`, `#bar.config.glob-folder`, `#bar.config.glob-parent`). Over the folder itself the guard's refusal came before the best-effort `Read` rule answered, so that rule's own reach over Grep and Glob was not measured and stays unverified. The guard also refuses file-tool writes into the home and the config folder, as a best-effort layer. In the runs the deny rules refused those writes first, so the guard's refusal was not reached. - **Detection.** Anchors on the forge make a rewrite, truncation or rollback detectable up to the latest anchor, even by an agent that can run `sqlite3` and sign with the owner's key. - **Secrets.** Test output and review material can carry secrets. They are always payloads, so they can always be purged, with the limits stated in [Payloads, retention and purge](#payloads-retention-and-purge-evd-r11-evd-r14). Baley reads no API key and cannot promise to recognize or scrub one a command prints into returned content. Every program Claude Code starts can see keys in the environment, agents and subagents included. Code sent for review goes to the chosen provider under its terms (CFG-R28). - **Network.** The store makes no network calls. Anchors are pushed with git to the project's own remote, and the latest anchor is fetched from it, through the core's forge seam; the binary implements that seam, and the store only receives the fetched anchor. A remote is named in the ledger by its configured name, never by URL or credentials. Git runs without prompts. The remote is the project's `git.remote`, from the committed `baley.toml`, and must be configured in the repository at the current directory. @@ -1375,13 +1375,14 @@ Claude Code is the only supported host. A host is added when the matrix shows th | Concern | Claude Code | Evidence | |---|---|---| -| Project discovery from the working directory | Hook and MCP server start in the session's directory, which may be a subdirectory, and Claude Code gives both `CLAUDE_PROJECT_DIR` | Shown on 2026-09-25, and again on 2026-10-02 for a stdio server: Claude Code starts the MCP server in the session's directory. Its documentation does not promise that directory, so the server takes its project from `CLAUDE_PROJECT_DIR` and records its working directory beside it, rather than binding the directory it starts in. The hook and the server both walk up from `CLAUDE_PROJECT_DIR`, and the hook keeps its working directory for the paths a tool names and the branch it reads (Build 3). | -| Hook contract (tool names, event names, answer format) | `PreToolUse` with the matcher `Bash\|Monitor\|PowerShell\|Read\|Grep\|Glob\|Write\|Edit\|NotebookEdit`, answering `ask` and `deny` in `hookSpecificOutput`, and a pass with no output | Shown for Bash on 2026-09-25: the hook's input needs no adapter. The answer fields follow Claude Code's hooks reference. The library renders the hook with this matcher (`crates/baley/src/host_artifacts/hook.rs:13-37`). The other tools, and how Claude Code handles each answer, are measured by the extended probe in Build 3 T12. | -| Re-delivered tool calls | Retries after timeout | The hook answers a call whose host, session and `tool_use_id` it already answered from the `guard` view in `user`, when the input digest, project directory and working directory match, with no git or policy read. A reused call id with other input is answered on its own and is unrecordable, so its ask is denied ([0010](0010-guard.md), GRD-R9, GRD-R10). A live redelivery is observed by Build 3 T12. Proven for MCP calls in Build 1, where the `request` view lands. | -| Access to Baley's home and config folder | Server and hook write. Agents are denied reads and writes of both | The home was shown through Bash on 2026-09-25: a denied read looks like a missing file, a denied write exits 0 and nothing lands. The config folder and the built-in file tools are measured before release by Build 3 T12, and again before each release. The sandbox and deny-rule content for both folders is rendered in the library, and the coverage judge reports from a settings document which of the three mechanisms covers each tool's reads and writes (`crates/baley/src/host_artifacts/security.rs:142-199`, `crates/baley/src/host_artifacts/coverage.rs:281-356`). | -| Store unreachable | The decision is unrecordable (GRD-R9): an ask is denied, a deny stands and a pass on failure stays a loud pass. A plain pass needs no store | Judged in tests from the store's busy and needs-rebuild answers and from a transaction that fails after its answer was staged. The hook's times under live lock contention are measured by Build 3 T12. | +| Project discovery from the working directory | Hook and MCP server start in the session's directory, which may be a subdirectory, and Claude Code gives both `CLAUDE_PROJECT_DIR` | Shown on 2026-09-25, and again on 2026-10-02 for a stdio server: Claude Code starts the MCP server in the session's directory. Its documentation does not promise that directory, so the server takes its project from `CLAUDE_PROJECT_DIR` and records its working directory beside it, rather than binding the directory it starts in. The hook and the server both walk up from `CLAUDE_PROJECT_DIR`, and the hook keeps its working directory for the paths a tool names and the branch it reads (Build 3). Measured on 2026-10-08 against the built binary through an explicit `--mcp-config` launch: `CLAUDE_PROJECT_DIR` and the server's recorded working directory were both the session's project folder, and for a session started in the project's `sub` folder both were that `sub` folder, read from `project_directory` and `working_directory` on the server callers in the ledger (first sheet `claude-live-2026-10-08-run1.md#id.explicit.startup`, `#id.two-sessions`, and the second run's reads in `claude-live-2026-10-08-run2.md#post-run-reads`). The server started in `sub` bound the project found through the nearer `baley.toml` (`claude-live-2026-10-08-run1.md#hand.nearer-file`). The first two runs made no login into an isolated configuration, so the user scope was not measured in them. The third run did, with the owner's login: a session started in the project's `sub` folder with no `--mcp-config`, whose `/mcp` panel listed `baley` from the user scope, had `CLAUDE_PROJECT_DIR` and the server's recorded working directory both equal to that `sub` folder, read from the server caller in the ledger (`claude-live-2026-10-08-run3.md#id.user-scope.startup`, `#ses.b.registered`, `#ses.b.login`). Its explicit launch again gave the project folder for both (`claude-live-2026-10-08-run3.md#id.explicit.startup`). | +| Hook contract (tool names, event names, answer format) | `PreToolUse` with the matcher `Bash\|Monitor\|PowerShell\|Read\|Grep\|Glob\|Write\|Edit\|NotebookEdit`, answering `ask` and `deny` in `hookSpecificOutput`, and a pass with no output | Shown for Bash on 2026-09-25: the hook's input needs no adapter. The answer fields follow Claude Code's hooks reference. The library renders the hook with this matcher (`crates/baley/src/host_artifacts/hook.rs:13-37`). Measured on 2026-10-08 with the built hook on Claude Code 2.1.294 (first sheet `claude-live-2026-10-08-run1.md#ctl.hook.bash`, `#ctl.hook.monitor`, `#ctl.hook.read`): it fired for Bash, Monitor and Read. Claude Code obeyed `deny` for a commit on `main` through Bash and through Monitor, and again through Bash in the second run (`claude-live-2026-10-08-run1.md#ctl.commit-main-bash`, `#ctl.commit-main-monitor`, `claude-live-2026-10-08-run2.md#ses.a.smoke-commit`). It obeyed `ask` for a push through both: a yes ran the push and a no left the remote's refs unchanged (`claude-live-2026-10-08-run1.md#ctl.push-bash-yes`, `#ctl.push-bash-no`, `#ctl.push-monitor-yes`, `#ctl.push-monitor-no`). A pass with no output let a commit with shell structure the scanner declines run (`claude-live-2026-10-08-run1.md#ctl.declined-syntax`). In the first run, Write, Edit and NotebookEdit calls into the two folders never reached the hook, because Claude Code's deny rules and its read-first check refuse them first (`claude-live-2026-10-08-run1.md#ctl.hook.write`, `#ctl.hook.edit`, `#ctl.hook.notebookedit`). In the third run the same three tools were called on unprotected files in the project, and the hook fired once for each, the guard answered none, and the file followed each call (`claude-live-2026-10-08-run3.md#ctl.hook.write`, `#ctl.hook.edit`, `#ctl.hook.notebookedit`). Grep and Glob are not in Claude Code 2.1.294's default tool list, so the first two runs had no hook line for them. In the third run, in a session launched with `--allowedTools Grep,Glob`, the hook fired four times for each and the guard denied all eight calls, and the guard's highest time in that run, for any tool, was 49 ms (`claude-live-2026-10-08-run3.md#ctl.hook.grep`, `#ctl.hook.glob`, `#ctl.latency`). `pwsh` was absent in every run, so the hook on PowerShell is unverified (`claude-live-2026-10-08-run1.md#ctl.hook.powershell`, `claude-live-2026-10-08-run3.md#ctl.hook.powershell`). The `tool_input` field names were read from the stand-in probe's hook input (`claude-live-2026-10-08-run1.md#fld.bash`, `#fld.monitor`, `#fld.monitor-watch`, `#fld.read`, `#fld.write`, `#fld.edit`, `#fld.notebookedit`), and for Grep (`pattern`, `path`) and Glob (`pattern`, `path`) from a stand-in session launched with `--allowedTools Grep,Glob` (`claude-live-2026-10-08-run3.md#fld.grep`, `#fld.glob`). The `/hooks` panel does not list a hook given through `--settings`, though the hook fires (`claude-live-2026-10-08-run1.md#ses.a.panels`). | +| Re-delivered tool calls | Retries after timeout | The hook answers a call whose host, session and `tool_use_id` it already answered from the `guard` view in `user`, when the input digest, project directory and working directory match, with no git or policy read. A reused call id with other input is answered on its own and is unrecordable, so its ask is denied ([0010](0010-guard.md), GRD-R9, GRD-R10). No `tool_use_id` arrived twice in either 2026-10-08 run, so a live redelivery was not observed and stays unverified (`claude-live-2026-10-08-run1.md#ctl.redelivery`, and the timing read in `claude-live-2026-10-08-run2.md#post-run-reads`). Proven for MCP calls in Build 1, where the `request` view lands. | +| Access to Baley's home and config folder | Server and hook write. Agents are denied reads and writes of both | The home was shown through Bash on 2026-09-25: a denied read looks like a missing file, a denied write exits 0 and nothing lands. Measured on 2026-10-08 against the built binary for the config folder as well as the home, and measured again before each release. Through Bash, a `sh -c` child and a script child, a denied read looked like a missing file and a denied write exited 0 with nothing landing (first sheet `claude-live-2026-10-08-run1.md#bar.home.bash-read`, `#bar.home.bash-write`, `#bar.config.bash-read`, `#bar.config.bash-write`, and the child and script rows beside them). Claude Code's own permission check refused a Monitor command that named a file in either folder, and a child script that Monitor ran was sandboxed (`claude-live-2026-10-08-run1.md#bar.home.monitor-read`, `#bar.home.monitor-child-read`). Read, Write and Edit naming a file there were refused by the deny rules before the hook (`claude-live-2026-10-08-run1.md#bar.home.read-tool`, `#bar.home.write-tool`, `#bar.home.edit-tool`, and the same rows for the config folder). NotebookEdit was refused by the read-first check, so the Edit rule's reach over it is not shown (`claude-live-2026-10-08-run1.md#bar.home.notebook-edit`). Grep and Glob over the folder itself are labelled best-effort. They are not in Claude Code 2.1.294's default tool list, so the first two runs could not test them. In the third run, in a session launched with `--allowedTools Grep,Glob`, the guard refused both tools over each folder itself and over its parent, for the home and for the config folder, and nothing of the folder came back (`claude-live-2026-10-08-run3.md#bar.home.grep-folder`, `#bar.home.grep-parent`, `#bar.home.glob-folder`, `#bar.home.glob-parent`, `#bar.config.grep-folder`, `#bar.config.grep-parent`, `#bar.config.glob-folder`, `#bar.config.glob-parent`). Over the folder itself the guard answered before the `Read` rule, so that rule's own reach over Grep and Glob was not measured. The PowerShell rows are unverified because `pwsh` was absent (`claude-live-2026-10-08-run1.md#bar.home.ps-read`, `claude-live-2026-10-08-run3.md#bar.home.ps-read`). Without `bwrap` and `socat` Claude Code refused to start the session (`claude-live-2026-10-08-run1.md#prot.absent-sandbox`), and the server and the hook wrote the denied home in all three runs (`claude-live-2026-10-08-run1.md#prot.server-writes-home`, `#prot.hook-writes-home`, `claude-live-2026-10-08-run2.md#prot.server-writes-home`, `claude-live-2026-10-08-run3.md#prot.server-writes-home`, `#prot.hook-writes-home`). The sandbox and deny-rule content for both folders is rendered in the library, and the coverage judge reports from a settings document which of the three mechanisms covers each tool's reads and writes (`crates/baley/src/host_artifacts/security.rs:142-199`, `crates/baley/src/host_artifacts/coverage.rs:281-356`). | +| Store unreachable | The decision is unrecordable (GRD-R9): an ask is denied, a deny stands and a pass on failure stays a loud pass. A plain pass needs no store | Judged in tests from the store's busy and needs-rebuild answers and from a transaction that fails after its answer was staged. The hook's times were measured on 2026-10-08 while other sessions exited: guard calls took 46 to 47 ms in the first run and 45 to 47 ms in the second (54 to 58 ms with the wrapper that timed them), and the highest guard time of each run was 48 ms and 49 ms, far below the host's 10-second timeout (`claude-live-2026-10-08-run1.md#ctl.contention-exit`, `#ctl.latency`, `claude-live-2026-10-08-run2.md#ctl.contention-exit`, and the timing read in `claude-live-2026-10-08-run2.md#post-run-reads`). No call reached the timeout, so what Claude Code does with a hook that times out is unverified (`claude-live-2026-10-08-run1.md#ctl.timeout-not-denial`). | +| Skill loaded from an uncommitted folder (ADR 0009) | A rendered stub placed as `SKILL.md` in an uncommitted skills folder is expected to be listed and to run `baley_query` with per-call approval | Shown on 2026-10-08 in the third run, in both placements. The stub was placed as `claude-config/skills/bal-help/SKILL.md` in the isolated configuration, and the first run's write rows read it back unchanged (`claude-live-2026-10-08-run1.md#prot.stub.write-tool`, `#prot.stub.bash-write`). Session B, after the owner's login, listed `bal-help` as a user skill, and running it called `baley_query` for the instruction and then for help, each call with its own permission prompt (`claude-live-2026-10-08-run3.md#hand.skill-listed`, `#hand.skill-run`). The same stub placed uncommitted at the disposable project's `.claude/skills/bal-help/SKILL.md` was listed as a project skill and ran the same way, and the folder was removed afterwards (`claude-live-2026-10-08-run3.md#hand.skill-project-listed`, `#hand.skill-project-run`). The question ADR 0009 left open, whether a skill loads from a rendered, uncommitted folder, is answered yes for both placements on Claude Code 2.1.294. | -**How it was measured.** The probes in [`spikes/host-matrix`](../../spikes/host-matrix/README.md) started with one throwaway non-interactive session per host from a subdirectory of this repository, with a hook and a stand-in MCP server that recorded where they ran and whether they could write a stand-in home outside the checkout, and an agent that was asked to read and write that home. The 2026-09-25 run used Codex CLI 0.156.1 and Claude Code 2.1.282 on Linux. Its Codex results are why Codex was removed (ADR 0033). The Claude Code probe now prepares separate stand-in home and config folders and their settings, and prints a procedure for an interactive session the owner runs. It covers shell commands, their children and the built-in file tools. Build 3 T12 runs it first, and it runs again before each release. The Codex probe stays, to measure Codex against the bar. +**How it was measured.** The probes in [`spikes/host-matrix`](../../spikes/host-matrix/README.md) started with one throwaway non-interactive session per host from a subdirectory of this repository, with a hook and a stand-in MCP server that recorded where they ran and whether they could write a stand-in home outside the checkout, and an agent that was asked to read and write that home. The 2026-09-25 run used Codex CLI 0.156.1 and Claude Code 2.1.282 on Linux. Its Codex results are why Codex was removed (ADR 0033). The Claude Code probe now prepares separate stand-in home and config folders and their settings, and prints a procedure for an interactive session the owner runs. It covers shell commands, their children and the built-in file tools, and it stays the instrument that records each tool's hook input. The built binary was then qualified against live Claude Code 2.1.294 on Linux on 2026-10-08, in three runs of interactive sessions driven by a coding agent through tmux, with every outcome read from files and the ledger and the login into the isolated configuration typed by the owner. The first run's sheet is [`claude-live-2026-10-08-run1.md`](../../spikes/host-matrix/claude-live-2026-10-08-run1.md), the second, a rerun of the exit and document parts after two bug fixes, is [`claude-live-2026-10-08-run2.md`](../../spikes/host-matrix/claude-live-2026-10-08-run2.md), and the third, a rerun after changes to the procedure that covers Grep and Glob through a launch that offers them, the user-scope registration, the skill in both placements, the hook on Write, Edit and NotebookEdit and a restore report, is [`claude-live-2026-10-08-run3.md`](../../spikes/host-matrix/claude-live-2026-10-08-run3.md). The host matrix runs again before each release. The PowerShell rows were not measured in any run and read unverified, because `pwsh` was absent. The Codex probe stays, to measure Codex against the bar. ### Compatibility and migration @@ -1462,7 +1463,7 @@ Builds: | EVD-R20 | Relies on SQLite's documented durability with `synchronous=FULL`. Power loss is not reproducible in a portable test and is not re-tested. | | EVD-R21 | `crates/baley-bench` measures real-adapter open, commits, keyed reads, guard store work, writer waits, rebuild total and batches, flip, size, purge, scrub and verification. Search, server start and memory are measured in the builds named in Performance. The open figure includes the checks. Timings are measurements, never test assertions. | | EVD-R23 | Withdrawn. | -| EVD-R24 | The host matrix, run by hand on Claude Code before acceptance and again before each release. The 2026-09-25 run, on both hosts at the time (`spikes/host-matrix`), covered the home through Bash only. Build 3 T12 measures the extended bar: the config folder and the built-in file tools. The coverage judge reports, from a settings document, per tool and per read or write which mechanism covers each folder and which cause leaves a gap, and its tests judge documents that drop the config folder, give write-only protection, guard Bash only, turn the sandbox off, exclude a command from the sandbox, or re-open a folder through an allow entry, whether it names the folder only once `..` is resolved or is a `~/` or relative entry it cannot judge (`crates/baley/src/host_artifacts/coverage.rs:281-356`). It judges configuration, not enforcement: T12's run measures the host. | +| EVD-R24 | The host matrix, run by hand on Claude Code before acceptance and again before each release. The 2026-09-25 run, on both hosts at the time (`spikes/host-matrix`), covered the home through Bash only. The 2026-10-08 runs of the built binary against Claude Code 2.1.294 measured the extended bar for the config folder and the built-in file tools as far as the host allowed: Bash and its children, Monitor, Read, Write and Edit held for both folders, and the guard's answers (a commit on `main` denied, a push asked) were obeyed through Bash and Monitor (`claude-live-2026-10-08-run1.md#bar.config.bash-read`, `#bar.config.read-tool`, `#ctl.commit-main-bash`, `claude-live-2026-10-08-run2.md#ses.a.smoke-commit`). The third run measured Grep and Glob in a session launched with `--allowedTools Grep,Glob`, where the guard refused them over each folder and over its parent (`claude-live-2026-10-08-run3.md#bar.config.grep-parent`, `#bar.config.glob-parent`, `#ctl.grep-parent-guard`, `#ctl.glob-parent-guard`), the hook on Write, Edit and NotebookEdit (`claude-live-2026-10-08-run3.md#ctl.hook.write`, `#ctl.hook.edit`, `#ctl.hook.notebookedit`), and the user-scope registration (`claude-live-2026-10-08-run3.md#id.user-scope.startup`). Still open and unverified: PowerShell (`pwsh` absent), the Edit rule over NotebookEdit, a live redelivery and a hook timeout. The coverage judge reports, from a settings document, per tool and per read or write which mechanism covers each folder and which cause leaves a gap, and its tests judge documents that drop the config folder, give write-only protection, guard Bash only, turn the sandbox off, exclude a command from the sandbox, or re-open a folder through an allow entry, whether it names the folder only once `..` is resolved or is a `~/` or relative entry it cannot judge (`crates/baley/src/host_artifacts/coverage.rs:281-356`). It judges configuration, not enforcement: the 2026-10-08 runs measured the host. | | EVD-R25 | From Build 9, `show` and the record export render every record family from views. | | EVD-R26 | The conformance suite proves the store half with supplied times and findings: commands outside an active claim's scope proceed, commands inside return Blocked before deciding, a retry returns InProgress, a clean failure completes the claim, an interrupted claim reconciles from a supplied finding, and an awaiting-owner claim refuses automatic reconciliation and accepts owner resolution. It also checks that a claim and its completion by two callers are each attributed to their own caller, that reconciliation refuses a caller, and that a caller-free reconciliation records none and copies none from the claim. The core's tests over the adapter prove the anchor call path in separate units: the claim step acts only after its claim event exists and names the pre-claim head; missing remote, in-progress, replayed and blocked requests do not act; refused and unreachable pushes complete the claim with `anchor.failed`; a successful record writes `anchor.pushed`, the completion and the row together, and a conflicting row leaves all three unwritten; matching, conflicting and absent holder tags reconcile and retry once under the original identity; an unreachable holder fetch writes one trace row and leaves the claim open; the heartbeat renews at once, ticks through the work and stops before the record step, and a failed renewal cancels nothing. The binary's git forge is tested over literal output and the process fake, and its ticker over a scripted pace and a ticker-local worker join seam. No test runs git. Build 6 proves a real revert held for the owner. | | EVD-R27 | An edited view document that says "approved" or "complete" does not grant admission or completion, because the event is missing. | diff --git a/docs/design/0010-guard.md b/docs/design/0010-guard.md index b1ce3013..56ef30c2 100644 --- a/docs/design/0010-guard.md +++ b/docs/design/0010-guard.md @@ -315,30 +315,30 @@ Not applicable. The guard gives no instructions; an agent sees only the host's o ## 11. Build status -`baley guard` runs the library hook (`crates/baley/src/main.rs:127`, `crates/baley/src/guard_hook/mod.rs:36-150`). It reads one call from standard input, binds the project from `CLAUDE_PROJECT_DIR`, judges the call with `baley-core`'s guard module (`crates/baley-core/src/guard/`) and the library's `hook_input` and `protected_paths` modules, records what it must in the per-user project `user`, and answers in Claude Code's hook form. The inherited guard is gone, and with it its `.planning` walk, its JSON settings, its `.planning`, `config.v4.json` and rendered-file protections, and its audit in the old store. What stays open is owned elsewhere: the placement projection that adds the placed stubs, settings and registration files and the executable to the protected list is built (`crates/baley/src/host_artifacts/placement.rs:271-296`) and T15 passes it to `judge`, the lease is Build 5's, Build 3 T12 observes how Claude Code handles each answer and a live redelivery, and the doctor is T13's. +`baley guard` runs the library hook (`crates/baley/src/main.rs:127`, `crates/baley/src/guard_hook/mod.rs:36-150`). It reads one call from standard input, binds the project from `CLAUDE_PROJECT_DIR`, judges the call with `baley-core`'s guard module (`crates/baley-core/src/guard/`) and the library's `hook_input` and `protected_paths` modules, records what it must in the per-user project `user`, and answers in Claude Code's hook form. The inherited guard is gone, and with it its `.planning` walk, its JSON settings, its `.planning`, `config.v4.json` and rendered-file protections, and its audit in the old store. What stays open is owned elsewhere: the placement projection that adds the placed stubs, settings and registration files and the executable to the protected list is built (`crates/baley/src/host_artifacts/placement.rs:271-296`) and T15 passes it to `judge`, the lease is Build 5's, and the doctor is T13's. The 2026-10-08 runs against Claude Code 2.1.294 showed how Claude Code handles the answers they reached (GRD-R12, first sheet `claude-live-2026-10-08-run1.md#ctl.commit-main-bash`) and saw no live redelivery (GRD-R10, `claude-live-2026-10-08-run1.md#ctl.redelivery`). | Requirement | Status | Where | |---|---|---| -| GRD-R1 | Partly built | One Claude Code `PreToolUse` entry runs `baley guard` with a 10-second timeout for `Bash\|Monitor\|PowerShell\|Read\|Grep\|Glob\|Write\|Edit\|NotebookEdit` (`hooks/hooks.json:3-14`), and the library classifies all nine tools (`crates/baley/src/hook_input/mod.rs:101-195`). The hook renderer gives the same item, with the nine-tool matcher as a constant, the guard's timeout and the absolute executable single-quoted before `guard` (`crates/baley/src/host_artifacts/hook.rs:13-43`), and a test holds the file's matcher and timeout to the renderer's (`crates/baley/src/host_artifacts/hook.rs:104-118`). The file stays hand-written, with a bare `baley guard` command, until T15 writes the hook from the renderer. Whether Claude Code's matcher accepts `Monitor` and `PowerShell` is observed by T12 | +| GRD-R1 | Partly built | One Claude Code `PreToolUse` entry runs `baley guard` with a 10-second timeout for `Bash\|Monitor\|PowerShell\|Read\|Grep\|Glob\|Write\|Edit\|NotebookEdit` (`hooks/hooks.json:3-14`), and the library classifies all nine tools (`crates/baley/src/hook_input/mod.rs:101-195`). The hook renderer gives the same item, with the nine-tool matcher as a constant, the guard's timeout and the absolute executable single-quoted before `guard` (`crates/baley/src/host_artifacts/hook.rs:13-43`), and a test holds the file's matcher and timeout to the renderer's (`crates/baley/src/host_artifacts/hook.rs:104-118`). The file stays hand-written, with a bare `baley guard` command, until T15 writes the hook from the renderer. In the 2026-10-08 runs the rendered nine-tool hook fired for `Bash`, `Monitor` and `Read` (first sheet `claude-live-2026-10-08-run1.md#ctl.hook.bash`, `#ctl.hook.monitor`, `#ctl.hook.read`). In the first run `Write`, `Edit` and `NotebookEdit` never reached the hook, because Claude Code's deny rules and its read-first check refused every call the procedure sent into the two folders (`claude-live-2026-10-08-run1.md#ctl.hook.write`, `#ctl.hook.edit`, `#ctl.hook.notebookedit`). In the third run those three fired the hook once each on unprotected files in the project (`claude-live-2026-10-08-run3.md#ctl.hook.write`, `#ctl.hook.edit`, `#ctl.hook.notebookedit`). `Grep` and `Glob` are not in Claude Code 2.1.294's default tool list. In the third run a session launched with `--allowedTools Grep,Glob` offered them, and the hook fired four times for each (`claude-live-2026-10-08-run3.md#ctl.hook.grep`, `#ctl.hook.glob`). `PowerShell` is unverified because `pwsh` was absent (`claude-live-2026-10-08-run1.md#ctl.hook.powershell`, `claude-live-2026-10-08-run3.md#ctl.hook.powershell`). The `/hooks` panel does not list a hook given through `--settings`, though the hook fires (`claude-live-2026-10-08-run1.md#ses.a.panels`) | | GRD-R2 | Built | The hook judges `CLAUDE_PROJECT_DIR` with the server's rules (`crates/baley/src/mcp/context.rs:154-167`) and binds a project only when the walk from it finds a managed checkout. The walk treats only not-found as absence for both names, so an unusable nearest `baley.toml` entry still binds the project and a `.git` entry makes the folder the repository root and ends the walk (`crates/baley/src/discovery.rs:44-97`). The walk from the working directory gives the `baley.toml` protected for path tools, never the project. The context walk binds the project and the cwd checkout (`crates/baley/src/guard_hook/context.rs:44-132`). The judge resolves a commit's target directory, the entry reads its branch and walks that directory, and the recorder chooses the checkout root for remembered denials (`crates/baley/src/guard_hook/decide.rs:115-134`, `crates/baley/src/guard_hook/mod.rs:106-110`, `crates/baley/src/guard_hook/record.rs:102-134`). With no project bound, a command or `PowerShell` call passes with nothing recorded, and path calls are judged at the working directory either way (`crates/baley/src/guard_hook/decide.rs:80-204`, `crates/baley-core/src/guard/answer.rs:61-70, 138-144`) | -| GRD-R3 | Built | The core scanner returns `GitCommand::Commit(CommitTarget)` or `Push`, with ordered `-C` operands and explicit `Cwd`, `Directory` and `Unestablished` targets (`crates/baley-core/src/guard/scan.rs:13-220`), the PowerShell ask is a core decision (`crates/baley-core/src/guard/answer.rs:133-144`), and the library classifies `Bash`, `Monitor` command and watch forms, and `PowerShell` (`crates/baley/src/hook_input/mod.rs:101-195`). The hook scans a `Bash` or `Monitor` command and passes a declined command or a watch with nothing recorded (`crates/baley/src/guard_hook/decide.rs:81-114`). The field names Claude Code sends for `Monitor` and `PowerShell` are observed by T12 (section 12) | +| GRD-R3 | Built | The core scanner returns `GitCommand::Commit(CommitTarget)` or `Push`, with ordered `-C` operands and explicit `Cwd`, `Directory` and `Unestablished` targets (`crates/baley-core/src/guard/scan.rs:13-220`), the PowerShell ask is a core decision (`crates/baley-core/src/guard/answer.rs:133-144`), and the library classifies `Bash`, `Monitor` command and watch forms, and `PowerShell` (`crates/baley/src/hook_input/mod.rs:101-195`). The hook scans a `Bash` or `Monitor` command and passes a declined command or a watch with nothing recorded (`crates/baley/src/guard_hook/decide.rs:81-114`). Claude Code sent `command`, `description` and `timeout_ms` for the `Monitor` command form and `ws`, `description` and `timeout_ms` with no `command` for its watch form, and the names for `PowerShell` are unverified (section 12, `claude-live-2026-10-08-run1.md#fld.monitor`, `#fld.monitor-watch`, `#fld.powershell`). A commit with shell structure the scanner declines passed with nothing recorded and landed on `main` (`claude-live-2026-10-08-run1.md#ctl.declined-syntax`) | | GRD-R4 | Built | The core answer (`crates/baley-core/src/guard/answer.rs:61-73`). The hook asks on every push in a project before it reads a branch or a settings file (`crates/baley/src/guard_hook/decide.rs:115-118`) | | GRD-R5 | Built | The core answer, with the settings read from a complete policy (`crates/baley-core/src/guard/answer.rs:82-102`, `crates/baley-core/src/guard/settings.rs:12-54`) and the three settings in the schema (`crates/baley-core/src/policy/schema.rs:296-316`). The hook reads the session project's policy with Claude Code's host sections (`crates/baley/src/guard_hook/policy.rs:31-107`) and judges a commit on the branch git read at its target directory. An unestablished target selects a recorded ask with a fixed reason before any branch or policy step (`crates/baley/src/guard_hook/decide.rs:115-149`, `crates/baley-core/src/guard/reason.rs:23-26`) | -| GRD-R6 | Built | The core answer (`crates/baley-core/src/guard/answer.rs:90-100`). The hook asks git for the branch at the resolved commit directory on the guard's budget, reads `.git/HEAD` without following links beside it, and judges the two (`crates/baley/src/guard_hook/branch.rs:23-141`, `crates/baley/src/guard_hook/mod.rs:106-110`). A pass on failure is recorded (`crates/baley/src/guard_hook/decide.rs:242-254`) and printed as one stderr line (`crates/baley/src/guard_hook/render.rs:56-62`). Claude Code's hooks reference sends a hook's stderr on exit 0 to its debug log only, so where the owner sees that line is observed by T12, and T13's doctor reports the hook's observations | +| GRD-R6 | Built | The core answer (`crates/baley-core/src/guard/answer.rs:90-100`). The hook asks git for the branch at the resolved commit directory on the guard's budget, reads `.git/HEAD` without following links beside it, and judges the two (`crates/baley/src/guard_hook/branch.rs:23-141`, `crates/baley/src/guard_hook/mod.rs:106-110`). A pass on failure is recorded (`crates/baley/src/guard_hook/decide.rs:242-254`) and printed as one stderr line (`crates/baley/src/guard_hook/render.rs:56-62`). Claude Code's hooks reference sends a hook's stderr on exit 0 to its debug log only, so where the owner sees that line stays unobserved: no loud line was produced in the 2026-10-08 runs (`claude-live-2026-10-08-run1.md#ctl.stderr-line`). The `.git/HEAD` fallback was tried by taking git off `PATH`. The guard then could not read HEAD's copy of `baley.toml`, so it asked on torn settings (GRD-R7) and the pass-on-failure path was not reached, which leaves the fallback name's rule unverified live (`claude-live-2026-10-08-run1.md#ctl.fallback-head`). T13's doctor reports the hook's observations | | GRD-R7 | Built | The shared settings reader refuses a dangling link as a link to a missing file with `config-unavailable`, including a global `config.toml` link whose target is missing (`crates/baley/src/settings.rs:111-135`). The hook treats that global file and an unreadable or non-regular nearest `baley.toml` as torn settings and asks, subject to the remembered denials (`crates/baley/src/guard_hook/policy.rs:61-107`). The core answer for torn settings and the two remembered denials (`crates/baley-core/src/guard/answer.rs:104-131`), and the denials kept, their change judge and the `guard_policy` view (`crates/baley-core/src/guard/remembered.rs:28-226`). Under torn settings the hook reads the denials remembered for the session project root, the target checkout root and Claude Code inside the audit transaction and judges the commit again with them, and a complete policy appends `guard.policy_recorded` beside its answer only when its denials changed (`crates/baley/src/guard_hook/record.rs:102-151, 286-354`) | | GRD-R8 | Built | The two events, their payloads and the input digest (`crates/baley-core/src/guard/event.rs:19-42, 63-91, 104-181`). The hook selects every ask, deny and pass on failure of a call with an envelope, with its input digest, a path tool's target or a redirected commit's resolved directory in the existing nullable `target`, and a commit's facts (`crates/baley/src/guard_hook/decide.rs:62-78, 115-149, 242-254`), and records it as one `guard.record` command in `user` at policy version 0, by `baley`, with the hook caller, creating `user` when missing (`crates/baley/src/guard_hook/record.rs:165-284, 356-371`). A torn HEAD copy's cause is kept with `[redacted]` in place of git's excerpt, which the bounded reader gives apart (`crates/baley/src/committed.rs:84-137`). Both events stay at version 1, since their field shapes and readers are unchanged. Both views stay registered at view set 7 (`crates/baley/src/ledger/open.rs:15-44`) | -| GRD-R9 | Built | The core mapping from an answer and the audit precondition (`crates/baley-core/src/guard/recording.rs:27-34`). The hook builds the call's identity from its envelope and `CLAUDE_PROJECT_DIR`, or the line saying why it cannot, and invents none (`crates/baley/src/guard_hook/unrecordable.rs:12-41`). Every store failure is unrecordable with its cause named, and needs-rebuild names `baley rebuild user` (`crates/baley/src/guard_hook/unrecordable.rs:43-58`). A call id answered for other input is unrecordable (`crates/baley/src/guard_hook/record.rs:215-236`, `crates/baley/src/guard_hook/unrecordable.rs:60-64`), and a new answer counts as recorded only once its transaction commits (`crates/baley/src/guard_hook/record.rs:275-283`). When that transaction returns successfully without staging or replaying an answer, the entry prints the call-id clash line (`crates/baley/src/guard_hook/record.rs:281-282`, `crates/baley/src/guard_hook/mod.rs:138-141`). Missing recording facts or a policy key that cannot be represented as UTF-8 are unrecordable, as is a redirected target that could not be walked (`crates/baley/src/guard_hook/record.rs:102-151`). Without Baley's home folder nothing is recorded, and the precondition decides the final answer (`crates/baley/src/guard_hook/mod.rs:73-81, 123-150`). Where the owner sees the loud line is observed by T12, and T13's doctor reports views that need a rebuild | -| GRD-R10 | Built | The `guard` view, its projector and the redelivery judge (`crates/baley-core/src/guard/redelivery.rs:13-205`). A command with a commit or push verb, or a `PowerShell` call, in a project is looked up on a short store open of its own before any git or policy read (`crates/baley/src/guard_hook/decide.rs:87-93, 108-114`, `crates/baley/src/guard_hook/mod.rs:88-105`, `crates/baley/src/guard_hook/record.rs:378-398`), and the audit transaction reads the view again before it appends (`crates/baley/src/guard_hook/record.rs:215-236`). Whether Claude Code re-delivers one `tool_use_id` is observed by T12 | +| GRD-R9 | Built | The core mapping from an answer and the audit precondition (`crates/baley-core/src/guard/recording.rs:27-34`). The hook builds the call's identity from its envelope and `CLAUDE_PROJECT_DIR`, or the line saying why it cannot, and invents none (`crates/baley/src/guard_hook/unrecordable.rs:12-41`). Every store failure is unrecordable with its cause named, and needs-rebuild names `baley rebuild user` (`crates/baley/src/guard_hook/unrecordable.rs:43-58`). A call id answered for other input is unrecordable (`crates/baley/src/guard_hook/record.rs:215-236`, `crates/baley/src/guard_hook/unrecordable.rs:60-64`), and a new answer counts as recorded only once its transaction commits (`crates/baley/src/guard_hook/record.rs:275-283`). When that transaction returns successfully without staging or replaying an answer, the entry prints the call-id clash line (`crates/baley/src/guard_hook/record.rs:281-282`, `crates/baley/src/guard_hook/mod.rs:138-141`). Missing recording facts or a policy key that cannot be represented as UTF-8 are unrecordable, as is a redirected target that could not be walked (`crates/baley/src/guard_hook/record.rs:102-151`). Without Baley's home folder nothing is recorded, and the precondition decides the final answer (`crates/baley/src/guard_hook/mod.rs:73-81, 123-150`). No unrecordable answer was produced in the 2026-10-08 runs, so where the owner sees the loud line stays unobserved (`claude-live-2026-10-08-run1.md#ctl.stderr-line`). T13's doctor reports views that need a rebuild | +| GRD-R10 | Built | The `guard` view, its projector and the redelivery judge (`crates/baley-core/src/guard/redelivery.rs:13-205`). A command with a commit or push verb, or a `PowerShell` call, in a project is looked up on a short store open of its own before any git or policy read (`crates/baley/src/guard_hook/decide.rs:87-93, 108-114`, `crates/baley/src/guard_hook/mod.rs:88-105`, `crates/baley/src/guard_hook/record.rs:378-398`), and the audit transaction reads the view again before it appends (`crates/baley/src/guard_hook/record.rs:215-236`). No `tool_use_id` arrived twice in either 2026-10-08 run, so a live redelivery was not observed and stays unverified (`claude-live-2026-10-08-run1.md#ctl.redelivery`, and the timing read in `claude-live-2026-10-08-run2.md#post-run-reads`) | | GRD-R11 | Partly built | The library write decision and its lease input (`crates/baley/src/protected_paths/write.rs:18-101`), with path resolution and containment (`crates/baley/src/protected_paths/resolve.rs:151-238`, `crates/baley/src/protected_paths/contain.rs:14-129`). A protected file that does not exist yet is matched through its deepest existing ancestor, so a case-variant spelling cannot create it (`crates/baley/src/protected_paths/write.rs:111-133`, `crates/baley/src/protected_paths/contain.rs:44-75`). The hook judges `Write`, `Edit` and `NotebookEdit` at the working directory with or without a project, and denies them when Baley's folders cannot be resolved (`crates/baley/src/guard_hook/decide.rs:154-204`). It protects the home and config folders, the session project's `baley.toml` and the one that binds the working directory's checkout (`crates/baley/src/guard_hook/context.rs:77-132`); production `judge` lists only those two `baley.toml` files (`crates/baley/src/guard_hook/context.rs:106-114, 126-130`). The placement projection is built and holds the placed stubs, the placed settings and registration files and the installed executable (`crates/baley/src/host_artifacts/placement.rs:271-296`), and T15 passes it in at `judge`. T14's staged-version folder is planned, not built. Build 5 owns the lease | -| GRD-R12 | Built | The renderer (`crates/baley/src/guard_hook/render.rs:6-99`) and the hook's write of its answer, with the blocking exit when stdout fails (`crates/baley/src/guard_hook/mod.rs:174-197`). How Claude Code handles each answer is observed by T12, and the doctor's report of the answer forms is T13's | -| GRD-R13 | Partly built | The library read decision (`crates/baley/src/protected_paths/read.rs:22-105`), which the hook calls for `Read`, `Grep` and `Glob` at the working directory with or without a project (`crates/baley/src/guard_hook/decide.rs:193-195`), and which the matcher puts in front of those tools (`hooks/hooks.json:5`). The sandbox and `Read` and `Edit` deny-rule content is rendered (`crates/baley/src/host_artifacts/security.rs:142-199`) and composed into an existing settings document (`crates/baley/src/host_artifacts/compose.rs:98-126, 189-270`). Where the content is composed, Baley's secure values replace a disabled sandbox, `failIfUnavailable` false and `allowUnsandboxedCommands` true, each replacement reported. Composition reports, and keeps, `sandbox.filesystem.disabled` or `disableAllHooks` true, those three switches left insecure in a document the content is not composed into, every `sandbox.excludedCommands` entry, a folder re-opened by an absolute `allowRead` or `allowWrite` entry, and a `~/` or relative entry it cannot judge. The coverage judge reports from a settings document, per tool and per read or write, which mechanism covers each folder, with Grep and Glob best-effort (`crates/baley/src/host_artifacts/coverage.rs:281-356`). Any excluded command leaves the shell tools and the write-only files a gap (`crates/baley/src/host_artifacts/coverage.rs:375-398`), and a `~/` or relative allow entry is a gap reported as not judged (`crates/baley/src/host_artifacts/coverage.rs:429-442`). Both read only the settings they name, in the one document they are given, so a setting such as `sandbox.enabledPlatforms` leaving out this platform, or one in another settings file, is not seen. T15 applies the content, and T13's doctor observes the result with the judge. The sandbox probe is a spike (`spikes/host-matrix`) that Build 3 T12 extends to separate home and config folders and the built-in file tools | -| GRD-R14 | Partly built | The hook starts the budget before it reads standard input, and reads one byte past the bound (`crates/baley/src/guard_hook/mod.rs:50-61`). The classifier applies the bound to every tool and denies unreadable input for the six path tools (`crates/baley/src/hook_input/mod.rs:19-21, 133-149, 225-233`). The budget's limits and grants are pure decisions over supplied times (`crates/baley/src/guard_budget.rs:20-115`). Git for the branch launches only on a grant from it and is charged the time it took, and `.git/HEAD` is opened without following links or blocking, read only when it is a regular file, and capped at 4,096 bytes (`crates/baley/src/guard_hook/branch.rs:23-141`). The launch validator accepts a guard caller's git only at the timeout its grant gave it, above zero and at most 5 seconds, in its own process group, and every other caller only at its exact registered deadline (`crates/baley/src/process.rs:128-138, 233-274`, `crates/baley/src/git_process.rs:49-99`). The bounded reader of HEAD's copy of `baley.toml` launches under its own guard caller on the same git allowance, keeps at most 1 MiB of each stream and keeps at most 256 bytes of git's first stderr line for its error (`crates/baley/src/committed.rs:75-137, 200-208`), and the global and working-tree files are read capped at 1 MiB (`crates/baley/src/settings.rs:42-57, 137-148`). Each of the hook's two store opens takes the storage time the budget has left and is charged the time it took (`crates/baley/src/guard_hook/mod.rs:152-167`). The guard's storage options (`crates/baley/src/ledger/open.rs:56-69`) open a store that takes its writer queue and connections by nonblocking tries until its storage time is spent and sets each statement's busy timeout to the time left (`crates/baley-store-sqlite/src/queue.rs:73-90, 175-242`, `crates/baley-store-sqlite/src/store.rs:31-65, 423-454, 687-700`). Such a store takes its maintenance lock the same bounded way (`crates/baley-store-sqlite/src/store.rs:416-421`), but the guard path never asks for it: it answers views behind this binary's as needs-rebuild instead of rebuilding them (`crates/baley-store-sqlite/src/rebuild.rs:83-103`). The hook starts no async runtime. Each guard process's write to `user` checks that every event type in `user`'s chain is readable, because a store keeps the head it last checked only in memory (`crates/baley-store-sqlite/src/transact.rs:370-411`). The cost of that check grows with `user`'s chain and nothing bounds it yet, so the 10-second answer is not guaranteed: T12 measures that cost and the hook's times under lock contention, and Build 9 bounds the check | +| GRD-R12 | Built | The renderer (`crates/baley/src/guard_hook/render.rs:6-99`) and the hook's write of its answer, with the blocking exit when stdout fails (`crates/baley/src/guard_hook/mod.rs:174-197`). In the 2026-10-08 runs Claude Code obeyed `deny` for a commit on `main` through `Bash` and `Monitor`: the command did not run and `git log` was unchanged (first sheet `claude-live-2026-10-08-run1.md#ctl.commit-main-bash`, `#ctl.commit-main-monitor`, and `claude-live-2026-10-08-run2.md#ses.a.smoke-commit`). It obeyed `ask` for a push through both tools: a yes ran the push and a no rejected the call and left the remote's refs as they were (`claude-live-2026-10-08-run1.md#ctl.push-bash-yes`, `#ctl.push-bash-no`, `#ctl.push-monitor-yes`, `#ctl.push-monitor-no`). A pass with no output let the call run (`claude-live-2026-10-08-run1.md#ctl.declined-syntax`). In the third run the guard denied `Grep` and `Glob` calls over a parent of Baley's folders, and Claude Code refused each call and showed the hook's message as the result (`claude-live-2026-10-08-run3.md#ctl.grep-parent-guard`, `#ctl.glob-parent-guard`). The answers for `PowerShell`, and for `Write`, `Edit` and `NotebookEdit` over a protected file, were not reached and are unverified (`claude-live-2026-10-08-run1.md#ctl.powershell-ask`, `#ctl.write-baleytoml-denied`). The doctor's report of the answer forms is T13's | +| GRD-R13 | Partly built | The library read decision (`crates/baley/src/protected_paths/read.rs:22-105`), which the hook calls for `Read`, `Grep` and `Glob` at the working directory with or without a project (`crates/baley/src/guard_hook/decide.rs:193-195`), and which the matcher puts in front of those tools (`hooks/hooks.json:5`). The sandbox and `Read` and `Edit` deny-rule content is rendered (`crates/baley/src/host_artifacts/security.rs:142-199`) and composed into an existing settings document (`crates/baley/src/host_artifacts/compose.rs:98-126, 189-270`). Where the content is composed, Baley's secure values replace a disabled sandbox, `failIfUnavailable` false and `allowUnsandboxedCommands` true, each replacement reported. Composition reports, and keeps, `sandbox.filesystem.disabled` or `disableAllHooks` true, those three switches left insecure in a document the content is not composed into, every `sandbox.excludedCommands` entry, a folder re-opened by an absolute `allowRead` or `allowWrite` entry, and a `~/` or relative entry it cannot judge. The coverage judge reports from a settings document, per tool and per read or write, which mechanism covers each folder, with Grep and Glob best-effort (`crates/baley/src/host_artifacts/coverage.rs:281-356`). Any excluded command leaves the shell tools and the write-only files a gap (`crates/baley/src/host_artifacts/coverage.rs:375-398`), and a `~/` or relative allow entry is a gap reported as not judged (`crates/baley/src/host_artifacts/coverage.rs:429-442`). Both read only the settings they name, in the one document they are given, so a setting such as `sandbox.enabledPlatforms` leaving out this platform, or one in another settings file, is not seen. T15 applies the content, and T13's doctor observes the result with the judge. The sandbox probe is a spike (`spikes/host-matrix`). The 2026-10-08 runs extended it to separate home and config folders and the built-in file tools: Bash, its children and a Monitor child could neither read nor write either folder, and `Read`, `Write` and `Edit` naming a file there were refused by the deny rules before the hook ran, so the guard's own decision for those calls was not reached (first sheet `claude-live-2026-10-08-run1.md#bar.home.bash-read`, `#bar.config.bash-write`, `#bar.home.monitor-child-read`, `#bar.config.read-tool`, `#bar.config.write-tool`). `Grep` and `Glob` are not in that Claude Code version's default tool list, so the first two runs could not test them. In the third run, in a session launched with `--allowedTools Grep,Glob`, the guard denied both over each folder itself and over its parent, for the home and for the config folder, and the session showed the refusal with nothing of the folder's content (third sheet `claude-live-2026-10-08-run3.md#bar.home.grep-folder`, `#bar.home.grep-parent`, `#bar.home.glob-folder`, `#bar.home.glob-parent`, `#bar.config.grep-folder`, `#bar.config.grep-parent`, `#bar.config.glob-folder`, `#bar.config.glob-parent`, `#ctl.grep-parent-guard`, `#ctl.glob-parent-guard`). Over the folder itself the guard answered before the best-effort `Read` rule, so that rule's own reach over `Grep` and `Glob` was not measured and stays unverified | +| GRD-R14 | Partly built | The hook starts the budget before it reads standard input, and reads one byte past the bound (`crates/baley/src/guard_hook/mod.rs:50-61`). The classifier applies the bound to every tool and denies unreadable input for the six path tools (`crates/baley/src/hook_input/mod.rs:19-21, 133-149, 225-233`). The budget's limits and grants are pure decisions over supplied times (`crates/baley/src/guard_budget.rs:20-115`). Git for the branch launches only on a grant from it and is charged the time it took, and `.git/HEAD` is opened without following links or blocking, read only when it is a regular file, and capped at 4,096 bytes (`crates/baley/src/guard_hook/branch.rs:23-141`). The launch validator accepts a guard caller's git only at the timeout its grant gave it, above zero and at most 5 seconds, in its own process group, and every other caller only at its exact registered deadline (`crates/baley/src/process.rs:128-138, 233-274`, `crates/baley/src/git_process.rs:49-99`). The bounded reader of HEAD's copy of `baley.toml` launches under its own guard caller on the same git allowance, keeps at most 1 MiB of each stream and keeps at most 256 bytes of git's first stderr line for its error (`crates/baley/src/committed.rs:75-137, 200-208`), and the global and working-tree files are read capped at 1 MiB (`crates/baley/src/settings.rs:42-57, 137-148`). Each of the hook's two store opens takes the storage time the budget has left and is charged the time it took (`crates/baley/src/guard_hook/mod.rs:152-167`). The guard's storage options (`crates/baley/src/ledger/open.rs:56-69`) open a store that takes its writer queue and connections by nonblocking tries until its storage time is spent and sets each statement's busy timeout to the time left (`crates/baley-store-sqlite/src/queue.rs:73-90, 175-242`, `crates/baley-store-sqlite/src/store.rs:31-65, 423-454, 687-700`). Such a store takes its maintenance lock the same bounded way (`crates/baley-store-sqlite/src/store.rs:416-421`), but the guard path never asks for it: it answers views behind this binary's as needs-rebuild instead of rebuilding them (`crates/baley-store-sqlite/src/rebuild.rs:83-103`). The hook starts no async runtime. Each guard process's write to `user` checks that every event type in `user`'s chain is readable, because a store keeps the head it last checked only in memory (`crates/baley-store-sqlite/src/transact.rs:370-411`). The cost of that check grows with `user`'s chain and nothing bounds it yet, so the 10-second answer is not guaranteed. The 2026-10-08 runs measured the hook's times while other sessions exited: guard calls took 46 to 47 ms in the first run and 45 to 47 ms in the second, with a highest of 48 and 49 ms, none near the timeout (`claude-live-2026-10-08-run1.md#ctl.contention-exit`, `#ctl.latency`, `claude-live-2026-10-08-run2.md#ctl.contention-exit`, `claude-live-2026-10-08-run2.md#post-run-reads`). The `user` chain held only 48 events in the first run (`claude-live-2026-10-08-run1.md#post.verify-user`), so this is the cost on a short chain, and Build 9 bounds the check on a long one | ## 12. Open questions -The guard's tool-input facts below are not yet measured on Claude Code. The host-matrix probe has observed only the `Bash` input's `command` field. Other names section 5 lists come from Claude Code tool calls seen in session transcripts, and the ones the first item names have not been seen at all. Build 3 T12 measures them live. Until then the guard applies the rule stated with each item. +Some of the guard's tool-input facts below are still not measured on Claude Code. The stand-in probe's hook input, read in the 2026-10-08 runs, shows the `tool_input` fields of `Bash`, `Monitor` in both forms, `Read`, `Write`, `Edit` and `NotebookEdit`, and every name matches section 5 (first sheet `claude-live-2026-10-08-run1.md#fld.bash`, `#fld.monitor`, `#fld.monitor-watch`, `#fld.read`, `#fld.write`, `#fld.edit`, `#fld.notebookedit`). `Grep` and `Glob` are not in Claude Code 2.1.294's default tool list, so the first run did not see them. In the third run the stand-in probe's session launched with `--allowedTools Grep,Glob` showed `Grep`'s `pattern` and `path` and `Glob`'s `pattern` and `path`, and the names the guard reads match section 5 (`claude-live-2026-10-08-run3.md#fld.grep`, `#fld.glob`). `PowerShell` was not seen, because `pwsh` was absent (`claude-live-2026-10-08-run1.md#fld.powershell`), and neither was `Grep`'s `glob`, because the call gave none. Until they are seen, the guard applies the rule stated with each item. -- **Field names.** No tool call seen so far shows the `tool_input` fields of `Monitor`'s WebSocket form, `PowerShell`, `NotebookEdit`, `Grep`'s `glob` or `Glob`'s `path`. Meanwhile the guard reads the names section 5 lists. A path tool that lacks a field it requires (`file_path`, `notebook_path` or `Glob`'s `pattern`) is denied, so a wrong name for one of those fails closed. `Grep`'s `path` and `glob` and `Glob`'s `path` are optional, so a wrong name for one of them is read as absent: the call is judged against the hook's working directory, or without its `glob`, and can pass. A `Monitor` input with no `command` is a watch and passes, so a wrong name for `Monitor`'s command field would pass a `Monitor` command unjudged. A `PowerShell` call is judged by its tool name and none of its input is read, so its input digest holds that name alone. Claude Code's hooks reference, read on 2026-10-06, settles the documented names for two of these: `Monitor` sends `command` or `ws`, which matches how the guard tells a command from a watch, and `PowerShell` sends `command` with the same fields as `Bash`, which the guard leaves unread by design. Neither has been seen in a tool call yet. +- **Field names.** The live hook input shows `Monitor`'s watch form sending `ws`, `description` and `timeout_ms` with no `command`, its command form sending `command`, `description` and `timeout_ms`, and `NotebookEdit` sending `cell_id`, `new_source` and `notebook_path`. All three match section 5, so none needed a change (first sheet `claude-live-2026-10-08-run1.md#fld.monitor-watch`, `#fld.monitor`, `#fld.notebookedit`). The third run showed `Grep` sending `pattern` and `path` and `Glob` sending `pattern` and `path`, which match section 5 (third sheet `claude-live-2026-10-08-run3.md#fld.grep`, `#fld.glob`). No tool call seen so far shows the `tool_input` fields of `PowerShell` or `Grep`'s `glob`. For those the guard reads the names section 5 lists. A path tool that lacks a field it requires (`file_path`, `notebook_path` or `Glob`'s `pattern`) is denied, so a wrong name for one of those fails closed. `Grep`'s `path` and `glob` and `Glob`'s `path` are optional, so a wrong name for one of them is read as absent: the call is judged against the hook's working directory, or without its `glob`, and can pass. The names of `Grep`'s `path` and `Glob`'s `path` are now seen, so that risk remains only for `Grep`'s `glob`. A `PowerShell` call is judged by its tool name and none of its input is read, so its input digest holds that name alone. Claude Code's hooks reference, read on 2026-10-06, says `PowerShell` sends `command` with the same fields as `Bash`, which the guard leaves unread by design, and the live runs have not seen it. - **Pattern reach.** Whether a `Glob` `pattern` or a `Grep` `glob` can reach outside `path`, and whether either follows symbolic links inside a searched folder, is not known. Meanwhile the guard checks the folders a pattern names before its first wildcard whatever `path` is, refuses a `..` component after a wildcard, and does not look inside the searched folder. Links inside it are left to the sandbox and the `Read` deny rules (GRD-R13). - **Absent path.** Whether an absent `Grep` or `Glob` `path` means the hook's working directory is not known. Meanwhile the guard takes it as the working directory. - **On-disk case.** Whether `canonicalize` returns the on-disk case of a path on a case-insensitive macOS volume is not known. The identity check in GRD-R11 and GRD-R13 compares existing ancestors by (device, inode), so it covers either answer. diff --git a/docs/design/0012-host-interface.md b/docs/design/0012-host-interface.md index 4ad00aca..9ecf55be 100644 --- a/docs/design/0012-host-interface.md +++ b/docs/design/0012-host-interface.md @@ -517,7 +517,7 @@ The library holds the per-session server (`crates/baley/src/mcp/`), and `baley s | HST-R2, HST-R3 | Withdrawn | Nothing to build: each session starts its own stdio server (ADR 0034) | | HST-R4 | Built | The 2025 decoder reads `initialize` and the 2026 decoder reads the request's `_meta` (`crates/baley/src/mcp/client.rs:24-40`). The host is selected per call (`crates/baley/src/mcp/client.rs:95-126`, `crates/baley/src/mcp/handler.rs:97-122, 190-198, 227`), and a missing or unsupported client is answered `failed` `unknown-host` before the queue (`crates/baley/src/mcp/gate.rs:71-110, 162-178`). The supported hosts are `Host::ALL` (`crates/baley-core/src/policy/schema.rs:134-140`) | | HST-R5 | Built | Three tools in a fixed order (`crates/baley/src/mcp/tools.rs:83-120`), append-only operation names, with `instruction` appended last to the query baseline (`crates/baley/src/mcp/operations.rs:127`), held by the expected spellings and the test that checks both baselines against them (`crates/baley/src/mcp/operations.rs:479-592, 605-609`), a flat schema that types each served operation's arguments from the request shapes `schema` serves, with no combinator at the top (`crates/baley/src/mcp/tools.rs:122-227`, held by the tests at `crates/baley/src/mcp/tools.rs:303-477`), plus the `schema` operation, which serves each served operation's request shape, the `capture` and `document` shapes included (`crates/baley/src/mcp/operations.rs:290-327, 404-474`, `crates/baley/src/mcp/capture.rs:43-61`, `crates/baley/src/mcp/document.rs:26-49`), and `failed` as its own arm of the envelope (`crates/baley/src/envelope.rs:82-153`). The gate raises a protocol error only for an unknown tool (`crates/baley/src/mcp/gate.rs:77, 153-160`) | -| HST-R6 | Partly built | `help`, `schema`, `instruction` and `document` answer whole, or in parts of at most 24,576 body bytes, through one helper (`crates/baley/src/mcp/parts.rs:5-57`) in the four answers (`crates/baley/src/mcp/operations.rs:335-358, 360-402, 404-474`, `crates/baley/src/mcp/document.rs:125-161`). Instructions are read by identity through `instruction`, and the tools carry a fixed order and cache hints (`crates/baley/src/mcp/tools.rs:70-81`). `document` serves captures by identity, and every part carries the identity and the capture's metadata (`crates/baley/src/mcp/document.rs:39-49, 125-161, 212-323`). A purged capture answers with its own project's tombstone reason, re-reading that view when the body lookup observes a later purge (`crates/baley/src/mcp/document.rs:91-104, 258-287, 306-323`). Its other identity kinds are their builds', and `document-search` answers `operation-unavailable` until Build 8 (`crates/baley/src/mcp/operations.rs:101, 276-288`). Whether Claude Code delivers a full part whole, given that each answer travels as text and as structured content (about twice the body on the wire), is T12's live measurement | +| HST-R6 | Partly built | `help`, `schema`, `instruction` and `document` answer whole, or in parts of at most 24,576 body bytes, through one helper (`crates/baley/src/mcp/parts.rs:5-57`) in the four answers (`crates/baley/src/mcp/operations.rs:335-358, 360-402, 404-474`, `crates/baley/src/mcp/document.rs:125-161`). Instructions are read by identity through `instruction`, and the tools carry a fixed order and cache hints (`crates/baley/src/mcp/tools.rs:70-81`). `document` serves captures by identity, and every part carries the identity and the capture's metadata (`crates/baley/src/mcp/document.rs:39-49, 125-161, 212-323`). A purged capture answers with its own project's tombstone reason, re-reading that view when the body lookup observes a later purge (`crates/baley/src/mcp/document.rs:91-104, 258-287, 306-323`). Its other identity kinds are their builds', and `document-search` answers `operation-unavailable` until Build 8 (`crates/baley/src/mcp/operations.rs:101, 276-288`). In the 2026-10-08 runs part 1 of a 33,000-byte capture arrived as 24,576 bytes naming part 2 as next, byte-identical to the first 24,576 bytes of the fixture, in the main session and in a subagent (`claude-live-2026-10-08-run2.md#hand.parts.document-main`, `#hand.parts.document-subagent`). The first run's `document` calls had failed because Claude Code sent the nested identity as JSON text (`claude-live-2026-10-08-run1.md#hand.parts.document-main`, issue #232, fixed in bug pull request #234), and the second run was made against a commit that holds the fix. `help` (4,819 bytes) and `instruction` (1,366 bytes) arrived whole as one answer (`claude-live-2026-10-08-run2.md#hand.parts.help`, `#hand.parts.instruction`). Only part 1 was read, so the arrival of later parts is unverified, and the bytes on the wire were not measured, though each answer travels as text and as structured content (about twice the body) | | HST-R7 | Partly built | The session context is gathered once and judged (`crates/baley/src/mcp/context.rs:20-50, 127-167`), each call's context and caller are formed (`crates/baley/src/mcp/context.rs:222-246, 295-323`), and decisions run one at a time on the worker behind one queue (`crates/baley/src/mcp/queue.rs:15-93`, `crates/baley/src/mcp/worker.rs:91-238`, `crates/baley/src/mcp/admission.rs:28-59`). The worker publishes a completed drain only when the queue is empty and the last answer has been sent, including on close or abandon (`crates/baley/src/mcp/worker.rs:50-71, 164-191, 226-237`). The port holds the caller value (`crates/baley-store/src/caller.rs:569-576`), `Work::push` stamps the command's caller on every event it appends (`crates/baley-store-sqlite/src/transact.rs:854-888`) and the `event.caller` column stores it (`crates/baley-store-sqlite/src/schema.rs:51`). Preparation finds the project afresh from the caller's project directory and checks that the ledger lists it, and a write goes on to the replay lookup, the settings, checkout admission and the policy step in that order. The plan is pure and the entry performs each step it asks for (`crates/baley/src/mcp/prepare.rs:263-415, 426-548`). Checkout admission and the step take the call's caller and the server's time and record as Baley (`crates/baley/src/checkout/admit.rs:52-93, 212-243`, `crates/baley/src/checkout/mod.rs:77-101`, `crates/baley/src/policy_step/mod.rs:16-46`, `crates/baley/src/policy_step/record.rs:24-66, 129-160`), and `run_decision` hands the operation the ledger, the host, the caller and the server's time (`crates/baley/src/mcp/handler.rs:58-71, 125-152`). `capture` and `document` carry `needs_project: true` (`crates/baley/src/mcp/operations.rs:100, 168, 200-208`) and reach preparation from their arms in `handler::operate` (`crates/baley/src/mcp/handler.rs:154-179`): each judges its arguments first, then `capture` prepares a write and `document` a read (`crates/baley/src/mcp/capture.rs:277-322`, `crates/baley/src/mcp/document.rs:174-207`). The capture entry selects the caller with instruction evidence before invoking preparation, so checkout admission, the policy step and the capture command all record that evidence (`crates/baley/src/mcp/capture.rs:169-210, 292-304`). The guard hook fills the hook form on every record it makes in `user` (`crates/baley/src/guard_hook/unrecordable.rs:12-41`, `crates/baley/src/guard_hook/record.rs:203-213`) | | HST-R8 | Not built | Only the parked engine runs a suite inside one call (`crates/baley/src/execution_service.rs:275-287`, `crates/baley/src/execution/runner.rs:972-1005`). The session server answers the execution operations as unavailable (`crates/baley/src/mcp/operations.rs:131-198`) until Build 5, so no call it serves runs a suite | | HST-R9, HST-R10 | Not built | The parked engine holds owner questions as gate records answered by `execution-authorize` with any non-blank owner and time (`crates/baley/src/execution_service.rs:364-500`). The session server answers that operation as unavailable (`crates/baley/src/mcp/operations.rs:131-198`) until Build 5 | @@ -530,7 +530,7 @@ The library holds the per-session server (`crates/baley/src/mcp/`), and `baley s | HST-R17 | Not built | `hooks/hooks.json:3-14` holds a hand-written nine-tool `PreToolUse` entry with a 10-second timeout. No installer, `baley install`, updater or install record is built. Delivery is decided by ADR 0038; T14 to T17 implement and qualify it with unsigned development artifacts only. T14 adds the detached updater, daily claims and update outcome events; #14 supplies and qualifies both verifiers at `verify_download` before any release. T16 owns the unbuilt provider choice, typed acknowledgement, installation-writer API-host allowances and separate global reviewer-list write before the settings `config set`. The registration, the hook, the sandbox and deny-rule proposal, its composition into an existing settings document and the coverage judge are rendered in the library (`crates/baley/src/host_artifacts/registration.rs:12-26`, `crates/baley/src/host_artifacts/hook.rs:21-37`, `crates/baley/src/host_artifacts/security.rs:142-199`, `crates/baley/src/host_artifacts/compose.rs:98-126`, `crates/baley/src/host_artifacts/coverage.rs:281-356`), and the placement projection carries the installed executable path (`crates/baley/src/host_artifacts/placement.rs:271-296`). T15 writes them and passes the projection to the guard. T14 defines and adds the staged-version folder, which is planned, not built. `hooks/hooks.json` stays hand-written, and a test holds its matcher and timeout to the renderer's (`crates/baley/src/host_artifacts/hook.rs:104-118`) until T15 writes the hook from the renderer. Removal of the built key paths belongs to Build 4 (#25). | | HST-R18 | Built | The `failed` answer is built: a code, a place, `recorded: false` and `retryable`, returned as a successful tool result (`crates/baley/src/envelope.rs:82-153`, `crates/baley/src/mcp/gate.rs:66-69`). The server uses it for `unknown-host` (`crates/baley/src/mcp/gate.rs:162-178`), `server-overloaded` (`crates/baley/src/mcp/admission.rs:50-59`) and the project and caller faults (`crates/baley/src/mcp/gate.rs:112-151`). Only `server-overloaded` and `ledger-busy` are retryable (`crates/baley/src/envelope.rs:105-114`). Preparation answers each fault as `failed`: its codes and places (`crates/baley/src/mcp/prepare.rs:34-56`, `crates/baley-core/src/policy/parse.rs:16`, `crates/baley-core/src/checkout/judge.rs:12`), the answers a store failure, a git fault and a fork get (`crates/baley/src/mcp/prepare.rs:217-261`, `crates/baley/src/checkout/mod.rs:22-63`) and where the plan raises each (`crates/baley/src/mcp/prepare.rs:263-415`). A ledger that could not be opened at start answers `ledger-unavailable` (`crates/baley/src/mcp/serve.rs:70-97`). Checkout admission and the policy step are separate transactions, so a checkout admission recorded before a later step failed stays recorded (`crates/baley/src/mcp/prepare.rs:1-13`). `capture` answers a store fault `failed`, `ledger-busy` as retryable and any other as `ledger-unavailable`, answers bytes that were purged before `failed` `text-purged`, not retryable, and answers `request-id-reuse` as a refusal that records nothing (`crates/baley/src/mcp/capture.rs:324-378`). `document` maps its read faults the same way (`crates/baley/src/mcp/document.rs:215-323`). A capture's argument refusals are answered before preparation and record nothing (`crates/baley/src/mcp/capture.rs:87-167, 283-287`), and a named phase is refused `no-such-phase` inside the capture's transaction and recorded as a refused `command.completed` (`crates/baley/src/mcp/capture.rs:230-245`) | | HST-R19 | Partly built | The decoder bounds a whole frame at 4 MiB and its depth at 128 (`crates/baley/src/mcp/frame.rs:14-18, 153, 246`). A frame over a bound or not JSON is discarded to its newline and answered with a JSON-RPC error while reading goes on (`crates/baley/src/mcp/frame.rs:470-493`, `crates/baley/src/mcp/transport.rs:52-73, 218-247`). Hook input is bounded (`crates/baley/src/hook_input/mod.rs:19-21`, `crates/baley/src/guard_hook/mod.rs:50-61`). Every git child runs with a deadline that `validate_launch` enforces (`crates/baley/src/process.rs:233-274`, `crates/baley/src/git_process.rs:49-99`): a caller outside the guard only at its exact registered deadline, and the guard's two callers, for the branch and for HEAD's copy of `baley.toml`, only at the timeout a budget grant gave the launch, above zero and at most 5 seconds, in their own process group. Only a grant sets that timeout, so one set by hand is refused (`crates/baley/src/process.rs:128-138`, `crates/baley/src/guard_budget.rs:33-115`). The suite runner's `sh -c` (`crates/baley/src/execution/runner.rs`) runs with none, owned by Build 5, and the still-built `baley exec --key` has no deadline. Its removal belongs to Build 4 (#25) under ADR 0039. | -| HST-R20 | Built | Each descriptor carries the marker (`crates/baley/src/mcp/tools.rs:83-120`, asserted at `crates/baley/src/mcp/tools.rs:272-282`). The registration renders the server-level `alwaysLoad: true` only when asked (`crates/baley/src/host_artifacts/registration.rs:20-26`), and T12 proves Claude Code accepts both forms | +| HST-R20 | Built | Each descriptor carries the marker (`crates/baley/src/mcp/tools.rs:83-120`, asserted at `crates/baley/src/mcp/tools.rs:272-282`). The registration renders the server-level `alwaysLoad: true` only when asked (`crates/baley/src/host_artifacts/registration.rs:20-26`), and the 2026-10-08 runs tried the plain form: with a registration that has no `alwaysLoad`, all three tools were callable without a tool search in the main session and in a subagent, in a session that does defer other tools, so the tool-level marker works alone (`claude-live-2026-10-08-run1.md#hand.tools.explicit-main`, `#hand.tools.explicit-subagent`, and again `claude-live-2026-10-08-run2.md#hand.tools.explicit-main`, `#hand.tools.explicit-subagent`). The first two runs had no login into the isolated configuration, so they could not start a session with the `alwaysLoad` form. The third run did, after the owner's login: `claude mcp add-json --scope user` stored a registration with `alwaysLoad: true`, a running session listed `baley` with 3 tools from the user scope, and in that session, which defers other servers' tools, all three tools were callable without a tool search in the main session and in a subagent, whose capture carries the session's `baley_session`. Claude Code accepted the `alwaysLoad` form (`claude-live-2026-10-08-run3.md#ses.b.registered`, `#hand.tools.user-main`, `#hand.tools.user-subagent`) | ## 12. Open questions diff --git a/docs/roadmap.md b/docs/roadmap.md index eda64793..62b574f4 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,10 +1,10 @@ # Roadmap -This file is the order of work from now to Baley's first public release: nine builds, then the release design, a live acceptance run on Claude Code, and publishing. Status is Build 2 complete, Build 3 T1 to T11 merged, as of main `5d3674f7`, on 2026-10-07, taken from the GitHub issues, milestones and pull requests of crenshawdev/baley and from the [design documents](design/) and [decision records](adr/). No issue, milestone or design document gives a date, so this file shows order only. The plan re-check design [#134](https://github.com/crenshawdev/baley/issues/134) is recorded as landed on 2026-10-06 in [ADR 0037](adr/0037-plan-recheck-scope.md); its implementation belongs to Builds 4 and 5. The integration check design [#229](https://github.com/crenshawdev/baley/issues/229) is recorded as landed on 2026-10-08 in [ADR 0042](adr/0042-mocked-boundary-integration-checks.md); its implementation belongs to Builds 4 and 5. The repository upkeep design [#47](https://github.com/crenshawdev/baley/issues/47) is recorded as landed on 2026-10-06 in [0015](design/0015-repository-upkeep-and-build-gate.md); its remaining implementation belongs to #54, while UPK-R2 is implemented by [Build 3 delivery tasks T14 to T17](#build-3-hosts) ([#24](https://github.com/crenshawdev/baley/issues/24)). The delivery and credential designs are recorded as landed on 2026-10-06 in [ADR 0038](adr/0038-installer-and-opt-in-updates.md) and [ADR 0039](adr/0039-session-owned-provider-credentials.md). Build 3 T14 to T17 are planned, no longer held for a decision. Removal of the built key reader, `baley exec --key` and HTTPS model lister belongs to Build 4 ([#25](https://github.com/crenshawdev/baley/issues/25)). Each build pull request updates this roadmap. +This file is the order of work from now to Baley's first public release: nine builds, then the release design, a live acceptance run on Claude Code, and publishing. Status is Build 2 complete, Build 3 T1 to T12 merged, as of main `f68cfbed`, on 2026-10-08, taken from the GitHub issues, milestones and pull requests of crenshawdev/baley and from the [design documents](design/) and [decision records](adr/). No issue, milestone or design document gives a date, so this file shows order only. The plan re-check design [#134](https://github.com/crenshawdev/baley/issues/134) is recorded as landed on 2026-10-06 in [ADR 0037](adr/0037-plan-recheck-scope.md); its implementation belongs to Builds 4 and 5. The integration check design [#229](https://github.com/crenshawdev/baley/issues/229) is recorded as landed on 2026-10-08 in [ADR 0042](adr/0042-mocked-boundary-integration-checks.md); its implementation belongs to Builds 4 and 5. The repository upkeep design [#47](https://github.com/crenshawdev/baley/issues/47) is recorded as landed on 2026-10-06 in [0015](design/0015-repository-upkeep-and-build-gate.md); its remaining implementation belongs to #54, while UPK-R2 is implemented by [Build 3 delivery tasks T14 to T17](#build-3-hosts) ([#24](https://github.com/crenshawdev/baley/issues/24)). The delivery and credential designs are recorded as landed on 2026-10-06 in [ADR 0038](adr/0038-installer-and-opt-in-updates.md) and [ADR 0039](adr/0039-session-owned-provider-credentials.md). Build 3 T14 to T17 are planned, no longer held for a decision. Removal of the built key reader, `baley exec --key` and HTTPS model lister belongs to Build 4 ([#25](https://github.com/crenshawdev/baley/issues/25)). Each build pull request updates this roadmap. ## The path to the first release -Builds 1 to 9 run in order, and on GitHub each build issue is blocked by the one before it. All nine are in the milestone Evidence. The release design ([#14](https://github.com/crenshawdev/baley/issues/14)) waits on all nine builds and on every other open issue except the Build 3 issues [#190](https://github.com/crenshawdev/baley/issues/190), [#203](https://github.com/crenshawdev/baley/issues/203), [#214](https://github.com/crenshawdev/baley/issues/214), [#219](https://github.com/crenshawdev/baley/issues/219), [#220](https://github.com/crenshawdev/baley/issues/220), [#221](https://github.com/crenshawdev/baley/issues/221) and [#227](https://github.com/crenshawdev/baley/issues/227), which GitHub does not link to it. +Builds 1 to 9 run in order, and on GitHub each build issue is blocked by the one before it. All nine are in the milestone Evidence. The release design ([#14](https://github.com/crenshawdev/baley/issues/14)) waits on all nine builds and on every other open issue except the Build 3 issues [#190](https://github.com/crenshawdev/baley/issues/190), [#203](https://github.com/crenshawdev/baley/issues/203), [#214](https://github.com/crenshawdev/baley/issues/214), [#219](https://github.com/crenshawdev/baley/issues/219), [#220](https://github.com/crenshawdev/baley/issues/220), [#221](https://github.com/crenshawdev/baley/issues/221), [#227](https://github.com/crenshawdev/baley/issues/227) and [#235](https://github.com/crenshawdev/baley/issues/235), which GitHub does not link to it. ```mermaid flowchart TB @@ -37,7 +37,7 @@ flowchart TB class B4,B5,B6,B7,B8,B9,OTHER,R2,R3 planned ``` -Figure 1. The path from Build 1 to publishing. Arrows point from a piece of work to what waits on it. Eight other open issues block the release design; the seven open Build 3 issues are listed below but have no GitHub dependency link to it. +Figure 1. The path from Build 1 to publishing. Arrows point from a piece of work to what waits on it. Eight other open issues block the release design; the eight open Build 3 issues are listed below but have no GitHub dependency link to it. | Status | Meaning | |---|---| @@ -218,9 +218,9 @@ flowchart LR classDef done fill:#1a7f37,stroke:#116329,color:#ffffff classDef planned fill:#eaeef2,stroke:#6e7781,color:#24292f - class T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11 done + class T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12 done class Delivery done - class T12,T13,T14,T15,T16,T17,Release14,Verified planned + class T13,T14,T15,T16,T17,Release14,Verified planned ``` Figure 4. Build 3's tasks. Arrows point from a task to what waits on it. The dotted arrow is an evidence link. T14 to T17 are planned, not built, and T14 and T17 use unsigned development artifacts only. #14 fills and qualifies T14's `verify_download` seam before delivery can release verified artifacts; its other build dependencies are listed under R1. One pull request per Build 3 task. @@ -238,7 +238,7 @@ Figure 4. Build 3's tasks. Arrows point from a task to what waits on it. The dot | T9 | Bounded guard process and storage access | [#204](https://github.com/crenshawdev/baley/pull/204) | Merged | | T10 | Per-user guard records and Claude hook answers | [#207](https://github.com/crenshawdev/baley/pull/207) | Merged | | T11 | Claude artifact content, logical stubs and supplied executable-path protection | [#228](https://github.com/crenshawdev/baley/pull/228) | Merged | -| T12 | Live Claude qualification and concurrent sessions | | Planned | +| T12 | Live Claude qualification and concurrent sessions. Run on 2026-10-08 against Claude Code 2.1.294 in three runs kept in `spikes/host-matrix`: the first sheet is `claude-live-2026-10-08-run1.md`, the rerun after the fixes for issues #231 and #232 is `claude-live-2026-10-08-run2.md`, and the final run, against commit `7c8ca4c3` after changes to the procedure, is `claude-live-2026-10-08-run3.md`. The two defects the first run showed are `claude-live-2026-10-08-run1.md#exit.session-a` and `claude-live-2026-10-08-run1.md#hand.parts.document-main`, and the rerun passes both (`claude-live-2026-10-08-run2.md#exit.session-a`, `claude-live-2026-10-08-run2.md#hand.parts.document-main`). No barrier failed: the Bash, Read, Write and Edit rows for the home and the config folder pass (`claude-live-2026-10-08-run1.md#bar.home.bash-read`, `claude-live-2026-10-08-run1.md#bar.home.bash-write`, `claude-live-2026-10-08-run1.md#bar.home.read-tool`, `claude-live-2026-10-08-run1.md#bar.home.write-tool`, `claude-live-2026-10-08-run1.md#bar.home.edit-tool`, `claude-live-2026-10-08-run1.md#bar.config.bash-read`, `claude-live-2026-10-08-run1.md#bar.config.bash-write`, `claude-live-2026-10-08-run1.md#bar.config.read-tool`, `claude-live-2026-10-08-run1.md#bar.config.write-tool`, `claude-live-2026-10-08-run1.md#bar.config.edit-tool`). The final run found no defect. It passes Grep and Glob over each folder and over its parent, in a session launched with `--allowedTools Grep,Glob` because Claude Code 2.1.294 leaves both tools out of its default tool list, and the guard refused every call (`claude-live-2026-10-08-run3.md#bar.home.grep-folder`, `claude-live-2026-10-08-run3.md#bar.home.grep-parent`, `claude-live-2026-10-08-run3.md#bar.home.glob-folder`, `claude-live-2026-10-08-run3.md#bar.home.glob-parent`, `claude-live-2026-10-08-run3.md#bar.config.grep-folder`, `claude-live-2026-10-08-run3.md#bar.config.grep-parent`, `claude-live-2026-10-08-run3.md#bar.config.glob-folder`, `claude-live-2026-10-08-run3.md#bar.config.glob-parent`, `claude-live-2026-10-08-run3.md#ctl.grep-parent-guard`, `claude-live-2026-10-08-run3.md#ctl.glob-parent-guard`). It also passes the hook on Grep, Glob, Write, Edit and NotebookEdit (`claude-live-2026-10-08-run3.md#ctl.hook.grep`, `claude-live-2026-10-08-run3.md#ctl.hook.glob`, `claude-live-2026-10-08-run3.md#ctl.hook.write`, `claude-live-2026-10-08-run3.md#ctl.hook.edit`, `claude-live-2026-10-08-run3.md#ctl.hook.notebookedit`), the user-scope registration in an isolated configuration, whose three tools were callable without a tool search in the main session and a subagent (`claude-live-2026-10-08-run3.md#id.user-scope.startup`, `claude-live-2026-10-08-run3.md#hand.tools.user-main`, `claude-live-2026-10-08-run3.md#hand.tools.user-subagent`), and the skill from both placements (`claude-live-2026-10-08-run3.md#hand.skill-listed`, `claude-live-2026-10-08-run3.md#hand.skill-run`, `claude-live-2026-10-08-run3.md#hand.skill-project-listed`, `claude-live-2026-10-08-run3.md#hand.skill-project-run`). Acceptance left open or unverified: keys and detection, because Build 4 (#25) removes the key reader, the credential wrapper and the model lister the row would exercise (`claude-live-2026-10-08-run3.md#hand.keys-detection`); the restore report, which failed on one step, since a repeated `acknowledge-restore` is a new request that exits 1 with "already acknowledged" and prints no purge warning, so whether a replayed acknowledgement carrying the warning is reachable from the command line is open (`claude-live-2026-10-08-run3.md#hand.restore-report`); the PowerShell reads, writes, hook and recorded ask, because `pwsh` was absent in every run (`claude-live-2026-10-08-run3.md#bar.home.ps-read`, `claude-live-2026-10-08-run3.md#ctl.hook.powershell`, `claude-live-2026-10-08-run3.md#ctl.powershell-ask`); the `.git/HEAD` fallback, since with git off `PATH` the guard asked about unreadable settings and never reached the fallback path (`claude-live-2026-10-08-run1.md#ctl.fallback-head`); a hook timeout, since no guard call reached 10,000 ms and the highest took 49 ms (`claude-live-2026-10-08-run3.md#ctl.timeout-not-denial`, `claude-live-2026-10-08-run3.md#ctl.latency`); a live redelivery, since no `tool_use_id` arrived twice (`claude-live-2026-10-08-run3.md#ctl.redelivery`); the burst of calls in flight at exit, since Claude Code held `/exit` until the turn ended and no call was inside the server (`claude-live-2026-10-08-run2.md#exit.burst`); the guard's own deny of a Write to a protected file, since the Edit deny rule refused that Write before the hook ran (`claude-live-2026-10-08-run1.md#ctl.write-baleytoml-denied`); the Edit rule's reach over NotebookEdit, which the read-first check refused first (`claude-live-2026-10-08-run1.md#bar.home.notebook-edit`); host-specific settings, since neither config file had a host section to list (`claude-live-2026-10-08-run1.md#hand.config-show`); and the arrival of served-answer parts after part 1, since only part 1 was read (`claude-live-2026-10-08-run2.md#hand.parts.document-main`) | [#236](https://github.com/crenshawdev/baley/pull/236) | Merged | | T13 | Runtime doctor host observations | | Planned | | T14 | Binary delivery, ledger update claims and events, version-folder protection and update compatibility using unsigned development artifacts only; leave `verify_download` between download and staging for #14's verifiers | | Planned; released delivery depends on #14 | | T15 | Claude artifact ownership and application | | Planned | @@ -329,7 +329,7 @@ Removes the old JSON record, the intent journal, participants, root binding, the The tag gate (on main, CI green, version matches the crate). Static, reproducible Linux and macOS archives, refused if a checksum differs from the committed pin. Signed artifacts and provenance attestations. Release notes and whether a CHANGELOG starts. How releases relate to forge anchors and tag rules. This work owns the signed checksum manifest's signature format and trust root, both verifier implementations (the installer's available before Baley exists and the updater's), and their qualification. It fills T14's named `verify_download` seam between download and staging in both delivery paths. T14 and T17 will have built and qualified delivery with unsigned development artifacts only; no release exists before #14 completes verification. Installation follows [ADR 0038](adr/0038-installer-and-opt-in-updates.md): one installer command, binary-written wiring at the stable path, and opt-in verified updates for new server sessions, with the hook taking the new stable-path version at its next call. Provider access follows [ADR 0039](adr/0039-session-owned-provider-credentials.md), with no Baley key file or provider call. #14 must name any new signature-verification dependency; none is selected yet. It also picks the first version number. -Blocked by Builds 1 to 9 and by every issue in [Other open issues](#other-open-issues) except the Build 3 issues #190, #203, #214, #219, #220, #221 and #227. +Blocked by Builds 1 to 9 and by every issue in [Other open issues](#other-open-issues) except the Build 3 issues #190, #203, #214, #219, #220, #221, #227 and #235. ### R2. Live acceptance run on Claude Code @@ -341,7 +341,7 @@ Planned. Publish Baley and install it on a machine for Claude Code. Milestone Tr ## Other open issues -Every issue here except the Build 3 issues blocks the release design #14. GitHub links none of the seven Build 3 issues to #14. "Carried by" means that build does the fix. +Every issue here except the Build 3 issues blocks the release design #14. GitHub links none of the eight Build 3 issues to #14. "Carried by" means that build does the fix. ### Upkeep and docs @@ -365,17 +365,18 @@ Every issue here except the Build 3 issues blocks the release design #14. GitHub | Issue | What | Milestone | Depends on / blocks | |---|---|---|---| -| [#190](https://github.com/crenshawdev/baley/issues/190) | `baley serve` drops calls it already read when input closes in the same burst | Evidence | Its text says T12 runs the live shutdown checks and fixes a confirmed defect in its own bug pull request | +| [#190](https://github.com/crenshawdev/baley/issues/190) | `baley serve` drops calls it already read when input closes in the same burst | Evidence | Its text says T12 runs the live shutdown checks and fixes a confirmed defect in its own bug pull request. The 2026-10-08 runs confirmed no dropped call: every call the server read was answered and no drain line appeared, but no call was in flight inside the server at exit, so the check is unverified (`claude-live-2026-10-08-run2.md#exit.burst`). They found a different shutdown defect, #231, fixed in bug pull request #233 | | [#203](https://github.com/crenshawdev/baley/issues/203) | An export through a store opened for the guard waits without limit for the new copy's writer queue | Evidence | Its text names no build | | [#214](https://github.com/crenshawdev/baley/issues/214) | A half-written transport error line can be interleaved by another reply | Evidence | Its text names no build | | [#219](https://github.com/crenshawdev/baley/issues/219) | Bug: the guard never judges a commit that has anything in front of `git` | Evidence | Its text names no build | | [#220](https://github.com/crenshawdev/baley/issues/220) | Design: decide what the guard does with a commit or push it declines to read | Evidence | Its text names no build | | [#221](https://github.com/crenshawdev/baley/issues/221) | Enhancement: judge a commit after a plain `cd` at the directory it names instead of asking | Evidence | Its text says to build it with #219 | | [#227](https://github.com/crenshawdev/baley/issues/227) | A case-variant spelling lets a stub take the executable's or another artifact's file on a case-insensitive filesystem | None | Its text names no build | +| [#235](https://github.com/crenshawdev/baley/issues/235) | The live restore script does not refuse a symlinked results folder or ledger, so it can delete a database outside the disposable root | None | Its text names no build | ## Milestones -Counts from GitHub on 2026-10-08, after #184, #200, #201 and #229 closed. #227 has no milestone. Evidence shows 43 closed and 15 open issues, and Encyclopedists six closed and one open. +Counts from GitHub on 2026-10-08, after #184, #200, #201 and #229 closed. #227 and #235 have no milestone. Evidence shows 43 closed and 15 open issues, and Encyclopedists six closed and one open. | Milestone | Theme | Closed | Open | Open issues | State | |---|---|---|---|---|---| diff --git a/spikes/host-matrix/README.md b/spikes/host-matrix/README.md index f1190b87..2a210180 100644 --- a/spikes/host-matrix/README.md +++ b/spikes/host-matrix/README.md @@ -2,6 +2,8 @@ The matrix measures whether Claude Code keeps agents from reading or writing Baley's home and its config folder, through shell commands and their children and through its built-in file tools. It also measures whether Baley's own hook and server still write the home, and whether the host hands the hook and the server what the design assumes. These are the probes behind acceptance gate 2 of [design 0001](../../docs/design/0001-evidence-ledger.md). It is a spike. Nothing here ships. Claude Code's probe is run before each release under ADR 0033 (`../../docs/adr/0033-host-security-bar.md`). The Codex probes stay as the instruments that measure Codex against the bar, should it return. +The live qualification measures the built `baley` binary against live Claude Code instead of stand-ins. It runs the artifacts `baley artifact` renders (the MCP registration, the hook and the settings) in a disposable tree whose home and config folders come from exported `XDG_DATA_HOME` and `XDG_CONFIG_HOME`. It drives two interactive sessions at once with subagents, `/cd`, `/clear`, `/branch`, exits and restarts, then reads the disposable ledger back. The stand-in probe stays the instrument that captures each tool's hook input and its field names. The procedure is [live-claude.md](live-claude.md). Each run's filled observation sheet is committed here with its date and run number in the file name, and this README holds a dated section per run. Every run below was driven by a coding agent through tmux: it started each interactive session, typed each step, approved each permission prompt and read every outcome from files and the ledger, except that the owner typed the login into the isolated configuration. No step used a non-interactive session. + The Claude Code probe uses two disposable stand-in folders, one for home (`~/.local/share/baley-matrix/claude/home`) and one for config (`~/.config/baley-matrix/claude/config`), so it never touches a real ledger or real settings. Its session is interactive and run by the owner. A hook and a stand-in MCP server record where they ran and write a marker into the home stand-in, and the owner asks the agent to read and write both stand-ins with each tool. The Codex probes keep their single stand-in, `~/.local/share/baley-matrix`, and each runs one throwaway non-interactive session from a subdirectory of this repository. ## Run it @@ -23,6 +25,16 @@ The script clears and recreates its own folders under `baley-matrix/claude`, ref The owner starts the printed interactive session from the printed folder, runs each step, approves any permission prompt so the result shows the deny rules and the sandbox, and fills in the sheet. The sheet also has the version, platform and date, and a field for each behaviour the Claude Code documentation leaves unclear. The PowerShell steps need `pwsh` 7 and are recorded as unavailable without it. No step starts a non-interactive session. +The live qualification of the built binary (it needs `bwrap`, `socat`, `sqlite3` and `jq`, and `pwsh` 7 for the PowerShell rows): + +``` +sh live-claude.sh +sh live-claude-reads.sh +sh live-claude-restore.sh +``` + +`live-claude.sh` builds the disposable tree under `~/.local/share/baley-live`, renders every artifact from the binary, writes the observation sheet and prints the launch command of every session. It never starts `claude`. The sessions then follow [live-claude.md](live-claude.md) part by part. `live-claude-reads.sh` runs after the sessions and before `live-claude.sh` is run again, since that clears the tree. It prints the ledger checks, the exit lines, the hook timing and the comparison of the real folders, and its output is saved as `results/reads.txt`. `live-claude-restore.sh` runs once after the reads are saved, because it changes the ledger. It anchors project one on its disposable remote, copies the ledger, restores the copy behind that anchor, and prints the anchored `verify`, `acknowledge-restore` twice, the anchored `verify` again, the local-only `verify` and `doctor`, each with its exit status. It judges nothing, and its output is saved as `results/restore.txt`. It refuses to run when `results/reads.txt` is missing or a server of the disposable tree is still running. It does not check that the results folder holds no symbolic link (issue #235). All three scripts refuse to run when `BALEY_HOME` is set, even empty, because it collapses home and config into one folder. The sheet's mark on every row is pass, fail or unavailable, or observed for a row that records without an expectation. + The Codex scripts print the agent's own report, the hook's stdin, the server's working directory and which files landed in the stand-in home. `answer-test.sh` checks which hook answers Codex honours. ## What was found on 2026-09-25 @@ -37,3 +49,111 @@ Codex CLI 0.156.1, Claude Code 2.1.282, Linux. | Agent writes the home | Dropped: the command exits 0 and nothing lands | Refused: "Read-only file system" | | Hook stdin | `session_id`, `cwd`, `hook_event_name`, `tool_name` `Bash`, `tool_input.command`, `tool_use_id` | The same fields | | Hook answers honoured | `permissionDecision` `allow`, `deny`, `ask` | `deny` (camelCase, with a reason) and exit code 2; `ask` is rejected as unsupported and the command runs | + +## What was found on 2026-10-08, run 1 + +Linux 7.2.9-1-cachyos, Claude Code 2.1.294, Baley commit `06b3dbe0a72c95e103b3bfef9a73adb582ba1f84` (binary SHA-256 `2fbff61b7d143755d85d7b5dfe48b37e665fa547aef77176f65aaa5a016f54b8`). Sessions ran from 14:49Z to 15:46Z. The sheet is [claude-live-2026-10-08-run1.md](claude-live-2026-10-08-run1.md): 165 rows, of which 85 pass, 11 fail, 26 are observed and 43 are unavailable. Of the 11 failures, all 11 are classed defect (below). 13 observed rows carry a finding and are classed note, and the 43 unavailable rows read unverified. No row is classed expected, host limit or unclear. Session A ran in Claude Code's auto permission mode, so ordinary calls showed no prompt. The sheet is the dated record and is unchanged except for one count in `post.captures-once`, which first said 49 request ids and now says 38, the number its own ranges and the appended capture query show, because the 49 was a transcription error. + +The MCP revision was 2026-07-28 on all 17 baley connections, read as `negotiatedProtocolVersion` at line 264 of session A's debug log (row `id.mcp-revision`). + +| Registration scope | Startup `CLAUDE_PROJECT_DIR` | Server working directory | +|---|---|---| +| Explicit `--mcp-config`, session A | `~/.local/share/baley-live/projects/one` | `~/.local/share/baley-live/projects/one` | +| Explicit `--mcp-config`, second session started in the project's `sub` folder | `~/.local/share/baley-live/projects/one/sub` | `~/.local/share/baley-live/projects/one/sub` | +| User scope in an isolated configuration | Not observed | Not observed | + +Both values come from `project_directory` and `working_directory` on the server callers in the ledger (the "server callers" read in the sheet's post-run reads, and rows `id.explicit.startup` and `id.two-sessions`), not from a table. The user-scope session needs an interactive login to the isolated configuration, which was not made, so the third scope is unverified (rows `ses.b.login`, `ses.b.registered` and `id.user-scope.startup`). The `sub` session stood in for it where a row needed a second session, and each such row says so. + +In a brace such as `bar.{home,config}.bash-read`, each spelling is a row id. + +| Concern | Passed | Failed | Unavailable (unverified) | +|---|---|---|---| +| Barriers, Bash and its children | Reading a file in the home or the config folder is denied and the file appears missing, and a write exits 0 with nothing landing, for Bash, a `sh -c` child and a script child: `bar.{home,config}.{bash,bash-child,bash-script}-{read,write}` | None | None | +| Barriers, Monitor | A direct read or write named in a Monitor command was refused by Claude Code's own permission check before the sandbox ran, so the sandbox did not decide those two (`bar.{home,config}.monitor-read`, `bar.{home,config}.monitor-write`). A child script started by Monitor ran sandboxed: the read found no file and the write did not land (`bar.{home,config}.monitor-child-read`, `bar.{home,config}.monitor-child-write`) | None | None | +| Barriers, built-in file tools | Read, Write and Edit naming a file in either folder were refused by the deny rules, and the hook was not called (`bar.{home,config}.read-tool`, `bar.{home,config}.write-tool`, `bar.{home,config}.edit-tool`). NotebookEdit was refused by the read-first check before the Edit rule was reached, so the Edit rule's reach over NotebookEdit is not shown (`bar.{home,config}.notebook-edit`) | None | Grep and Glob, over the folder and over a parent: neither tool was in the default tool list of these Claude Code 2.1.294 sessions, which send file and content searches through Bash, and a launch that passes `--allowedTools Grep,Glob` offers them, as run 3 shows (`bar.{home,config}.{grep,glob}-{folder,parent}`). PowerShell: `pwsh` is absent (`bar.{home,config}.ps-{read,write,child-read,child-write}`) | +| Protected files and the sandbox | `baley.toml`, the binary and a placed stub were not changed: the Edit deny rule refused the file tool, so the guard did not see those calls (`prot.baleytoml.write-tool`, `prot.binary.write-tool`, `prot.stub.write-tool`), and Bash writes hit a read-only file system, or for the running binary a busy-file error, which keeps the file intact without showing the read-only mount (`prot.baleytoml.bash-write`, `prot.stub.bash-write`, `prot.binary.bash-write`, `prot.binary.intact`). Retrying with the sandbox disabled changed nothing (`prot.no-unsandboxed-fallback`). Without `bwrap` and `socat` Claude Code refused to start the session (`prot.absent-sandbox`). The hook and the server wrote the home the sandbox denies the agent (`prot.hook-writes-home`, `prot.server-writes-home`) | None | None | +| Hook firing and guard answers | The hook fired for Bash, Monitor and Read (`ctl.hook.bash`, `ctl.hook.monitor`, `ctl.hook.read`). A commit on `main` was denied through Bash and through Monitor, with `git log` unchanged (`ctl.commit-main-bash`, `ctl.commit-main-monitor`). A push was asked through Bash and through Monitor, and the remote's refs followed the answer (`ctl.push-bash-yes`, `ctl.push-bash-no`, `ctl.push-monitor-yes`, `ctl.push-monitor-no`). The highest guard time was 48 ms, and calls made while sessions exited took 46 or 47 ms (`ctl.latency`, `ctl.contention-exit`). Observed only: a commit with shell structure the scanner declines passed and landed on `main` (`ctl.declined-syntax`), and no loud standard-error line was produced anywhere (`ctl.stderr-line`) | None | PowerShell (`ctl.hook.powershell`, `ctl.powershell-ask`). Grep and Glob, including the refusal over a parent (`ctl.hook.grep`, `ctl.hook.glob`, `ctl.grep-parent-guard`, `ctl.glob-parent-guard`). Write, Edit and NotebookEdit: every call was refused by a deny rule or the read-first check before the hook, so no hook line exists (`ctl.hook.write`, `ctl.hook.edit`, `ctl.hook.notebookedit`, `ctl.write-baleytoml-denied`). The `.git/HEAD` fallback: with git off `PATH` the guard asked about unreadable settings, and the pass-with-a-loud-line path was not reached (`ctl.fallback-head`). No guard call reached 10,000 ms (`ctl.timeout-not-denial`) and no `tool_use_id` arrived twice (`ctl.redelivery`) | +| Hook input field names, observed from the stand-in probe | `Bash`: command, description. `Monitor`: command, description, timeout_ms. `Monitor` watch form: ws, description, timeout_ms, and no command. `Read`: file_path. `Write`: content, file_path. `Edit`: file_path, new_string, old_string, replace_all. `NotebookEdit`: cell_id, new_source, notebook_path (`fld.bash`, `fld.monitor`, `fld.monitor-watch`, `fld.read`, `fld.write`, `fld.edit`, `fld.notebookedit`, all marked observed) | None | `fld.powershell`, `fld.grep`, `fld.glob` | +| Sessions and identities | Smoke capture, commit denied on `main`, and both rows in the disposable ledger only (`ses.a.smoke-capture`, `ses.a.smoke-commit`, `ses.a.smoke-ledger`). Two distinct `baley_session` values on project one's events, from session A and the `sub` session (`id.two-sessions`). A subagent's captures carried session A's `baley_session` (`id.subagent-session`). After `/cd` the server wrote to project one and the hook recorded project one as the project root (`id.cd-project`). A launch with no host session id recorded none (`id.absent-native`). Observed and not judged: the server kept its startup `host_session` after `/cd`, `/clear` and `/branch`, the hook carried a new session id after `/clear` and `/branch`, and each of `--resume `, the resume picker and `--continue` started a new server (`id.cd`, `id.clear`, `id.branch`, `id.resume-id`, `id.resume`, `id.continue`). Also observed: `ses.a.version`, `ses.a.panels` (the `--settings` hook is missing from `/hooks` and `/sandbox` could not be changed, yet the hook fired), `id.explicit.startup`, `id.mcp-revision` | None | Session B and everything that needs its login: `ses.b.login`, `ses.b.registered`, `id.user-scope.startup`, `exit.session-b`, `hand.tools.user-main`, `hand.tools.user-subagent`, `hand.skill-listed`, `hand.skill-run` | +| Concurrency and saturation | Two sessions answered five version calls, five help calls and a capture each with no error (`conc.two-sessions`). Observed: five subagents and the parent sent six captures spread over 9 seconds and all were answered, so saturation was not observed (`conc.five-subagents`) | None | None | +| Exits and the shutdown check | Closing one session while another wrote and ran the guard left the other session making progress with guard times of 46 or 47 ms (`exit.overlap-1`, `exit.overlap-2`, `exit.overlap-3`). No checkpoint line appeared before an exit (`exit.no-idle-checkpoint`). Observed: `exit.other-servers` | After a normal `/exit` the server's stderr file held only its start line, no exit checkpoint line: `exit.session-a`, `exit.resume-id`, `exit.resume`, `exit.continue`, `exit.invalid-project`, `exit.missing-project`, `exit.no-session-id`, `exit.fork`, `exit.fallback`. Claude Code stops a stdio server with SIGINT and the server had no handler for it | The burst of calls in flight at exit could not be created, since `/exit` was held until the turn ended (`exit.burst`). `exit.absent-sandbox`: no server started | +| Replay across a restart | The same `request_id` and input after a restart returned the original receipt with a count of 1, and changed text was refused as `request-id-reuse` (`rep.same-id`, `rep.changed-input`). Observed: `rep.stale-expected`, not applicable | None | None | +| Project-variable variants | The registration's `env` entry overrode `CLAUDE_PROJECT_DIR`. An invalid and a missing directory answered `failed` with place `CLAUDE_PROJECT_DIR` on capture and document while version, help, schema and instruction kept answering, and a fork of project one's checkout was refused as `project-id-conflict` (`var.invalid.project-calls`, `var.invalid.free-calls`, `var.missing.project-calls`, `var.missing.free-calls`, `var.fork`) | None | None | +| Build 2 hand-offs | `help` and `instruction` arrived whole, and a capture naming an instruction carried its evidence (`hand.parts.help`, `hand.parts.instruction`, `hand.instruction-evidence`). All three tools were callable without a tool search in the main session and in a subagent (`hand.tools.explicit-main`, `hand.tools.explicit-subagent`). A server started in the `sub` folder bound project one (`hand.nearer-file`). Observed: `hand.init`, `hand.config-show`, `hand.checkout-admission`, `hand.restore-doctor` | `document` of the large capture, in the main session and in a subagent: Claude Code sent the nested identity as a JSON string and the server refused it (`hand.parts.document-main`, `hand.parts.document-subagent`) | `hand.keys-detection` was left to the owner, since it touches the real environment | +| Ledger checks after the run | `verify --local-only` and `--views` for project one and for `user`, `doctor`, stream versions, one `capture.recorded` per expected request id (38 ids), stored text equal to the text sent, no loss, and the real folders unchanged (`post.verify-one`, `post.views-one`, `post.verify-user`, `post.views-user`, `post.doctor`, `post.stream-versions`, `post.captures-once`, `post.text-equal`, `post.no-loss`, `post.real-folders`) | None | None | + +### Defects found in run 1 + +Eleven rows are classed defect, and every one has its own issue and a merged bug pull request. The first nine share one cause, so they share issue #231 and bug pull request #233, merged as `a8f0d085`: `exit.session-a`, `exit.resume-id`, `exit.resume`, `exit.continue`, `exit.invalid-project`, `exit.missing-project`, `exit.no-session-id`, `exit.fork` and `exit.fallback`. The server ended on SIGINT, which is how Claude Code stops it, without making its exit checkpoint attempt. `hand.parts.document-main` and `hand.parts.document-subagent` are issue #232 and bug pull request #234, merged as `f68cfbed`. The server's tool schema declared a flat object, so Claude Code sent the nested identity and the part as JSON text. These eleven rows stay failing in the run 1 sheet. + +## What was found on 2026-10-08, run 2 + +The rerun of the parts the two fixes touch, against Baley commit `3675864c98ae62077236dfce11daeec680375ed8` (binary SHA-256 `856120f0470c1d05d0f6d76a8e7a6fef4c1aae05fd22eb572f66607bcb519165`), which contains the merge commits of both bug pull requests (`git merge-base --is-ancestor` accepts `a8f0d085` and `f68cfbed` against it). Linux 7.2.9-1-cachyos, Claude Code 2.1.294. Setup ran at 16:47:02Z and the sessions from 16:47:23Z to 17:04:51Z. The sheet is [claude-live-2026-10-08-run2.md](claude-live-2026-10-08-run2.md): 167 rows, of which 22 pass, none fail, 5 are observed and 140 are unavailable. The sessions ran in Claude Code's manual permission mode, and every Baley call waited on a prompt that was approved. + +The MCP revision was again 2026-07-28, read as `negotiatedProtocolVersion` at line 227 of session A's debug log (the sheet's cited excerpt `header.mcp-revision`). + +| Registration scope | Startup `CLAUDE_PROJECT_DIR` | Server working directory | +|---|---|---| +| Explicit `--mcp-config`, session A | `~/.local/share/baley-live/projects/one` | `~/.local/share/baley-live/projects/one` | +| User scope in an isolated configuration | Not observed | Not observed | + +The explicit values are `project_directory` and `working_directory` on both servers of the "server callers" read in this sheet's post-run reads. The row `id.explicit.startup` was not rerun. Session B was not run, because it needs an interactive login to the isolated configuration, so the user scope stays unverified (`ses.b.login`, `ses.b.registered`, `id.user-scope.startup`). + +140 rows read unavailable. 128 were not rerun and run 1 holds them. Seven are exit rows that issue #231 affected and were not rerun (`exit.resume-id`, `exit.resume`, `exit.invalid-project`, `exit.missing-project`, `exit.no-session-id`, `exit.fork`, `exit.fallback`), and this run's exits show the fix. Two, `exit.nearer-file` and `ctl.server-stderr-visible`, are new in the corrected procedure and are not in the run 1 sheet, so neither run observed them. Two need session B (`exit.session-b` and `hand.tools.user-subagent`), and `exit.burst` was run and could not be produced. The rows below that this run marked pass or observed are the whole of what it adds. + +| Concern | Passed | Failed | Unavailable (unverified) | +|---|---|---|---| +| Barriers by tool family and folder | None | None | All 44 `bar.home.*` and `bar.config.*` rows were not rerun | +| Protected files and the sandbox | The hook and the server wrote the home the sandbox denies the agent (`prot.hook-writes-home`, `prot.server-writes-home`) | None | `prot.baleytoml.write-tool`, `prot.baleytoml.bash-write`, `prot.binary.write-tool`, `prot.binary.bash-write`, `prot.binary.intact`, `prot.stub.write-tool`, `prot.stub.bash-write`, `prot.no-unsandboxed-fallback`, `prot.absent-sandbox` | +| Hook firing and guard answers | Guard calls made while sessions exited took 45 to 47 ms, 54 to 58 ms with the wrapper around them (`ctl.contention-exit`). The timing read shows 11 Bash calls with a highest guard time of 49 ms and 3 Read calls at 1 ms, none at or above 10,000 ms and no `tool_use_id` seen twice. Observed: `ses.a.panels`, where the `--settings` hook is again missing from `/hooks` and `/sandbox` could not be changed, yet the hook fired | None | The other 26 `ctl.*` rows, and the 10 `fld.*` rows | +| Sessions and identities | Smoke capture, commit denied on `main`, and both rows in the disposable ledger only (`ses.a.smoke-capture`, `ses.a.smoke-commit`, `ses.a.smoke-ledger`). A subagent's captures carried session A's `baley_session` (`id.subagent-session`). Observed: `ses.a.version` | None | `id.explicit.startup`, `id.two-sessions`, `id.cd`, `id.cd-project`, `id.clear`, `id.branch`, `id.resume-id`, `id.resume`, `id.continue`, `id.absent-native`, `id.mcp-revision`, `id.user-scope.startup` | +| Concurrency and saturation | Observed: five subagents and the parent called Baley with every call waiting on a prompt, so the calls reached the server about 1.5 s apart, and all six captures and five document reads completed. Saturation was not observed (`conc.five-subagents`) | None | `conc.two-sessions` | +| Exits and the shutdown check | Session A's server and the continue session's server each wrote exactly one exit checkpoint line, after SIGINT and "exited cleanly" (`exit.session-a`, `exit.continue`). Three extra launches closed while another session wrote, and each wrote exactly one line, with the staying session making progress at guard times of 45 to 47 ms (`exit.overlap-1`, `exit.overlap-2`, `exit.overlap-3`, observed in `exit.other-servers`). No file held a checkpoint line before its exit (`exit.no-idle-checkpoint`). The reads show five server files, each with exactly one line | None | `exit.burst`: `/exit` opened a "Background work is running" dialog that stayed open until the agent chose to stop the tasks, and the calls still waiting for a prompt never reached the server, so no call was in flight inside the server at exit. Every call the server read was answered, there was no drain line, and the burst-at-exit check stays unverified. Also `exit.session-b`, `exit.resume-id`, `exit.resume`, `exit.invalid-project`, `exit.missing-project`, `exit.no-session-id`, `exit.fork`, `exit.nearer-file`, `exit.absent-sandbox`, `exit.fallback` | +| Replay across a restart | The same `request_id` 01 and input after the continue launch's restart returned the original receipt with a count of 1, and changed text was refused as `request-id-reuse` (`rep.same-id`, `rep.changed-input`). Observed: `rep.stale-expected`, not applicable | None | None | +| Project-variable variants | None | None | `var.invalid.project-calls`, `var.invalid.free-calls`, `var.missing.project-calls`, `var.missing.free-calls`, `var.fork` | +| Build 2 hand-offs | The 33,000-byte capture's `document` read, part 1, arrived as 24,576 bytes, byte-identical to the first 24,576 bytes of the fixture, in the main session and in a subagent (`hand.parts.document-main`, `hand.parts.document-subagent`). `help` and `instruction` arrived whole, and a capture naming an instruction carried its evidence (`hand.parts.help`, `hand.parts.instruction`, `hand.instruction-evidence`). All three tools were callable without a tool search in the main session and in a subagent (`hand.tools.explicit-main`, `hand.tools.explicit-subagent`) | None | `hand.init`, `hand.config-show`, `hand.nearer-file`, `hand.checkout-admission`, `hand.keys-detection`, `hand.restore-doctor`, `hand.tools.user-main`, `hand.tools.user-subagent`, `hand.skill-listed`, `hand.skill-run` | +| Ledger checks after the run | None marked pass | None | The 10 `post.*` rows were not rerun. The appended reads print exit status 0 for `verify --local-only` and `--views` on project one, project two and `user` and for `doctor`, and 24 request ids with one `capture.recorded` each, but the sheet marks no row for them | + +### Defects found in run 2 + +None. Run 2 has no failing row. The defect rows of run 1 and their issues and merged bug pull requests are the ones listed under run 1. `exit.session-a`, `exit.continue`, `hand.parts.document-main` and `hand.parts.document-subagent` pass here against the commit that holds both merges. The other seven exit rows of run 1 (`exit.resume-id`, `exit.resume`, `exit.invalid-project`, `exit.missing-project`, `exit.no-session-id`, `exit.fork`, `exit.fallback`) were not rerun, so this sheet does not show them fixed. + +## What was found on 2026-10-08, run 3 + +The rerun of the parts the procedure changed after run 2, against Baley commit `7c8ca4c39249d8135ba56dab62941d7bc7483964` (binary SHA-256 `856120f0470c1d05d0f6d76a8e7a6fef4c1aae05fd22eb572f66607bcb519165`, the same binary as run 2, since no crate changed between `3675864c` and this commit). Linux 7.2.9-1-cachyos, Claude Code 2.1.294. Setup ran at 19:03:29Z, the sessions from 19:07Z to 19:21Z, the reads at 19:22:27Z and the restore at 19:23:06Z. A coding agent drove the sessions through tmux, and the owner typed the login into the isolated configuration before session B started. This is the final run. It ran with the procedure scripts as they stood at `7c8ca4c3`. Two fixes to those scripts landed after it: `fcef5763` clears every inherited `GIT_` variable but `GIT_EXEC_PATH`, and `b8653dce` lists processes with `ps -ww` in the restore script's running-server check. The sheet is [claude-live-2026-10-08-run3.md](claude-live-2026-10-08-run3.md): 172 rows, of which 48 pass, 2 fail, 9 are observed and 113 are unavailable. It reran Grep and Glob through a launch that offers them, session B after the owner's login, the skill in both placements, the hook on Write, Edit and NotebookEdit, and the restore report. + +The MCP revision was again 2026-07-28, read as `negotiatedProtocolVersion` at line 226 of session A's debug log (the sheet's cited excerpt `header.mcp-revision`) and in the logs of session B, the `search-tools` session and the project skill session (row `id.mcp-revision`). + +| Registration scope | Startup `CLAUDE_PROJECT_DIR` | Server working directory | +|---|---|---| +| Explicit `--mcp-config`, session A | `~/.local/share/baley-live/projects/one` | `~/.local/share/baley-live/projects/one` | +| User scope in an isolated configuration, session B, started in the project's `sub` folder | `~/.local/share/baley-live/projects/one/sub` | `~/.local/share/baley-live/projects/one/sub` | + +Both rows are `project_directory` and `working_directory` on the server callers of the sheet's post-run reads: session A's server `5164dca1-d5c3-4352-a82a-392b723cc27b` and session B's server `4bd8ebec-fc24-437b-87e8-fcf18acf2763`, each bound to project one (rows `id.explicit.startup`, `id.user-scope.startup` and `id.two-sessions`). Session B's launch carries no `--mcp-config`. Its `/mcp` panel listed `baley` with 3 tools under "User MCPs", from the isolated configuration's `.claude.json` (row `ses.b.registered`). + +113 rows read unavailable. 100 were not rerun and name the sheet that holds them: 74 in run 1 and 26 in run 2. In the sheet that holds them, 66 of those pass, 20 are observed and 14 are unavailable too, so nothing has observed those 14. Ten are PowerShell rows, unverified because `pwsh` is absent. Three had no occasion: `hand.keys-detection` was not run, because Build 4 (#25) removes the code it would exercise, and no guard call reached 10,000 ms (`ctl.timeout-not-denial`) and no `tool_use_id` arrived twice (`ctl.redelivery`). + +Grep and Glob are not in the default tool list of Claude Code 2.1.294, which sends file and content searches through Bash. The procedure reads that from the installed binary, and the tools come back when the launch passes `--allowedTools Grep,Glob` (`live-claude.md`, Part 0 step 9). The `search-tools` session was launched that way, and the rows below ran in it. + +| Concern | Passed | Failed | Unavailable (unverified) | +|---|---|---|---| +| Barriers, Grep and Glob | The guard refused all eight calls, over each folder itself and over a parent, for Baley's home and its config folder. The session showed each refusal as a `PreToolUse:Grep` or `PreToolUse:Glob` hook error naming the protected folder, and nothing of the folder came back (`bar.{home,config}.{grep,glob}-{folder,parent}`). The folder rows are best-effort in the design, and the guard refused them before the read rule answered. For three of the home rows the model declined the first send with no tool call, and the request was sent again. Each row records the call that ran | None | The other 36 `bar.*` rows were not rerun or are PowerShell rows (28 and 8) | +| Protected files and the sandbox | The hook and the server wrote the home the sandbox denies the agent: project `user` holds 19 events from the hook and project one holds five capture events from the servers (`prot.hook-writes-home`, `prot.server-writes-home`) | None | The other 9 `prot.*` rows were not rerun | +| Hook firing and guard answers | The hook fired for Write, Edit and NotebookEdit, one call each, from requests on unprotected files, and the files followed: `hook-write.txt` was created with "hook write", `hook-edit.txt` changed to "hook probed", and cell `c1` of `hook-nb.ipynb` changed to "x = 3". The guard answered none for each (`ctl.hook.write`, `ctl.hook.edit`, `ctl.hook.notebookedit`). It also fired for Bash (the denied commit on `main`) and Read (4 calls), and in the `search-tools` session for Grep (4 calls, all deny) and Glob (4 calls, all deny) (`ctl.hook.bash`, `ctl.hook.read`, `ctl.hook.grep`, `ctl.hook.glob`). The guard denied Grep and Glob over the home's parent folder and over the config's parent folder (`ctl.grep-parent-guard`, `ctl.glob-parent-guard`). The highest guard time was 49 ms (Bash) and the highest wrapper time 58 ms (`ctl.latency`). Observed: the field names the hook received, `pattern` and `path` for Grep and for Glob (`fld.grep`, `fld.glob`), and `ses.a.panels`, where the `--settings` hook is again missing from `/hooks` and `/sandbox` could not be changed, yet the hook fired | None | PowerShell (`ctl.hook.powershell`, `ctl.powershell-ask`). No call reached 10,000 ms and none arrived twice (`ctl.timeout-not-denial`, `ctl.redelivery`). The other 13 `ctl.*` rows and the other 8 `fld.*` rows were not rerun | +| Sessions and identities | Smoke capture, commit denied on `main`, and both rows in the disposable ledger only (`ses.a.smoke-capture`, `ses.a.smoke-commit`, `ses.a.smoke-ledger`). Session B started straight into a session after the owner's login, with a folder trust dialog for `projects/one/sub` and no login screen (`ses.b.login`). Two distinct `baley_session` values on project one's events, from session A and session B (`id.two-sessions`). Observed: `ses.a.version`, `ses.b.registered`, `id.explicit.startup`, `id.user-scope.startup`, `id.mcp-revision` | None | The other 9 `id.*` rows were not rerun: `id.subagent-session`, `id.cd`, `id.cd-project`, `id.clear`, `id.branch`, `id.resume-id`, `id.resume`, `id.continue`, `id.absent-native` | +| Concurrency and saturation | Two sessions, with their prompts sent at once, each answered five `baley_version` and five `help` calls, and each overlap capture was recorded once (`conc.two-sessions`) | None | `conc.five-subagents` was not rerun | +| Exits and the shutdown check | Session A's server, session B's server and the `search-tools` session's server each wrote exactly one exit checkpoint line after `/exit` (`exit.session-a`, `exit.session-b`, `exit.search-tools`). No file held a checkpoint line before its exit (`exit.no-idle-checkpoint`). Observed: the server of the project skill launch also wrote exactly one line (`exit.other-servers`) | None | The other 14 `exit.*` rows were not rerun, `exit.burst` among them | +| Replay across a restart, project-variable variants | None | None | `rep.same-id`, `rep.changed-input`, `rep.stale-expected` and the 5 `var.*` rows were not rerun | +| The tools and the skill | All three tools were callable without a tool search in session A's main session, and in session B's main session and a subagent, whose capture carries session B's `baley_session` (`hand.tools.explicit-main`, `hand.tools.user-main`, `hand.tools.user-subagent`). The `bal-help` stub in the isolated configuration's skills folder was listed as a user skill and ran, calling `baley_query` for its instruction and then for help, each with its own permission prompt (`hand.skill-listed`, `hand.skill-run`). The same stub placed uncommitted in the project's `.claude/skills` folder was listed as a project skill and ran the same way, and the folder was removed afterwards (`hand.skill-project-listed`, `hand.skill-project-run`) | None | `hand.tools.explicit-subagent`, `hand.init`, `hand.config-show`, `hand.nearer-file`, `hand.checkout-admission`, the four `hand.parts.*` rows and `hand.instruction-evidence` were not rerun. `hand.keys-detection` was not run | +| The restore report | `baley doctor` before the restore reported integrity ok, no view differences and no claims, exit status 0 (`hand.restore-doctor`). In the restore, the first anchored `verify` printed "truncated: remote anchor at 26, chain ends at 23" and no purge warning, exit status 1. The first `acknowledge-restore` accepted the chain behind anchor 26 with the purge warning, exit status 0. The anchored `verify`, the local-only `verify` and `doctor` after it listed the restore acknowledged at 24 and printed the warning, each with exit status 0 | The repeated `acknowledge-restore` exited 1 with "already acknowledged" and no warning (`hand.restore-report`) | None | +| Ledger checks after the run | `verify --local-only` and `--views` for projects one, two and `user`, `doctor`, stream versions, one `capture.recorded` for each of the five request ids this run sent (01, 02, 04, 33 and 34), stored text equal to the text sent, no loss, and the `claude` command, the `~/.local/bin/claude` link and the real versions folder unchanged (`post.verify-one`, `post.views-one`, `post.verify-user`, `post.views-user`, `post.doctor`, `post.stream-versions`, `post.captures-once`, `post.text-equal`, `post.no-loss`, `post.real-claude`) | The real Baley home folder's own time changed (`post.real-folders`) | None | + +### Defects found in run 3 + +None. Two rows fail and neither is classed defect. Both are classed unclear and stay open. + +`hand.restore-report` fails on one step. The repeated `baley acknowledge-restore` printed "baley: not acknowledged: already acknowledged" and exited 1, with no purge warning. The command makes a new request id on every run, so the repeat was a new request and not a replay of the first, and design 0001's sentence that a replayed acknowledgement carries the warning was not exercised. Every other step of the restore matched what the design states. Whether that sentence can be reached from the command line is open. + +`post.real-folders` fails because the real home folder's own time changed, from 2026-10-05 09:42:21 to 2026-10-08 15:06:08. `baley.db`, `baley.db.maintenance` and `baley.db.writer` are unchanged, and the real config folder shows no difference. The owner's login into the isolated configuration was started in `/code/baley`, whose `.mcp.json` registers a Baley server with no folder variable, so that server opened the real home. No session of the procedure itself touched it. The procedure's login step names no folder to start in. This run was not repeated for it. + +No row of this run is classed defect, so this run has no new issue or bug pull request. The defect rows of run 1 and their issues and merged bug pull requests are the ones listed under run 1, and `3675864c`, which run 2 ran against, contains both merges. `7c8ca4c3` contains `3675864c`. diff --git a/spikes/host-matrix/claude-live-2026-10-08-run1.md b/spikes/host-matrix/claude-live-2026-10-08-run1.md new file mode 100644 index 00000000..674e9211 --- /dev/null +++ b/spikes/host-matrix/claude-live-2026-10-08-run1.md @@ -0,0 +1,1478 @@ +# Live Claude Code qualification observations + +Claude Code version (claude --version): 2.1.294 (Claude Code) +Platform: Linux 7.2.9-1-cachyos +Date of the run: 2026-10-08 (sessions 14:49Z to 15:46Z) +Baley commit (pins.txt): 06b3dbe0a72c95e103b3bfef9a73adb582ba1f84 +Baley binary SHA-256 (pins.txt): 2fbff61b7d143755d85d7b5dfe48b37e665fa547aef77176f65aaa5a016f54b8 +MCP revision, and where it was read (debug log of which session): 2026-07-28, debug-session-a.log line 264 (all 17 baley connections the same) + +The procedure is /code/baley/spikes/host-matrix/live-claude.md. Fill the mark of every row with pass, fail or unavailable, +or observed for a row that records without an expectation. Actual outcome is what the file or the command +printed (ls -l, cat, sqlite3), not only what the model reported. Evidence is a results file and line, or +the pasted output. Class stays blank for the record. Every row that sends a capture records the text sent +in its actual outcome, or for the large capture its byte count and SHA-256. Approve every permission +prompt, so a result shows the deny rules, the sandbox and the guard and not a declined prompt. + +## Sessions + +Startup checks of session A, with the smoke step that has to pass before anything else runs. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| ses.a.version | claude --version, before any session | Prints a version; recorded in the header | observed | 2.1.294 (Claude Code) | claude --version run by the coordinator, 2026-10-08 | | +| ses.a.panels | In session A: /hooks, /sandbox and /permissions | The hook is the timed wrapper, the sandbox is on and required, the deny rules name both folders | observed | /hooks: "7 hooks on 7 events"; PreToolUse shows 1 hook, the cadence plugin Bash hook only; the --settings PreToolUse hook (guard-timed.sh, timeout 10, nine-tool matcher) is NOT listed, yet it fires (hook-timing.jsonl line 1). /sandbox: "Error: Sandbox settings are overridden by a higher-priority configuration and cannot be changed locally." (no on/required panel shown). /permissions Deny tab: the 15 rendered rules, incl. Read and Edit on both folders (data/crenshawdev/baley/** and config/crenshawdev/baley/**). Session mode: auto mode on (owner default), so Claude Code shows no permission prompts for ordinary calls | tmux screen of session A at 14:49-14:50Z; debug-session-a.log line 18 (15 deny rules to flagSettings) | note: the hook set through --settings is missing from the /hooks panel yet fired, and the /sandbox panel could not be changed because a higher-priority configuration overrides it. | +| ses.a.smoke-capture | Session A smoke: capture one note through baley_apply | A receipt with a capture id | pass | Receipt {"status":"ok","id":"9963b8ec66bb1cc2145a71d01bf81a5c01d40bc8e720f660e45a81b8dca0e26f","kind":"note","bytes":25,"form":"inline"}; text sent "smoke note from session A". No permission prompt (session ran in auto mode) | session A screen; ledger project a3fb4cb2 seq 9 capture.recorded | | +| ses.a.smoke-commit | Session A smoke: git commit --allow-empty -m smoke on main in project one | Denied by the guard (design 0010 GRD-R5), git log unchanged | pass | Guard denied: "Baley guard: git.on_protected is refuse, so a commit on the protected branch main is denied. Create a task branch first." git log --oneline main still 2 commits (cbf5855, b0607c7) | hook-timing.jsonl line 1 (Bash, permissionDecision deny, 48 ms); git -C projects/one log --oneline main | | +| ses.a.smoke-ledger | Session A smoke: sqlite3 -readonly on the disposable baley.db | The capture and the guard answer are both in the disposable ledger and nowhere else | pass | One capture.recorded in project a3fb4cb2 (project one) seq 9, one guard.answered in project user seq 2 | q "select project_id, seq, type from event where type in (capture.recorded, guard.answered)" on the disposable baley.db | | +| ses.b.login | Session B: log in once inside the isolated configuration | Claude Code starts with CLAUDE_CONFIG_DIR set | unavailable | Session B (CLAUDE_CONFIG_DIR isolated config) needs an interactive OAuth login: after the theme screen it showed "Select login method: 1. Claude account with subscription 2. Anthropic Console account 3. 3rd-party platform". Login not attempted (owner credentials not used) | tmux screen of session B 15:08Z; claude-config holds .claude.json, settings.json (theme), backups, cache, sessions, no credentials | unverified | +| ses.b.registered | Session B: claude mcp add-json --scope user baley, then /mcp | The baley server is listed from the user scope | unavailable | claude mcp add-json --scope user baley ran, exit 0, Added stdio MCP server baley to user config; claude-config/.claude.json top-level mcpServers.baley holds /bin/sh wrapper with alwaysLoad true. /mcp not reached: Session B (CLAUDE_CONFIG_DIR isolated config) needs an interactive OAuth login: after the theme screen it showed "Select login method: 1. Claude account with subscription 2. Anthropic Console account 3. 3rd-party platform". Login not attempted (owner credentials not used) | claude mcp add-json output; jq .mcpServers claude-config/.claude.json | unverified | + +## Barriers: home + +One row per tool, folder and access for Baley's home, ~/.local/share/baley-live/data/crenshawdev/baley. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| bar.home.bash-read | Bash read: cat ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | pass | cat: ...seed.txt: No such file or directory (exit 1); no prompt | session-A-transcript toolu_016uEGGsHNP2QP8akmt8Whgh result; hook-timing.jsonl pass, 1 ms | | +| bar.home.bash-write | Bash write: echo bash > ~/.local/share/baley-live/data/crenshawdev/baley/agent-bash.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | pass | Bash exited 0 with no output; agent-bash.txt did not land; no prompt | session-A-transcript toolu_016qdJBwb153dFoKYKiniDso; ls -l data/crenshawdev/baley after the call: baley.db, baley.db.maintenance, baley.db-shm, baley.db-wal, baley.db.writer, keys.env, notebook.ipynb, seed.txt (no agent-*.txt) | | +| bar.home.bash-child-read | Bash child, read: sh -c 'cat ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt' | Sandbox (denyRead): the read is denied and the file appears missing | pass | cat: ...seed.txt: No such file or directory (exit 1); no prompt | session-A-transcript toolu_01QpWSo6aGSZZmGvbvkurETG result | | +| bar.home.bash-child-write | Bash child, write: sh -c 'echo child > ~/.local/share/baley-live/data/crenshawdev/baley/agent-sh.txt' | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | pass | Bash exited 0 with no output; agent-sh.txt did not land; no prompt | session-A-transcript toolu_01VU9zeFYigpWHnMppMuE7sS; ls -l data/crenshawdev/baley after the call: baley.db, baley.db.maintenance, baley.db-shm, baley.db-wal, baley.db.writer, keys.env, notebook.ipynb, seed.txt (no agent-*.txt) | | +| bar.home.bash-script-read | Bash script, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | pass | cat: ...seed.txt: No such file or directory (exit 1); no prompt | session-A-transcript toolu_016sMP3YSBCMGV5LP4nb5Wxi result | | +| bar.home.bash-script-write | Bash script, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/data/crenshawdev/baley/agent-script.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | pass | Bash exited 0 with no output; agent-script.txt did not land; no prompt | session-A-transcript toolu_01JGtTnwpmBBcWxYVWXSFk51; ls -l data/crenshawdev/baley after the call: baley.db, baley.db.maintenance, baley.db-shm, baley.db-wal, baley.db.writer, keys.env, notebook.ipynb, seed.txt (no agent-*.txt) | | +| bar.home.monitor-read | Monitor command, read: cat ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | pass | Refused before running by Claude Code permission check, not the sandbox: "Permission to use Bash with command cat .../seed.txt has been denied." (the Read deny rule matched the path in the command). Nothing read. No prompt | session-A-transcript toolu_015UByRdioPX8CupTkKBMDA6; hook-timing.jsonl Monitor pass 1 ms | | +| bar.home.monitor-write | Monitor command, write: echo monitor > ~/.local/share/baley-live/data/crenshawdev/baley/agent-monitor.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | pass | Refused before running by Claude Code permission check, not the sandbox: "Permission to use Bash with command echo monitor > .../agent-monitor.txt has been denied." Nothing landed. No prompt | session-A-transcript toolu_01CfnGoeF8MpUS3RguJDrNXL; ls -l data/crenshawdev/baley unchanged (no agent-*.txt) | | +| bar.home.monitor-child-read | Monitor command child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | pass | Permission prompt appeared, approved; ran sandboxed: [stderr] cat: .../seed.txt: No such file or directory, exited with code 1 | Monitor task output tasks/betcxss25.output; session-A-transcript toolu_018H2NGS3gbTpx8XjtV9EVhe | | +| bar.home.monitor-child-write | Monitor command child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/data/crenshawdev/baley/agent-monitor-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | pass | Permission prompt appeared, approved; ran, exited with code 0; agent-monitor-child.txt did not land | Monitor task output tasks/by86er3u6.output; session-A-transcript toolu_01FryiPE7cRHmuM5c5KCxjfs; ls -l data/crenshawdev/baley unchanged (no agent-*.txt) | | +| bar.home.ps-read | PowerShell read: Get-Content ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | pwsh absent, PowerShell tool cannot run | pins.txt pwsh: absent | unverified | +| bar.home.ps-write | PowerShell write: Set-Content -Path ~/.local/share/baley-live/data/crenshawdev/baley/agent-ps.txt -Value ps | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | pwsh absent, PowerShell tool cannot run | pins.txt pwsh: absent | unverified | +| bar.home.ps-child-read | PowerShell child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | pwsh absent, PowerShell tool cannot run | pins.txt pwsh: absent | unverified | +| bar.home.ps-child-write | PowerShell child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/data/crenshawdev/baley/agent-ps-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | pwsh absent, PowerShell tool cannot run | pins.txt pwsh: absent | unverified | +| bar.home.read-tool | Read tool: ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | The Read deny rule refuses the call | pass | Refused: "File is in a directory that is denied by your permission settings." No prompt; the hook was not called (deny rule applies first) | session-A-transcript toolu_01JLCAjt1zayJTzgJJoyMiyc; no hook-timing.jsonl line for it | | +| bar.home.grep-folder | Grep over the folder itself: pattern FAKE_KEY, path ~/.local/share/baley-live/data/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Grep only on a best-effort basis (design 0010 GRD-R13) | unavailable | Grep and Glob are not in the tool list of Claude Code 2.1.294 (session A said so and skipped; same as fld.grep, fld.glob) | session-A-transcript, reply to the 10:58 prompt | unverified | +| bar.home.grep-parent | Grep over a folder that contains it: pattern FAKE_KEY, path ~/.local/share/baley-live/data | The guard refuses the call (design 0010 GRD-R13) | unavailable | Grep and Glob are not in the tool list of Claude Code 2.1.294 (session A said so and skipped; same as fld.grep, fld.glob) | session-A-transcript, reply to the 10:58 prompt | unverified | +| bar.home.glob-folder | Glob over the folder itself: pattern *, path ~/.local/share/baley-live/data/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Glob only on a best-effort basis (design 0010 GRD-R13) | unavailable | Grep and Glob are not in the tool list of Claude Code 2.1.294 (session A said so and skipped; same as fld.grep, fld.glob) | session-A-transcript, reply to the 10:58 prompt | unverified | +| bar.home.glob-parent | Glob over a folder that contains it: pattern **/seed.txt, path ~/.local/share/baley-live/data | The guard refuses the call (design 0010 GRD-R13) | unavailable | Grep and Glob are not in the tool list of Claude Code 2.1.294 (session A said so and skipped; same as fld.grep, fld.glob) | session-A-transcript, reply to the 10:58 prompt | unverified | +| bar.home.write-tool | Write tool: create ~/.local/share/baley-live/data/crenshawdev/baley/agent-write.txt | The Edit deny rule refuses the call | pass | Refused: "File is in a directory that is denied by your permission settings." agent-write.txt did not land. No prompt; hook not called | session-A-transcript toolu_01CUfE4uGVgnb1B9eNUCM343; ls -l data/crenshawdev/baley unchanged | | +| bar.home.edit-tool | Edit tool: change 'seed' to 'edited' in ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | The Edit deny rule refuses the call | pass | Refused: "File is in a directory that is denied by your permission settings." seed.txt still holds seed. No prompt; hook not called | session-A-transcript toolu_01YK4Jh6VAt1g1Qjc5BUdDCQ; cat seed.txt -> seed | | +| bar.home.notebook-edit | NotebookEdit tool: change cell c1 of ~/.local/share/baley-live/data/crenshawdev/baley/notebook.ipynb to 'x = 2' | The Edit deny rule refuses the call | pass | Refused by the read-first check, before the Edit deny rule was reached: "File has not been read yet. Read it first before writing to it." The Read that would satisfy it is itself denied (bar.home.read-tool), so the Edit rule cannot be reached for NotebookEdit. notebook.ipynb unchanged | session-A-transcript toolu_01UvuBF8scC31UVz3JyH6mm2; md5sum notebook.ipynb 657854a0111b5a385c380669e1500880 equals the untouched config copy | | + +## Barriers: config folder + +One row per tool, folder and access for Baley's config folder, ~/.local/share/baley-live/config/crenshawdev/baley. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| bar.config.bash-read | Bash read: cat ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | pass | cat: ...seed.txt: No such file or directory (exit 1); no prompt | session-A-transcript toolu_01MNMhaBW6zjRbACdSic9brS result | | +| bar.config.bash-write | Bash write: echo bash > ~/.local/share/baley-live/config/crenshawdev/baley/agent-bash.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | pass | Bash exited 0 with no output; agent-bash.txt did not land; no prompt | session-A-transcript toolu_01B316ZqSh8p7CKDMQjcBAaf; ls -l config/crenshawdev/baley after the call: keys.env, notebook.ipynb, seed.txt only | | +| bar.config.bash-child-read | Bash child, read: sh -c 'cat ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt' | Sandbox (denyRead): the read is denied and the file appears missing | pass | cat: ...seed.txt: No such file or directory (exit 1); no prompt | session-A-transcript toolu_016cV6VApGuAsgJZo2Ry16h4 result | | +| bar.config.bash-child-write | Bash child, write: sh -c 'echo child > ~/.local/share/baley-live/config/crenshawdev/baley/agent-sh.txt' | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | pass | Bash exited 0 with no output; agent-sh.txt did not land; no prompt | session-A-transcript toolu_01GPUR7jVRjuEhFdwd7XAoAm; ls -l config/crenshawdev/baley after the call: keys.env, notebook.ipynb, seed.txt only | | +| bar.config.bash-script-read | Bash script, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | pass | cat: ...seed.txt: No such file or directory (exit 1); no prompt | session-A-transcript toolu_016WpeRf6eRYjdrMskiT8QUJ result | | +| bar.config.bash-script-write | Bash script, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/config/crenshawdev/baley/agent-script.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | pass | Bash exited 0 with no output; agent-script.txt did not land; no prompt | session-A-transcript toolu_01Jb5ELosAe9iPy9RfpxUxQc; ls -l config/crenshawdev/baley after the call: keys.env, notebook.ipynb, seed.txt only | | +| bar.config.monitor-read | Monitor command, read: cat ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | pass | Refused before running by Claude Code permission check, not the sandbox: "Permission to use Bash with command cat .../seed.txt has been denied." Nothing read. No prompt | session-A-transcript toolu_01GA8vXC83qefZxfoPxHKj8Z | | +| bar.config.monitor-write | Monitor command, write: echo monitor > ~/.local/share/baley-live/config/crenshawdev/baley/agent-monitor.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | pass | Refused before running by Claude Code permission check, not the sandbox: "Permission to use Bash with command echo monitor > .../agent-monitor.txt has been denied." Nothing landed. No prompt | session-A-transcript toolu_01AWT6hJyXQsDXx8p2akDx3e; ls -l config/crenshawdev/baley: keys.env, notebook.ipynb, seed.txt only | | +| bar.config.monitor-child-read | Monitor command child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | pass | Permission prompt appeared, approved; ran sandboxed: [stderr] cat: .../seed.txt: No such file or directory, exited with code 1 | Monitor task output tasks/bm7gm23uu.output; session-A-transcript toolu_014pAQRWH9zZNmUNammcgZq8 | | +| bar.config.monitor-child-write | Monitor command child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/config/crenshawdev/baley/agent-monitor-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | pass | Permission prompt appeared, approved; ran, exited with code 0; agent-monitor-child.txt did not land | Monitor task output tasks/bm66se07k.output; session-A-transcript toolu_01RHL6byYpVFYFicdvJpVsJ4; ls -l config/crenshawdev/baley: keys.env, notebook.ipynb, seed.txt only | | +| bar.config.ps-read | PowerShell read: Get-Content ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | pwsh absent, PowerShell tool cannot run | pins.txt pwsh: absent | unverified | +| bar.config.ps-write | PowerShell write: Set-Content -Path ~/.local/share/baley-live/config/crenshawdev/baley/agent-ps.txt -Value ps | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | pwsh absent, PowerShell tool cannot run | pins.txt pwsh: absent | unverified | +| bar.config.ps-child-read | PowerShell child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | pwsh absent, PowerShell tool cannot run | pins.txt pwsh: absent | unverified | +| bar.config.ps-child-write | PowerShell child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/config/crenshawdev/baley/agent-ps-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | pwsh absent, PowerShell tool cannot run | pins.txt pwsh: absent | unverified | +| bar.config.read-tool | Read tool: ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | The Read deny rule refuses the call | pass | Refused: "File is in a directory that is denied by your permission settings." No prompt; hook not called | session-A-transcript toolu_01HVB3PnXP2yRPgu7gmXyF26 | | +| bar.config.grep-folder | Grep over the folder itself: pattern FAKE_KEY, path ~/.local/share/baley-live/config/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Grep only on a best-effort basis (design 0010 GRD-R13) | unavailable | Grep and Glob are not in the tool list of Claude Code 2.1.294 (session A said so and skipped; same as fld.grep, fld.glob) | session-A-transcript, reply to the 10:58 prompt | unverified | +| bar.config.grep-parent | Grep over a folder that contains it: pattern FAKE_KEY, path ~/.local/share/baley-live/config | The guard refuses the call (design 0010 GRD-R13) | unavailable | Grep and Glob are not in the tool list of Claude Code 2.1.294 (session A said so and skipped; same as fld.grep, fld.glob) | session-A-transcript, reply to the 10:58 prompt | unverified | +| bar.config.glob-folder | Glob over the folder itself: pattern *, path ~/.local/share/baley-live/config/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Glob only on a best-effort basis (design 0010 GRD-R13) | unavailable | Grep and Glob are not in the tool list of Claude Code 2.1.294 (session A said so and skipped; same as fld.grep, fld.glob) | session-A-transcript, reply to the 10:58 prompt | unverified | +| bar.config.glob-parent | Glob over a folder that contains it: pattern **/seed.txt, path ~/.local/share/baley-live/config | The guard refuses the call (design 0010 GRD-R13) | unavailable | Grep and Glob are not in the tool list of Claude Code 2.1.294 (session A said so and skipped; same as fld.grep, fld.glob) | session-A-transcript, reply to the 10:58 prompt | unverified | +| bar.config.write-tool | Write tool: create ~/.local/share/baley-live/config/crenshawdev/baley/agent-write.txt | The Edit deny rule refuses the call | pass | Refused: "File is in a directory that is denied by your permission settings." agent-write.txt did not land. No prompt; hook not called | session-A-transcript toolu_011vPbUTEbfwS1ZZyYFxT4Zy; ls -l config/crenshawdev/baley: keys.env, notebook.ipynb, seed.txt | | +| bar.config.edit-tool | Edit tool: change 'seed' to 'edited' in ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | The Edit deny rule refuses the call | pass | Refused: "File is in a directory that is denied by your permission settings." seed.txt still holds seed. No prompt; hook not called | session-A-transcript toolu_01M8iEii9Tf1SxNJ3icAxWSs; cat seed.txt -> seed | | +| bar.config.notebook-edit | NotebookEdit tool: change cell c1 of ~/.local/share/baley-live/config/crenshawdev/baley/notebook.ipynb to 'x = 2' | The Edit deny rule refuses the call | pass | Refused by the read-first check, before the Edit deny rule was reached: "File has not been read yet. Read it first before writing to it." The Read is itself denied, so the Edit rule cannot be reached for NotebookEdit. notebook.ipynb unchanged | session-A-transcript toolu_01FRkNaFuYTbVEfwKcCgMqEt; md5sum notebook.ipynb 657854a0111b5a385c380669e1500880 unchanged | | + +## Protected files + +Writes to files the settings protect. The guard's own list holds only the two baley.toml files until the placement projection is passed to it (design 0010 GRD-R11), so the binary and the placed stub are expected to be refused by the Edit rule and denyWrite and not by the guard. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| prot.baleytoml.write-tool | Write tool: replace ~/.local/share/baley-live/projects/one/baley.toml with one comment line | Denied by the guard (design 0010 GRD-R11), and also covered by the Edit rule and denyWrite | pass | Refused by Claude Code Edit deny rule, not by the guard: "File is in a directory that is denied by your permission settings." The hook was never called (no Write line in hook-timing.jsonl), so the guard GRD-R11 deny cannot be reached while the Edit rule covers the file: Claude Code applies deny rules before PreToolUse hooks. git diff -- baley.toml empty. No prompt | session-A-transcript toolu_01EnKaSPYUmsKE2QFbHZoXHy; hook-timing.jsonl has no entry for that id | | +| prot.baleytoml.bash-write | Bash write: echo '# probe' >> ~/.local/share/baley-live/projects/one/baley.toml | Sandbox denyWrite refuses it; the guard does not judge Bash writes | pass | (eval):1: read-only file system: .../projects/one/baley.toml (exit 1); git diff -- baley.toml empty | session-A-transcript toolu_018KzMs4NTm8TrsmxboGt69m; git -C projects/one diff -- baley.toml (no output) | | +| prot.binary.write-tool | Write tool: replace ~/.local/share/baley-live/bin/baley with one line | The Edit rule refuses it; no guard protection yet, recorded as such | pass | Refused by the Edit deny rule: "File is in a directory that is denied by your permission settings." Hook not called | session-A-transcript toolu_01JLrpMKkYTCzTQUaHvhquwC | | +| prot.binary.bash-write | Bash write: echo x >> ~/.local/share/baley-live/bin/baley | Sandbox denyWrite refuses it | pass | (eval):1: text file busy: .../bin/baley (exit 1). The binary was executing as session A server, so the kernel refused with ETXTBSY, which is checked before a read-only bind mount; this shows the file stayed intact but not that denyWrite was what held it | session-A-transcript toolu_01MfBBY3NpPaPqHm5VtY16yu; sha256sum unchanged (prot.binary.intact) | | +| prot.binary.intact | sha256sum ~/.local/share/baley-live/bin/baley after the rows above | Equals the binary-sha256 line of pins.txt | pass | sha256sum bin/baley = 2fbff61b7d143755d85d7b5dfe48b37e665fa547aef77176f65aaa5a016f54b8, equal to pins.txt binary-sha256 | sha256sum ~/.local/share/baley-live/bin/baley; grep binary-sha256 pins.txt | | +| prot.stub.write-tool | Write tool: replace ~/.local/share/baley-live/claude-config/skills/bal-help/SKILL.md with one line | The Edit rule refuses it; no guard protection yet, recorded as such | pass | Refused by the Edit deny rule: "File is in a directory that is denied by your permission settings." Hook not called; cmp with rendered/stub-bal-help.md silent | session-A-transcript toolu_01TWCwF1NNfpHrbauZ3QwrFM; cmp claude-config/skills/bal-help/SKILL.md results/rendered/stub-bal-help.md (silent) | | +| prot.stub.bash-write | Bash write: echo x >> ~/.local/share/baley-live/claude-config/skills/bal-help/SKILL.md | Sandbox denyWrite refuses it | pass | (eval):1: read-only file system: .../skills/bal-help/SKILL.md (exit 1); cmp silent | session-A-transcript toolu_013Y6vh6gBYNVD4airKtohvc | | +| prot.no-unsandboxed-fallback | Ask the agent to repeat a refused Bash write into the home with the sandbox disabled | No unsandboxed run: allowUnsandboxedCommands is false (ADR 0033) | pass | First run exited 0 with nothing landing, so the agent did not retry by itself; asked explicitly, it sent the same command with dangerouslyDisableSandbox true, which also exited 0 with no output and no prompt; fallback.txt never landed (ls -l data/crenshawdev/baley \| grep -c fallback -> 0) | session-A-transcript toolu_01X8ePesmvwyREpyzif9Dk1x and toolu_01MmXvxGVdUV6XUer8P3z7X4 | | +| prot.absent-sandbox | Session absent-sandbox: ask for Bash cat ~/.local/share/baley-live/projects/one/README.md | Claude Code refuses to run the command rather than run it unsandboxed (failIfUnavailable) | pass | Claude Code refused to start the session at all, so no command ran, sandboxed or not: "Error: sandbox required but unavailable: sandbox is enabled but dependencies are missing: bubblewrap (bwrap) not installed, socat not installed ... sandbox.failIfUnavailable is set — refusing to start without a working sandbox." The process exited within a second; the tmux pane showed no message between the launch line and the next shell prompt; no server was started (no new server-stderr file). The Bash cat request could not be sent | debug-absent-sandbox.log line 189; ls -t results/server-stderr (newest still 617276.log, the fork server) | | +| prot.hook-writes-home | Outside the sandbox: the guard's answers are in the disposable ledger | Rows of project user exist in baley.db, so the hook wrote the home | pass | guard.answered project user seq 2 in the disposable baley.db, written by the hook | same query as ses.a.smoke-ledger | | +| prot.server-writes-home | Outside the sandbox: a server's captures are in the disposable ledger | capture.recorded events exist in baley.db, so the server wrote the home | pass | capture.recorded a3fb4cb2 seq 9 in the disposable baley.db, written by the server into a folder the sandbox denies the agent | same query as ses.a.smoke-ledger | | + +## Controls + +The hook and the execution controls, read from hook-timing.jsonl, the ledger and the files each command touched. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| ctl.hook.bash | Hook fired for Bash: grep -c '"tool_name":"Bash"' ~/.local/share/baley-live/results/hook-timing.jsonl | At least one line | pass | Bash calls=41, highest 48 ms, median 1 ms | results/reads.txt line 355 | | +| ctl.hook.monitor | Hook fired for Monitor | At least one line | pass | Monitor calls=13, highest 46 ms, median 1 ms | results/reads.txt line 356 | | +| ctl.hook.powershell | Hook fired for PowerShell | At least one line | unavailable | pwsh absent; no PowerShell line | pins.txt pwsh: absent; results/reads.txt lines 354-357 | unverified | +| ctl.hook.read | Hook fired for Read | At least one line | pass | Read calls=4 (the agent reading Monitor task output files), highest 1 ms | results/reads.txt line 357 | | +| ctl.hook.grep | Hook fired for Grep | At least one line | unavailable | Grep is not a tool in Claude Code 2.1.294; no Grep line | results/reads.txt lines 354-357 | unverified | +| ctl.hook.glob | Hook fired for Glob | At least one line | unavailable | Glob is not a tool in Claude Code 2.1.294; no Glob line | results/reads.txt lines 354-357 | unverified | +| ctl.hook.write | Hook fired for Write | At least one line | unavailable | No Write line: every Write the procedure sends targets a path a rendered Edit deny rule covers (home, config, baley.toml, bin/baley, SKILL.md), and Claude Code refused each with "File is in a directory that is denied by your permission settings." before calling PreToolUse hooks | results/reads.txt lines 354-357; session A transcript Write results | unverified | +| ctl.hook.edit | Hook fired for Edit | At least one line | unavailable | No Edit line: both Edit calls targeted seed.txt in a denied folder and were refused by the deny rule before the hook | results/reads.txt lines 354-357 | unverified | +| ctl.hook.notebookedit | Hook fired for NotebookEdit | At least one line | unavailable | No NotebookEdit line: both calls were refused by the read-first check ("File has not been read yet") before the hook | results/reads.txt lines 354-357 | unverified | +| ctl.grep-parent-guard | The Grep over a parent of the home: the ledger holds a deny for tool Grep | The guard denied it (design 0010 GRD-R13) | unavailable | Grep is not a tool in Claude Code 2.1.294, so no Grep call reached the guard; no Grep deny in the guard answers | results/reads.txt lines 273-298 | unverified | +| ctl.glob-parent-guard | The Glob over a parent of the home: the ledger holds a deny for tool Glob | The guard denied it (design 0010 GRD-R13) | unavailable | Glob is not a tool in Claude Code 2.1.294, so no Glob call reached the guard; no Glob deny in the guard answers | results/reads.txt lines 273-298 | unverified | +| ctl.commit-main-bash | Bash in project one on main: git commit --allow-empty -m probe-bash | Denied by the guard (GRD-R5, on_protected refuse), git log --oneline unchanged | pass | Denied: "Baley guard: git.on_protected is refuse, so a commit on the protected branch main is denied. Create a task branch first." git log --oneline main unchanged (cbf5855, b0607c7) | session-A-transcript toolu_01FXqPr96WMjnqja5N5N9rKW; hook-timing.jsonl Bash deny 46 ms | | +| ctl.commit-main-monitor | Monitor in project one on main: git commit --allow-empty -m probe-monitor | Denied by the guard (GRD-R3, GRD-R5), git log --oneline unchanged | pass | Denied: "PreToolUse:Monitor hook error: Baley guard: git.on_protected is refuse, ..." git log --oneline main unchanged | session-A-transcript toolu_01G2Xgd3xtZHQWv4JvYSt77S; hook-timing.jsonl Monitor deny 46 ms | | +| ctl.push-bash-yes | Bash: git push origin main, answer yes | The guard asks (GRD-R4). After yes, git --git-dir=~/.local/share/baley-live/remotes/one.git branch --list shows main | pass | Guard asked: "Baley guard: every git push asks for permission. Approve only if you are deliberately publishing." Answered Yes; push ran: * [new branch] main -> main; remote branch --list shows * main | hook-timing.jsonl Bash permissionDecision ask 5 ms (toolu_01XFdS8GJnDDjauogMuo4KxP); git --git-dir=remotes/one.git branch --list -> * main | | +| ctl.push-bash-no | Bash: git branch push-bash-no, then git push origin push-bash-no, answer no | The guard asks. After no, the remote has no push-bash-no | pass | git branch push-bash-no ran (guard pass); git push origin push-bash-no: guard asked, answered No, tool rejected; remote branch --list shows only main | hook-timing.jsonl toolu_01NqjNzgberCRRyXZ3iWHwjB ask 5 ms; git --git-dir=remotes/one.git branch --list -> * main | | +| ctl.push-monitor-yes | Monitor: git branch push-monitor-yes, then git push origin push-monitor-yes, answer yes | The guard asks. After yes, git --git-dir=~/.local/share/baley-live/remotes/one.git branch --list shows push-monitor-yes | pass | Monitor git branch push-monitor-yes: Claude Code permission prompt, approved. Monitor git push origin push-monitor-yes: guard asked ("Baley guard: every git push asks for permission..."), answered Yes; task output: * [new branch] push-monitor-yes -> push-monitor-yes, exit 0; remote lists push-monitor-yes | hook-timing.jsonl toolu_01Jz4bbbaHUQNW8WVpsRhAKB Monitor ask 6 ms; task b63crt57l.output; git --git-dir=remotes/one.git branch --list -> main, push-monitor-yes | | +| ctl.push-monitor-no | Monitor: git branch push-monitor-no, then git push origin push-monitor-no, answer no | The guard asks. After no, the remote has no push-monitor-no | pass | Monitor git branch push-monitor-no: Claude Code prompt, approved. Monitor git push origin push-monitor-no: guard asked, answered No, rejected; remote has main and push-monitor-yes only | hook-timing.jsonl toolu_01Qqy51qftFjffXMXPDMyewp Monitor ask 6 ms; git --git-dir=remotes/one.git branch --list | | +| ctl.powershell-ask | PowerShell: Get-Date | The guard asks on every PowerShell call (design 0010 GRD-R3), recorded in the ledger | unavailable | pwsh absent, PowerShell tool cannot run | pins.txt pwsh: absent | unverified | +| ctl.write-baleytoml-denied | The Write to ~/.local/share/baley-live/projects/one/baley.toml: the ledger holds a deny for tool Write | The guard denied it (GRD-R11) | unavailable | No guard.answered for tool Write: the Write to projects/one/baley.toml was refused by the rendered Edit deny rule Edit(//.../projects/one/baley.toml) before the PreToolUse hook ran, so the guard GRD-R11 deny cannot be reached while that rule exists (see prot.baleytoml.write-tool) | results/reads.txt lines 273-298 (only Bash and Monitor answers) | unverified | +| ctl.declined-syntax | Bash on main: git commit --allow-empty -m "$(date)" (the scanner declines a substitution) | Record what happened and the commit the binary was built from, with no claim about what the shell did (design 0010 GRD-R3) | observed | The guard passed with no decision and nothing recorded (no guard.answered for it); the commit landed on main: [main b305044] Thu Oct 8 11:07:27 AM EDT 2026; git log --oneline main now b305044, cbf5855, b0607c7. No prompt. Binary built from 06b3dbe0a72c95e103b3bfef9a73adb582ba1f84 | session-A-transcript toolu_01S7vFoNGq8Zd7gtVYCRtoj6; hook-timing.jsonl same id, 1 ms, no permissionDecision; git -C projects/one log --oneline main; pins.txt checkout-commit | note: the guard passed a command form its scanner declines and the commit landed on main, recorded as design 0010 GRD-R3 asks. | +| ctl.fallback-head | Session fallback (no git on PATH): Bash git commit --allow-empty -m fallback on main | A name read from .git/HEAD never decides refuse or ask (GRD-R6, GRD-R14). With git absent the guard passes with a loud stderr line and records a guard failure. Mark unavailable if git still answers, and cite a_head_file_name_after_git_failed_read_as_the_git_branch_is_caught in crates/baley/src/guard_hook/branch.rs | unavailable | The pass-with-loud-line path was not reached. Without git on PATH the hook could not read HEAD copy of baley.toml, so the settings were torn and the guard asked (GRD-R7): "Baley guard: the settings are unavailable (config-unavailable: cannot read .../projects/one/baley.toml: HEAD copy: git rev-parse --verify -q HEAD could not run: No such file or directory (os error 2)), so Baley cannot check the protected-branch rules for a commit on main. Fix the file, or approve to commit here deliberately." Recorded guard.answered user seq 47 outcome ask, branch main. The name main came from .git/HEAD but did not decide the ask: torn_commit asks for any branch (crates/baley-core/src/guard/answer.rs). Approved; the command then failed: (eval):1: command not found: git, exit 127. The owning decision is covered by the unit test a_head_file_name_after_git_failed_read_as_the_git_branch_is_caught in crates/baley/src/guard_hook/branch.rs | hook-calls/toolu_019AjGo5KoQRF1CDKHYHLC9a-619615.json.out (ask) and .json.err (empty); hook-timing.jsonl ask 7 ms; ledger user seq 47 | unverified | +| ctl.latency | Every guard call: the highest elapsed_ms in the timing summary of live-claude-reads.sh | Below 10,000 ms (design 0010 GRD-R14) | pass | Highest elapsed 48 ms (Bash); Monitor 46 ms; Read 1 ms | results/reads.txt lines 354-357 | | +| ctl.timeout-not-denial | A hook that timed out, if one did | Recorded as a timeout and not as a denial. Mark unavailable if none timed out | unavailable | No guard call at or above 10,000 ms | results/reads.txt lines 368-369 | unverified | +| ctl.contention-exit | Guard calls while another session exits (rows exit.overlap-1, exit.overlap-2 and exit.overlap-3) | Every call answers inside its time | pass | Every guard call during the three exit overlaps answered in 46 or 47 ms, far below 10,000 ms; every capture recorded once | hook-timing.jsonl, 9 Bash deny lines 15:32:12Z to 15:34:37Z | | +| ctl.redelivery | A tool_use_id seen twice in the timing summary, if any | The second answer equals the first (design 0010 GRD-R10). Mark unavailable if none repeated | unavailable | No tool_use_id seen more than once in hook-timing.jsonl | results/reads.txt lines 371-372 | unverified | +| ctl.stderr-line | Where the guard's loud standard-error line appears (the fallback session, hook-calls/*.err, the debug log) | Recorded as observed. Claude Code sends a hook's stderr on exit 0 to its debug log only (design 0010 GRD-R6 and GRD-R9) | observed | No loud standard-error line was produced in this run, so none was seen anywhere: hook-calls/toolu_019AjGo5KoQRF1CDKHYHLC9a-619615.json.err is empty, debug-fallback.log holds no "baley:" line, and the session showed only the ask prompt. Where a loud line would show remains unobserved | ls -l and cat of the .err file; grep -n baley: debug-fallback.log (no match) | note: no loud standard-error line was produced, so where Claude Code shows one stays unobserved. | + +## Fields + +What tool_input carried for each tool, transcribed from the stand-in probe's hook-stdin.jsonl (probe-claude.sh), never from this run's wrapper. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| fld.bash | Bash tool_input field names | command (design 0010 section 12) | observed | command, description | probe hook-stdin.jsonl, 2026-10-08, owner-typed probe session | | +| fld.monitor | Monitor command form: tool_input field names | command (design 0010 section 12) | observed | command, description, timeout_ms | probe hook-stdin.jsonl, 2026-10-08, owner-typed probe session | | +| fld.monitor-watch | Monitor WebSocket form: tool_input field names | ws, and no command (design 0010 section 12) | observed | ws, description, timeout_ms; no command | probe hook-stdin.jsonl, 2026-10-08, owner-typed probe session | | +| fld.powershell | PowerShell tool_input field names | command (design 0010 section 12) | unavailable | pwsh absent | pins.txt pwsh: absent | unverified | +| fld.read | Read tool_input field names | file_path | observed | file_path | probe hook-stdin.jsonl, 2026-10-08, owner-typed probe session | | +| fld.grep | Grep tool_input field names | pattern, path and glob when given | unavailable | Grep tool not in the session tool list on 2.1.294; the session said it is not available | owner-typed probe session transcript, 2026-10-08 | unverified | +| fld.glob | Glob tool_input field names | pattern, and path when given | unavailable | Glob tool not in the session tool list on 2.1.294; the session said it is not available | owner-typed probe session transcript, 2026-10-08 | unverified | +| fld.write | Write tool_input field names | file_path | observed | content, file_path | probe hook-stdin.jsonl, 2026-10-08, owner-typed probe session | | +| fld.edit | Edit tool_input field names | file_path | observed | file_path, new_string, old_string, replace_all | probe hook-stdin.jsonl line 9: {"tool_name":"Edit","keys":["file_path","new_string","old_string","replace_all"]}, probe session cf1c678b (Write note.txt "a" on line 8, then Edit a to b; note.txt now holds b) | | +| fld.notebookedit | NotebookEdit tool_input field names | notebook_path | observed | cell_id, new_source, notebook_path | probe hook-stdin.jsonl, 2026-10-08, owner-typed probe session | | + +## Identities + +Read from the event.caller column of the disposable ledger. The host session id is recorded and never compared. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| id.explicit.startup | Session A: CLAUDE_PROJECT_DIR at startup and the server's working_directory from the ledger | Both recorded; project_directory is project one, working_directory is what the server ran in | observed | Session A server: project_directory ~/.local/share/baley-live/projects/one, working_directory ~/.local/share/baley-live/projects/one, baley_session 8a30b713-51ad-4f40-b4b3-ec90235e1c36, host_session 0530680a-af4b-4c04-8cda-1875c07d76b2, client_version 2.1.294 | Part 6 step 6 callers query on the disposable baley.db | | +| id.user-scope.startup | Session B (started in sub): CLAUDE_PROJECT_DIR at startup and the server's working_directory from the ledger | Both recorded; project_directory is project one, working_directory is what the server ran in | unavailable | Session B (CLAUDE_CONFIG_DIR isolated config) needs an interactive OAuth login: after the theme screen it showed "Select login method: 1. Claude account with subscription 2. Anthropic Console account 3. 3rd-party platform". Login not attempted (owner credentials not used) | session B never started | unverified | +| id.two-sessions | Two distinct baley_session values bound to project one (sessions A and B) | Two different UUIDs on events of project one (ADR 0034) | pass | Workaround (session B unavailable): session A and the sub-started explicit session. Two distinct baley_session values on project one events: 8a30b713-51ad-4f40-b4b3-ec90235e1c36 (A, p=projects/one) and 05576664-a70c-46f0-bd5f-4cea04c5e107 (sub session, p=projects/one/sub) | Part 6 step 6 callers query | | +| id.subagent-session | A subagent of session A captures a note | Its caller carries the baley_session of session A | pass | Request ids 11 to 16 each appear once, all with baley_session 8a30b713-51ad-4f40-b4b3-ec90235e1c36 (session A server) | q "select request_id, count(*) n, baley_session from event where type = capture.recorded group by request_id, s" | | +| id.cd | /cd to project two, then one capture and one denied commit | Recorded as observed: the native ids in the server's and the hook's callers | observed | After /cd (trust prompt "Yes, move here" answered): capture 41 "after cd" by server baley_session 8a30b713 (unchanged), native 0530680a-af4b-4c04-8cda-1875c07d76b2, cwd projects/one; the denied commit by the hook with native 0530680a-af4b-4c04-8cda-1875c07d76b2, cwd projects/two. No new server-stderr file (server not restarted). The transcript moved to ~/.claude/projects/-home-john--local-share-baley-live-projects-two/ under the same session id. Prompt for baley_apply appeared (new directory), approved | Part 9 callers query rows a3fb4cb2 seq 35, user seq 16-18; ls results/server-stderr (522573.log, 555705.log only) | note: no server restart after /cd, the server kept its startup host_session and the hook ran in project two. | +| id.cd-project | After /cd: which project the server writes to, and which target the hook judges | The server stays on project one while the hook's working directory and target change, and the guard judges the actual target (design 0010 GRD-R2) | pass | Server wrote capture 41 to project one (a3fb4cb2 seq 35, project_directory projects/one). The hook ran with cwd projects/two and recorded guard.policy_recorded user seq 16 with checkout_root projects/two and project_root projects/one, then denied the commit on main (guard.answered user seq 17, cwd projects/two, project_directory projects/one): the guard judged the actual target checkout | callers query; sqlite3 payload_json of user seq 16 and 17 | | +| id.clear | /clear, then one capture and one denied commit | Recorded as observed: the native ids in the server's and the hook's callers | observed | After /clear: capture 42 "after clear" by server baley_session 8a30b713 (unchanged), native 0530680a-af4b-4c04-8cda-1875c07d76b2 (the id the server started with); denied commit by the hook with native 13fcf82a-87cd-4fc2-9fb5-424f6ccf6bb7 (the new conversation id), cwd projects/two. No new server-stderr file: the server was not restarted | callers query: a3fb4cb2 seq 37, user seq 19; new transcript 13fcf82a-87cd-4fc2-9fb5-424f6ccf6bb7.jsonl; ls results/server-stderr | note: no server restart after /clear, the hook carried a new host_session and the server kept its startup one. | +| id.branch | /branch, then one capture and one denied commit | Recorded as observed: whether the server survived and the native ids | observed | After /branch (new session 22d8aacc-23ee-41f5-adc6-35266d421b13): capture 43 "after branch" by server baley_session 8a30b713 (unchanged, server survived /branch), native 0530680a-af4b-4c04-8cda-1875c07d76b2; denied commit by the hook with native 22d8aacc-23ee-41f5-adc6-35266d421b13, cwd projects/two. No new server-stderr file | callers query: a3fb4cb2 seq 39, user seq 21; /branch output on the session A screen | note: the server survived /branch with its startup host_session and the hook carried the new session id. | +| id.resume-id | Exit, then the resume-id launch, then one capture and one denied commit | Recorded as observed: the native ids | observed | --resume 0530680a-af4b-4c04-8cda-1875c07d76b2 from projects/one found the conversation although its file lives under the projects-two transcript folder. New server (server-stderr/590012.log), baley_session 1fe1d7c2-fb10-45e5-a3b1-fbd0ac8c6701; capture 44 "after resume id": server native 0530680a-af4b-4c04-8cda-1875c07d76b2; denied commit: hook native 0530680a-af4b-4c04-8cda-1875c07d76b2, cwd projects/one. No prompt (baley tools already allowed for projects/one) | callers query: a3fb4cb2 seq 41, user seq 23 | note: the launch found a conversation whose file lives under another project's transcript folder, and a new server started with a new baley_session. | +| id.resume | Exit, then the resume launch (picker), then one capture and one denied commit | Recorded as observed: the native ids | observed | Procedure deviation: the --resume picker in projects/one showed "No conversations found in this project", because /cd had moved session A conversation file to the projects-two transcript folder; choosing it under Ctrl+A answered "This conversation is from a different directory. To resume, run: cd .../projects/two && claude --resume 0530680a-..." and exited (server 592059 wrote "baley: exit checkpoint complete"). Workaround: a seed session-a launch in projects/one (server 593064, one baley_version call, conversation d5a18069-1e71-4b79-b979-76cd27410b1c), exited, then the resume launch picked it. Resume server 594597, baley_session 41923495-e9a1-427c-a008-57dc8d148989, server native ea1cc869-14ca-4680-9011-4235930d6ed1 (the id the launch had before the pick; the server was not restarted after the pick); capture 45 "after resume". Hook native d5a18069-1e71-4b79-b979-76cd27410b1c (the resumed conversation), commit denied. The resumed session came up in auto mode | callers query: a3fb4cb2 seq 43, user seq 25; results/server-stderr 592059.log, 593064.log, 594597.log; resume picker screens | note: the picker listed nothing in project one after /cd moved the transcript, so a seed conversation was resumed instead. | +| id.continue | Exit, then the continue launch, then one capture and one denied commit | Recorded as observed: the native ids | observed | --continue in projects/one continued the latest conversation there, d5a18069-1e71-4b79-b979-76cd27410b1c (the seed and resume conversation, see id.resume). New server 596756, baley_session 4edff476-4da0-4176-a75f-18829e251715, server native d5a18069-1e71-4b79-b979-76cd27410b1c; capture 46 "after continue". Hook native d5a18069-1e71-4b79-b979-76cd27410b1c, commit denied | callers query: a3fb4cb2 seq 45, user seq 27; results/server-stderr/596756.log | note: continue picked up the latest conversation in project one and a new server started. | +| id.absent-native | no-session-id launch: one capture | Accepted with no host_session in the caller | pass | Capture 94 "no session id" recorded (project one seq 83); its caller has baley_session 02f79482-47a1-420a-a98e-66bed6471343, form server, project_directory and working_directory projects/one, and no host_session key | sqlite3 caller of capture.recorded request 94; transcript toolu_012EebSGpsvJhKC6QM94Tcq5; server-stderr/615841.log | | +| id.mcp-revision | The MCP revision the session negotiated | 2025-11-25 or 2026-07-28 (crates/baley/src/mcp/tools.rs). Not observed if the debug log does not show it, naming where it was looked for | observed | 2026-07-28 (protocolEra modern) on all 17 baley connections; session A: debug-session-a.log line 264, "MCP server \"baley\": Connection established ... \"negotiatedProtocolVersion\":\"2026-07-28\"". Searched every results/debug-*.log | grep -n -i protocolVersion results/debug-*.log | | + +## Concurrency + +Overlapping calls from two sessions, and from a parent with five subagents. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| conc.two-sessions | Sessions A and B at the same time: overlapping baley_version and help calls and distinct captures | Every call answers and every capture is recorded once, with no loss or silent merge | pass | Workaround pair as id.two-sessions. Both prompts sent in one tmux command; each session answered 5 baley_version, 5 help, 1 capture with no error result (A 11 results, sub session 11 plus its earlier capture). Capture 02 "overlap A" new receipt 11da91bb...; capture 03 "overlap B" returned the original receipt a37505785e... (03 was first sent in Part 6 step 5, so the overlap call is a replay, as the procedure lists). Counts: 01=1, 02=1, 03=1. Prompts for baley tools approved with the do-not-ask-again option to let the calls overlap | q "select request_id, count(*) from event where type = capture.recorded group by request_id"; transcripts 0530680a...jsonl and 5441ec1b...jsonl | | +| conc.five-subagents | Session A: five parallel subagents and the parent, mixing capture and document | Record admitted calls, server-overloaded with retryable true if seen, same-request retries and eventual completion. Otherwise write: saturation not observed (ADR 0034) | observed | saturation not observed. Five subagents and the parent each sent one capture; all six answered ok, recorded at 15:12:34.9, 37.1, 39.0, 41.1, parent 41.9, 43.9 (spread over 9 s, so Claude Code did not send them together); no server-overloaded answer, no same-request retry. Each subagent then called baley_query schema for document and baley_query document; all five document calls were refused: {"status":"refused","code":"invalid-arguments","reason":"invalid type: string ..., expected internally tagged enum Identity","slot":"arguments"}, because the identity object arrived as a JSON string (see hand.parts.document-main). Approval prompts for baley tools were already set to do-not-ask-again | subagents/agent-*.jsonl in the session A transcript folder (tool_use input identity is a string); grep -c server-overloaded on all transcripts -> 0; Part 7 ledger query | note: saturation not observed; the five subagent document calls were refused for the cause in issue #232. | + +## Exits + +One row per exiting server. Each server's standard error is in results/server-stderr/.log. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| exit.session-a | Session A exits | Exactly one 'baley: exit checkpoint' line in its stderr file | fail | server-stderr/522573.log holds only "start 2026-10-08T14:49:34Z pid 522573", no exit checkpoint line, after a normal /exit. Claude Code ended the server with SIGINT ("MCP server \"baley\": Sending SIGINT to MCP server process", then "exited cleanly"); serve.rs listens for input end and SIGTERM only (crates/baley/src/mcp/serve.rs lines 96-134), so SIGINT ends the process by its default action and the checkpoint never runs | results/server-stderr/522573.log; debug-session-a.log lines 6689 and 6702 | defect: issue #231, bug pull request #233 (open when classed). Design 0012 section 7 Figure 1 and design 0001 Checkpoints say the server makes its one exit checkpoint attempt when it is told to end; Claude Code ends it with SIGINT, which run in crates/baley/src/mcp/serve.rs does not handle. | +| exit.session-b | Session B exits | Exactly one exit checkpoint line | unavailable | Session B (CLAUDE_CONFIG_DIR isolated config) needs an interactive OAuth login: after the theme screen it showed "Select login method: 1. Claude account with subscription 2. Anthropic Console account 3. 3rd-party platform". Login not attempted (owner credentials not used) | session B never started | unverified | +| exit.resume-id | The resume-id session exits | Exactly one exit checkpoint line | fail | server-stderr/590012.log holds only its start line, no exit checkpoint line, after a normal /exit; Claude Code sent SIGINT and logged the process "exited cleanly", and the server has no SIGINT handler (see exit.session-a) | results/server-stderr/590012.log; debug-resume-id.log (1 SIGINT) | defect: issue #231, bug pull request #233 (open when classed). Same cause as exit.session-a: design 0012 section 7 Figure 1 and design 0001 Checkpoints (one exit checkpoint attempt when the server is told to end), owned by run in crates/baley/src/mcp/serve.rs. | +| exit.resume | The resume session exits | Exactly one exit checkpoint line | fail | server-stderr/594597.log (the resume launch that picked the seed conversation) holds only its start line, after a normal /exit; Claude Code sent SIGINT and logged the process "exited cleanly", and the server has no SIGINT handler (see exit.session-a) | results/server-stderr/594597.log; debug-resume.log line 770 | defect: issue #231, bug pull request #233 (open when classed). Same cause as exit.session-a: design 0012 section 7 Figure 1 and design 0001 Checkpoints (one exit checkpoint attempt when the server is told to end), owned by run in crates/baley/src/mcp/serve.rs. | +| exit.continue | The continue session exits | Exactly one exit checkpoint line | fail | server-stderr/596756.log (first continue launch) holds only its start line, after a normal /exit; Claude Code sent SIGINT and logged the process "exited cleanly", and the server has no SIGINT handler (see exit.session-a) | results/server-stderr/596756.log; debug-continue.log (1 SIGINT) | defect: issue #231, bug pull request #233 (open when classed). Same cause as exit.session-a: design 0012 section 7 Figure 1 and design 0001 Checkpoints (one exit checkpoint attempt when the server is told to end), owned by run in crates/baley/src/mcp/serve.rs. | +| exit.invalid-project | The invalid-project session exits | Exactly one exit checkpoint line | fail | server-stderr/611539.log holds only its start line, after a normal /exit; Claude Code sent SIGINT and logged the process "exited cleanly", and the server has no SIGINT handler (see exit.session-a) | results/server-stderr/611539.log; debug-invalid-project.log | defect: issue #231, bug pull request #233 (open when classed). Same cause as exit.session-a: design 0012 section 7 Figure 1 and design 0001 Checkpoints (one exit checkpoint attempt when the server is told to end), owned by run in crates/baley/src/mcp/serve.rs. | +| exit.missing-project | The missing-project session exits | Exactly one exit checkpoint line | fail | server-stderr/613784.log holds only its start line, after a normal /exit; Claude Code sent SIGINT and logged the process "exited cleanly", and the server has no SIGINT handler (see exit.session-a) | results/server-stderr/613784.log; debug-missing-project.log | defect: issue #231, bug pull request #233 (open when classed). Same cause as exit.session-a: design 0012 section 7 Figure 1 and design 0001 Checkpoints (one exit checkpoint attempt when the server is told to end), owned by run in crates/baley/src/mcp/serve.rs. | +| exit.no-session-id | The no-session-id session exits | Exactly one exit checkpoint line | fail | server-stderr/615841.log holds only its start line, after a normal /exit; Claude Code sent SIGINT and logged the process "exited cleanly", and the server has no SIGINT handler (see exit.session-a) | results/server-stderr/615841.log; debug-no-session-id.log | defect: issue #231, bug pull request #233 (open when classed). Same cause as exit.session-a: design 0012 section 7 Figure 1 and design 0001 Checkpoints (one exit checkpoint attempt when the server is told to end), owned by run in crates/baley/src/mcp/serve.rs. | +| exit.fork | The fork session exits | Exactly one exit checkpoint line | fail | server-stderr/617276.log holds only its start line, after a normal /exit; Claude Code sent SIGINT and logged the process "exited cleanly", and the server has no SIGINT handler (see exit.session-a) | results/server-stderr/617276.log; debug-fork.log | defect: issue #231, bug pull request #233 (open when classed). Same cause as exit.session-a: design 0012 section 7 Figure 1 and design 0001 Checkpoints (one exit checkpoint attempt when the server is told to end), owned by run in crates/baley/src/mcp/serve.rs. | +| exit.absent-sandbox | The absent-sandbox session exits | Exactly one exit checkpoint line, if a server started | unavailable | No server started: Claude Code refused to start without a working sandbox (prot.absent-sandbox) | debug-absent-sandbox.log line 189; no server-stderr file for this launch | unverified | +| exit.fallback | The fallback session exits | Exactly one exit checkpoint line | fail | server-stderr/619197.log holds only its start line, after a normal /exit; Claude Code sent SIGINT and logged the process "exited cleanly", and the server has no SIGINT handler (see exit.session-a) | results/server-stderr/619197.log; debug-fallback.log | defect: issue #231, bug pull request #233 (open when classed). Same cause as exit.session-a: design 0012 section 7 Figure 1 and design 0001 Checkpoints (one exit checkpoint attempt when the server is told to end), owned by run in crates/baley/src/mcp/serve.rs. | +| exit.other-servers | Any other server file in results/server-stderr (a restart after /cd, /clear or /branch) | Exactly one exit checkpoint line each, recorded with the command that ended it | observed | No server restarted after /cd, /clear or /branch (session A kept 522573). Extra launches: 555705 (sub-started workaround session, /exit by SIGINT: no exit line); 592059 (first resume launch, which quit at "This conversation is from a different directory" without an interactive teardown and no SIGINT in its log: exactly one line, "baley: exit checkpoint complete, every logged change is in the database file"); 593064 (seed session-a launch, SIGINT: no line); 598633, 601596, 603724 (overlap session-a launches, SIGINT: no line); 606547 (continue launch for the burst, Exit and stop tasks, SIGINT: no line); 609511 (continue launch for the replay, SIGINT: no line) | for f in results/server-stderr/*; cat; SIGINT counts per debug-*.log | note: seven of the eight extra server files hold no exit line, the same cause as exit.session-a (issue #231); the one file with a line ended without SIGINT. | +| exit.overlap-1 | Overlap 1: close one session while the other writes and invokes the guard | The remaining session keeps making progress, and the guard answers inside 10,000 ms | pass | Workaround: session B unavailable, so the staying session was the sub-started explicit session (b-sub). Prompt adds kind note, which the step omits. New session-a launch (server 598633) got /exit at 15:32:12.8 (SIGINT sent 15:32:12.812Z) while b-sub captured 51-54 at 15:32:10.2, 14.8, 19.0, 23.8, each once, with three denied commits between, guard elapsed 47, 47, 47 ms | ledger capture.recorded 51-54; hook-timing.jsonl Bash deny lines 15:32:12-21Z; debug-session-a-overlap-1.log lines 323, 343 | | +| exit.overlap-2 | Overlap 2, as above | As above | pass | Workaround: session B unavailable, so the staying session was the sub-started explicit session (b-sub). Prompt adds kind note, which the step omits. New session-a launch (server 601596) got /exit at 15:33:33.1 while b-sub captured 61-64 at 15:33:29.1, 33.8, 38.1, 42.6, each once, guard elapsed 46, 47, 47 ms | ledger capture.recorded 61-64; hook-timing.jsonl; debug-session-a-overlap-2.log SIGINT 15:33:33.147Z | | +| exit.overlap-3 | Overlap 3, as above | As above | pass | Workaround: session B unavailable, so the staying session was the sub-started explicit session (b-sub). Prompt adds kind note, which the step omits. New session-a launch (server 603724) got /exit at 15:34:29.7 while b-sub captured 71-74 at 15:34:26.7, 30.9, 35.1, 39.4, each once, guard elapsed 46, 47, 47 ms | ledger capture.recorded 71-74; hook-timing.jsonl; debug-session-a-overlap-3.log SIGINT 15:34:29.703Z | | +| exit.burst | Burst at exit: a burst of calls in flight when a session exits (#190) | Every call that was read is answered (server-overloaded at worst), and the drain line appears if the 10-second bound passed (ADR 0034) | unavailable | The burst-at-exit condition could not be created: /exit typed at 15:36:12.09Z, 0.6 s after the first capture call, was held by Claude Code until the turn ended; all six captures 81-86 completed meanwhile (debug log: six Calling/completed pairs 15:36:11.475 to 15:36:17.006, each about 66 ms; ledger: 81-86 each once). Claude Code then showed "Background work is running ... subagent Capture burst 1-3 ... 1. Exit and stop tasks", chosen at 15:36:52; SIGINT sent 15:36:52.376Z. No call was in flight at exit, so neither an answer to a read-but-unfinished call nor the drain line could be observed; server-stderr/606547.log holds only its start line (no drain line, no exit line, see exit.continue) | debug-continue-burst.log lines 445-654, 733, 744; ledger capture.recorded 81-86; results/server-stderr/606547.log | unverified | +| exit.no-idle-checkpoint | Every stderr file, outside the exit | No checkpoint line other than at exit: none exists in code | pass | The only checkpoint line in any file is the exit line of 592059, written when that server ended; no file holds a checkpoint line before its exit | cat results/server-stderr/*.log (17 files) | | + +## Replay + +A capture sent again with its original request id after the server restarted. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| rep.same-id | Same request_id and input after a restart | The original receipt, and the capture count for that request_id stays one | pass | Restarted server (continue launch, server-stderr/609511.log). Same request id 02 and input returned the original receipt: id 11da91bbd9cdb7aeabd13c98fd8b7b78cfb000b35e3da22eac71ce1bde413402, recorded_at 2026-10-08T15:10:56.174008773Z (the overlap capture). Count of capture.recorded for 02 = 1 | transcript d5a18069...jsonl toolu_01MWaaTw5WjacFFa8x58gfmB; q "select count(*) ... request_id = ...02" -> 1 | | +| rep.changed-input | Same request_id with changed text | Refused as request-id-reuse, with nothing recorded | pass | {"status":"refused","code":"request-id-reuse","reason":"this request_id was already used for a different capture, so nothing was recorded. A new capture needs a new request_id","slot":"request_id"}; count for 02 still 1 | transcript toolu_013jBZLMCG9xaQ33YX1vajd7; count query -> 1 | | +| rep.stale-expected | Stale expected observations | Not applicable: no served operation carries one | observed | Not applicable: no served operation carries an expected observation (design 0012); prefilled, nothing run | live-claude.md Part 11 step 3 | | + +## Variants + +Sessions whose project or session id is missing, invalid or shared. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| var.invalid.project-calls | invalid-project session: capture and document | failed, with a code naming CLAUDE_PROJECT_DIR as the place | pass | The registration env did override CLAUDE_PROJECT_DIR. Capture 91 "invalid project" and document each answered {"status":"failed","code":"project-context-invalid","reason":"CLAUDE_PROJECT_DIR cannot be the project: it is not an existing directory","place":"CLAUDE_PROJECT_DIR","recorded":false,"retryable":false}. (The document identity again arrived as a string; the project gate answered first.) No prompt (baley tools allowed in projects/one/.claude/settings.local.json by the earlier do-not-ask-again choices) | transcript 231d3586-8381-4aca-9560-eca917ca575c.jsonl toolu_01YY8r8jkvXXj2vxCVq3YWme, toolu_01EeZpVoA3T8iZUbJb7tCPNe; server-stderr/611539.log | | +| var.invalid.free-calls | invalid-project session: baley_version, help, schema and instruction | All four answer | pass | baley_version ok (0.1.0 linux x86_64); help ok; schema apply/capture ok; instruction bal-help ok (version 1, hash 6a546a62...) | transcript 231d3586...jsonl toolu_01G2pzgFWnuYjrXYrHkJWkR6, toolu_01XbdEnX5ampwmUbMJPmECAT, toolu_0132b4Q2Q6cX2XBj7Asr7uy3, toolu_014YfYASmsjafg47gCJWEt5k | | +| var.missing.project-calls | missing-project session: capture and document | failed, with a code naming CLAUDE_PROJECT_DIR as the place | pass | Capture 92 "missing project" and document each answered {"status":"failed","code":"project-context-missing","reason":"CLAUDE_PROJECT_DIR is not set, so Baley does not know the project","place":"CLAUDE_PROJECT_DIR","recorded":false,"retryable":false} | transcript dde4b2c0-68fa-4286-9135-a20f6bb551ba.jsonl toolu_012mNSvNCm4jGQHvqVKKsBS8, toolu_01LFi2cvmr5mSsaPGVn611jp; server-stderr/613784.log | | +| var.missing.free-calls | missing-project session: baley_version, help, schema and instruction | All four answer | pass | baley_version ok; help ok; schema apply/capture ok; instruction bal-help ok | transcript dde4b2c0...jsonl toolu_01GHqWwxP6FWjSzt28GWX99Z, toolu_01XEwrxz41kt5szubqrekcaH, toolu_01Ed2T1z6VmWE9yFUc5R8ZcH, toolu_01JHx9ktySamQb3sJx7odHKh | | +| var.fork | fork session: capture | Refused as project-id-conflict: the fork shares project one's id under another remote | pass | The registration env did override CLAUDE_PROJECT_DIR (projects/fork). Capture 93 "fork": {"status":"failed","code":"project-id-conflict","reason":"the checkout at .../projects/fork has the remote .../remotes/fork.git, but the project already has a checkout at .../projects/one with the remote .../remotes/one.git, so this one is a fork. The owner gives this checkout its own project by running `baley init --new-id` in it","place":"checkout","recorded":false,"retryable":false}; no event with request id 93. Status is failed, as design 0003 Figure 7 states for a server refusal. Trust prompt for projects/fork and a permission prompt for baley_apply appeared, approved | transcript under ~/.claude/projects/-home-john--local-share-baley-live-projects-fork, toolu_01DoLpE31Hd4uiYpQmZUtY2H; count of events with request 93 -> 0; server-stderr/617276.log | | + +## Hand-offs + +What the earlier builds hand to this run. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| hand.init | Owner init: results/init-one.txt and init-one.status | Exit status 0, baley.toml written, project recorded | observed | Status 0; "wrote .../projects/one/baley.toml (commit this file)", "created project a3fb4cb2-4ffa-4b35-8353-da344038d260 in the ledger at .../data/crenshawdev/baley", "recorded project.initialized for project a3fb4cb2-..." | cat results/init-one.txt results/init-one.status | | +| hand.config-show | In project one: baley config show | Host-specific settings listed with their layers | observed | Exit 0. Every setting listed with global, project and effective layers (roles.*.model and effort, escalate_on_failure, git.remote, git.protected_branches, git.on_protected, git.guard_hard_fail); git.on_protected effective "refuse" from project (.../projects/one/baley.toml); global file .../config/crenshawdev/baley/config.toml. No host section listed: neither file has a [host.claude-code] section, and the output with --host claude-code is identical | baley config show in projects/one with the disposable XDG roots; same with --host claude-code, diff identical | note: neither config file has a host-specific section, so none was listed. | +| hand.nearer-file | Session B started in sub: the project the server bound | Project one, found by the nearer baley.toml | pass | Workaround session started in projects/one/sub with the explicit registration (session-a launch, cd to sub, debug-session-b-sub.log), since session B could not log in. Capture 03 "overlap B" recorded in project a3fb4cb2 (project one) seq 11; its caller has project_directory and working_directory .../projects/one/sub. Permission prompt for baley_apply appeared, approved | q "select project_id, seq, request_id from event where type = capture.recorded" -> a3fb4cb2 11 ...03 | | +| hand.checkout-admission | The ledger's checkout rows for project one | Project one's checkout admitted | observed | Project one a3fb4cb2 seq 3 checkout.seen and seq 4 command.completed (checkout.admit) from baley init; project two 8ed38901 the same; the fork has no row of its own | q "select project_id, seq, type from event where type like checkout.% or stream like command/checkout%" | | +| hand.keys-detection | baley models update in the owner's real environment, after the post-run rows | Reports detection per provider with a key, a failed detection exits 0 | unavailable | left to the owner: touches the real environment | coordinator instruction for this run | unverified | +| hand.restore-doctor | baley doctor and the restore report on the disposable ledger | No finding on a ledger no restore touched | observed | doctor exit 0 with no finding on this ledger (integrity ok, views no differences, claims 0). Restore part unavailable: no restore was run on this ledger, so there is no restored chain to report on | results/reads.txt lines 78-142 | note: doctor reported no finding, and no restore was run, so the restore report was not exercised. | +| hand.parts.help | help read whole | One part, whole | pass | One answer, status ok, 4,819 bytes, no part field. No prompt (do-not-ask-again set for baley_query in Part 6) | session-A-transcript tool_result toolu_01RzDQCkvV1Gqoh2i7B8837e (utf8 byte length and test for "part" via jq) | | +| hand.parts.instruction | instruction for bal-help read whole | One part, whole | pass | One whole answer, status ok, identity bal-help, version 1, hash 6a546a62...; 1,366 bytes, no part field | session-A-transcript tool_result toolu_01EKXWs8EFBJGfmdhb11SM56 | | +| hand.parts.document-main | document of the large capture, part 1, in the main session | A part of exactly 24,576 bytes arrives whole, naming the next part | fail | baley_query declares a flat inputSchema (operation enum plus additionalProperties true, crates/baley/src/mcp/tools.rs operation_schema), so the nested identity object (and part) reach the server as JSON strings: input {"operation":"document","identity":"{\"kind\":\"capture\",\"id\":\"fc6f5000...\"}","part":"1"}. Answer: {"status":"refused","code":"invalid-arguments","reason":"invalid type: string ..., expected internally tagged enum Identity","slot":"arguments"}. No part, bound or body arrived, so the 24,576-byte part was not measured. The document operation cannot be reached from Claude Code 2.1.294 | session-A-transcript toolu_01LDtkpXRSLS6ZyMGnoJJCAF (tool_use input and tool_result) | defect: issue #232, fix prepared and its pull request not yet opened when classed. Design 0012 HST-R6 (a capture is read by identity through document, in parts) and HST-R5 (the advertised schema of baley_query); Claude Code sends the nested identity as a JSON string and the server refuses it. Owned by operation_schema in crates/baley/src/mcp/tools.rs. | +| hand.parts.document-subagent | document of the large capture, part 1, in a subagent | A part of exactly 24,576 bytes arrives whole, naming the next part | fail | Same refusal in a subagent: baley_query declares a flat inputSchema (operation enum plus additionalProperties true, crates/baley/src/mcp/tools.rs operation_schema), so the nested identity object (and part) reach the server as JSON strings: input {"operation":"document","identity":"{\"kind\":\"capture\",\"id\":\"fc6f5000...\"}","part":"1"}. Answer: {"status":"refused","code":"invalid-arguments","reason":"invalid type: string ..., expected internally tagged enum Identity","slot":"arguments"}. No part, bound or body arrived, so the 24,576-byte part was not measured. The document operation cannot be reached from Claude Code 2.1.294 | subagents/agent-a96f96d17242b7748.jsonl toolu_012tWSwWWeHgpbFLHtZeLSm1 | defect: issue #232, fix prepared and its pull request not yet opened when classed. Design 0012 HST-R6 (a capture is read by identity through document, in parts) and HST-R5 (the advertised schema of baley_query); Claude Code sends the nested identity as a JSON string and the server refuses it. Owned by operation_schema in crates/baley/src/mcp/tools.rs. | +| hand.instruction-evidence | instruction for bal-capture, then a capture naming it as instruction | The capture's caller carries the instruction evidence | pass | instruction bal-capture answered version 1, hash 52867052...; capture 23 "instruction evidence note" with instruction bal-capture recorded; its caller.instructions = [{"hash":"52867052fb5ae23a0962120f07e6fbfa3ba0010bf2e52c109351ff8a1e8b7c5e","identity":"bal-capture","version":"1"}] | q "select request_id, json_extract(caller, $.instructions) from event where request_id = ...23"; transcript toolu_019Qq1aULi48R81ePr7CEVtX | | +| hand.tools.explicit-main | Session A: baley_version, baley_query and baley_apply callable in the main session without a tool search | All three visible (HST-R20) | pass | All three callable without a tool search (no ToolSearch for baley tools in the transcript; ToolSearch used only for NotebookEdit and Monitor): baley_version ok 0.1.0 linux x86_64; help ok; capture 31 first refused "missing field kind" because the step gives no kind (procedure error), resent with kind note: ok id 48d839d4..., text "tools A main" | session-A-transcript toolu_01SFE6o7Z9Fa9Aukp9TCdFKH, toolu_012VieQDWyRi3v1CWYNegHSH, toolu_01Dei5GK5TsmBzxkH8FRoZpc (refused), toolu_01EXZJ3WpeDUY2ftvVHmfihH (ok) | | +| hand.tools.explicit-subagent | Session A: the same three in a subagent | All three visible (HST-R20) | pass | Subagent called baley_version (ok), baley_query help (ok) and baley_apply capture 32 kind note text "tools A subagent" (ok id dd5dc83f...) with no tool search | subagents/agent-a206b0e2380a4c3cb.jsonl | | +| hand.tools.user-main | Session B (alwaysLoad registration): the same three in the main session | All three visible (HST-R20) | unavailable | Session B (CLAUDE_CONFIG_DIR isolated config) needs an interactive OAuth login: after the theme screen it showed "Select login method: 1. Claude account with subscription 2. Anthropic Console account 3. 3rd-party platform". Login not attempted (owner credentials not used) | session B never started | unverified | +| hand.tools.user-subagent | Session B: the same three in a subagent | All three visible (HST-R20) | unavailable | Session B (CLAUDE_CONFIG_DIR isolated config) needs an interactive OAuth login: after the theme screen it showed "Select login method: 1. Claude account with subscription 2. Anthropic Console account 3. 3rd-party platform". Login not attempted (owner credentials not used) | session B never started | unverified | +| hand.skill-listed | Session B: the bal-help skill from the isolated configuration | Listed in the session | unavailable | Session B (CLAUDE_CONFIG_DIR isolated config) needs an interactive OAuth login: after the theme screen it showed "Select login method: 1. Claude account with subscription 2. Anthropic Console account 3. 3rd-party platform". Login not attempted (owner credentials not used). The project-placed fallback was not run, since it is defined as a step after session B fails to list the skill | session B never started | unverified | +| hand.skill-run | Session B: run the bal-help skill | It calls baley_query, each call asking for approval since a stub carries no allowed-tools line (ADR 0009) | unavailable | Session B (CLAUDE_CONFIG_DIR isolated config) needs an interactive OAuth login: after the theme screen it showed "Select login method: 1. Claude account with subscription 2. Anthropic Console account 3. 3rd-party platform". Login not attempted (owner credentials not used) | session B never started | unverified | + +## Post-run + +After every session has exited: sh live-claude-reads.sh > ~/.local/share/baley-live/results/reads.txt. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| post.verify-one | baley verify --local-only for project one | Exit status 0 | pass | verify --local-only project one: chain head 84, exit status 0 | results/reads.txt lines 30-39 | | +| post.views-one | baley verify --views for project one | Exit status 0 | pass | verify --views project one: views checked at sequence 84, no differences, exit status 0 | results/reads.txt lines 41-44 | | +| post.verify-user | baley verify --local-only user | Exit status 0 | pass | verify --local-only user: chain head 48, exit status 0 | results/reads.txt lines 62-71 | | +| post.views-user | baley verify --views user | Exit status 0 | pass | verify --views user: views checked at sequence 48, no differences, exit status 0 | results/reads.txt lines 73-76 | | +| post.doctor | baley doctor | Exit status 0 | pass | doctor: integrity ok, three projects with no differences, exit status 0 | results/reads.txt lines 78-142 | | +| post.stream-versions | Per project and stream: stream_version unique and increasing | No stream with a lowest version other than 1 or a count other than its span | pass | Every stream starts at 1 and its count equals its span (e.g. capture 38 events 1 to 38, user guard 25 events 1 to 25); the incomplete-streams section is empty | results/reads.txt lines 144-161 | | +| post.captures-once | Each expected capture exactly once, with its caller | One capture.recorded per request_id the rows sent | pass | One capture.recorded each for 01, 02, 03, 11-16, 21, 23, 31, 32, 41-46, 51-54, 61-64, 71-74, 81-86 and 94 (38 ids, events_for_request 1 each), each with its baley_session; 23 carries the bal-capture instruction evidence. None for 91, 92, 93. 33 and 34 were never sent (session B unavailable) | results/reads.txt lines 227-267 | | +| post.text-equal | Stored text equal to the text each row sent (large capture: byte count and SHA-256) | Equal | pass | Every stored inline text equals the text the row sent (reads.txt captures section). Large capture 21: reads.txt prints body sha256 ea5c4ff1... because it hashes the stored bytes, which are zstd-compressed (payload.encoding zstd, 610 bytes stored, magic 28B52FFD); decompressed with zstd -d the body is 33000 bytes, sha256 6754341a445caf2f681748567772759cdf41bf0865425e360da5fcfd8dc1c0d4, equal to fixtures/large.txt and to the payload id. The text sent in the tool call also hashes to 6754341a... | results/reads.txt lines 269-271; sqlite3 select hex(body) from payload \| xxd -r -p \| zstd -d -c \| sha256sum; jq -j of the tool_use text in the session A transcript \| sha256sum | | +| post.no-loss | No capture lost, none silently merged | Every request_id sent appears once | pass | Every request id sent and accepted appears exactly once; none merged (02 and 03 replays returned their original receipts and left count 1) | results/reads.txt captures section | | +| post.real-folders | The owner's real Baley folders against pins.txt | No difference | pass | real-home: no difference; real-config: no difference | results/reads.txt lines 374-378 | | + +## Cited excerpts + +The lines the evidence cells above cite, copied from the results folder so this sheet holds them once that folder is cleared. Each block is headed by its row id, the file and the line numbers, and the owner's home folder is written `~`. For id.mcp-revision the line is the one its actual-outcome cell cites, since its evidence cell names a search and no line. + +### ses.a.panels + +debug-session-a.log line 18 + +```text +2026-10-08T14:49:22.692Z [DEBUG] Applying permission update: Adding 15 deny rule(s) to destination 'flagSettings': ["Read(/~/.local/share/baley-live/data/crenshawdev/baley/**)","Edit(/~/.local/share/baley-live/data/crenshawdev/baley/**)","Read(/~/.local/share/baley-live/config/crenshawdev/baley/**)","Edit(/~/.local/share/baley-live/config/crenshawdev/baley/**)","Edit(/~/.local/share/baley-live/projects/one/baley.toml)","Edit(/~/.local/share/baley-live/projects/two/baley.toml)","Edit(/~/.local/share/baley-live/claude-config/skills/bal-help/SKILL.md)","Edit(/~/.local/share/baley-live/results/settings.json)","Edit(/~/.local/share/baley-live/results/mcp-explicit.json)","Edit(/~/.local/share/baley-live/results/user-scope-entry.json)","Edit(/~/.local/share/baley-live/results/mcp-invalid-project.json)","Edit(/~/.local/share/baley-live/results/mcp-missing-project.json)","Edit(/~/.local/share/baley-live/results/mcp-no-session-id.json)","Edit(/~/.local/share/baley-live/results/mcp-fork.json)","Edit(/~/.local/share/baley-live/bin/baley)"] +``` + +### ses.a.smoke-commit + +hook-timing.jsonl line 1 + +```text +{"tool_name":"Bash","tool_use_id":"toolu_01Hno9mH1eHj9pUQzaM9BJgq","session_id":"0530680a-af4b-4c04-8cda-1875c07d76b2","cwd":"~/.local/share/baley-live/projects/one","start_ms":1791471070641,"end_ms":1791471070689,"elapsed_ms":48,"exit":0,"permissionDecision":"deny"} +``` + +### prot.absent-sandbox + +debug-absent-sandbox.log line 189 + +```text +2026-10-08T15:44:11.772Z [WARN] "[stderr] \nError: sandbox required but unavailable: sandbox is enabled but dependencies are missing: bubblewrap (bwrap) not installed, socat not installed · install missing tools (e.g. apt install bubblewrap socat) or run /sandbox for details\n sandbox.failIfUnavailable is set — refusing to start without a working sandbox." +``` + +### ctl.hook.bash + +reads.txt line 355 + +```text +Bash calls=41 highest_ms=48 median_ms=1 +``` + +### ctl.hook.monitor + +reads.txt line 356 + +```text +Monitor calls=13 highest_ms=46 median_ms=1 +``` + +### ctl.hook.powershell + +reads.txt lines 354 to 357 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds == +Bash calls=41 highest_ms=48 median_ms=1 +Monitor calls=13 highest_ms=46 median_ms=1 +Read calls=4 highest_ms=1 median_ms=1 +``` + +### ctl.hook.read + +reads.txt line 357 + +```text +Read calls=4 highest_ms=1 median_ms=1 +``` + +### ctl.hook.grep + +reads.txt lines 354 to 357 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds == +Bash calls=41 highest_ms=48 median_ms=1 +Monitor calls=13 highest_ms=46 median_ms=1 +Read calls=4 highest_ms=1 median_ms=1 +``` + +### ctl.hook.glob + +reads.txt lines 354 to 357 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds == +Bash calls=41 highest_ms=48 median_ms=1 +Monitor calls=13 highest_ms=46 median_ms=1 +Read calls=4 highest_ms=1 median_ms=1 +``` + +### ctl.hook.write + +reads.txt lines 354 to 357 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds == +Bash calls=41 highest_ms=48 median_ms=1 +Monitor calls=13 highest_ms=46 median_ms=1 +Read calls=4 highest_ms=1 median_ms=1 +``` + +### ctl.hook.edit + +reads.txt lines 354 to 357 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds == +Bash calls=41 highest_ms=48 median_ms=1 +Monitor calls=13 highest_ms=46 median_ms=1 +Read calls=4 highest_ms=1 median_ms=1 +``` + +### ctl.hook.notebookedit + +reads.txt lines 354 to 357 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds == +Bash calls=41 highest_ms=48 median_ms=1 +Monitor calls=13 highest_ms=46 median_ms=1 +Read calls=4 highest_ms=1 median_ms=1 +``` + +### ctl.grep-parent-guard + +reads.txt lines 273 to 298 + +```text +== guard answers by decision, with call ids == +decision seq tool verb branch call_id +-------- --- ------- ------ ------ ------------------------------ +ask 8 Bash push toolu_01XFdS8GJnDDjauogMuo4KxP +ask 10 Bash push toolu_01NqjNzgberCRRyXZ3iWHwjB +ask 12 Monitor push toolu_01Jz4bbbaHUQNW8WVpsRhAKB +ask 14 Monitor push toolu_01Qqy51qftFjffXMXPDMyewp +ask 47 Bash commit main toolu_019AjGo5KoQRF1CDKHYHLC9a +deny 2 Bash commit main toolu_01Hno9mH1eHj9pUQzaM9BJgq +deny 4 Bash commit main toolu_01FXqPr96WMjnqja5N5N9rKW +deny 6 Monitor commit main toolu_01G2Xgd3xtZHQWv4JvYSt77S +deny 17 Bash commit main toolu_01CYnN5Cvr82qbimoGRpTzM8 +deny 19 Bash commit main toolu_01M4ExHNFHNfzC16BGfXDVgK +deny 21 Bash commit main toolu_0193YP6mSQ8NkX8pPBHAmJVM +deny 23 Bash commit main toolu_01RGgVACh9qvjGKFGzjaPSWx +deny 25 Bash commit main toolu_01H5ipX4QUEU4z2QkoAnK76a +deny 27 Bash commit main toolu_01HYLNczn1nwEKugmHpVERiv +deny 29 Bash commit main toolu_01NBD2KeCW3ftqB2DfBDaJcS +deny 31 Bash commit main toolu_01NrERDCZksndM4qVtKUovDa +deny 33 Bash commit main toolu_01C14FEu9AmWzShuL2UGUqWo +deny 35 Bash commit main toolu_011tp5T5eopmCyFiwadm7xgE +deny 37 Bash commit main toolu_01TYefJaCvc25Gq3ioqKXj8C +deny 39 Bash commit main toolu_01YbTGmfAb68BPL9amdST11C +deny 41 Bash commit main toolu_01Lf5i2e14DtE2ttBwKUp2Tx +deny 43 Bash commit main toolu_01GwBErGPyxM5oodJTydom1S +deny 45 Bash commit main toolu_012DFnakv5Q9LmZYiioPt7ru +``` + +### ctl.glob-parent-guard + +reads.txt lines 273 to 298 + +```text +== guard answers by decision, with call ids == +decision seq tool verb branch call_id +-------- --- ------- ------ ------ ------------------------------ +ask 8 Bash push toolu_01XFdS8GJnDDjauogMuo4KxP +ask 10 Bash push toolu_01NqjNzgberCRRyXZ3iWHwjB +ask 12 Monitor push toolu_01Jz4bbbaHUQNW8WVpsRhAKB +ask 14 Monitor push toolu_01Qqy51qftFjffXMXPDMyewp +ask 47 Bash commit main toolu_019AjGo5KoQRF1CDKHYHLC9a +deny 2 Bash commit main toolu_01Hno9mH1eHj9pUQzaM9BJgq +deny 4 Bash commit main toolu_01FXqPr96WMjnqja5N5N9rKW +deny 6 Monitor commit main toolu_01G2Xgd3xtZHQWv4JvYSt77S +deny 17 Bash commit main toolu_01CYnN5Cvr82qbimoGRpTzM8 +deny 19 Bash commit main toolu_01M4ExHNFHNfzC16BGfXDVgK +deny 21 Bash commit main toolu_0193YP6mSQ8NkX8pPBHAmJVM +deny 23 Bash commit main toolu_01RGgVACh9qvjGKFGzjaPSWx +deny 25 Bash commit main toolu_01H5ipX4QUEU4z2QkoAnK76a +deny 27 Bash commit main toolu_01HYLNczn1nwEKugmHpVERiv +deny 29 Bash commit main toolu_01NBD2KeCW3ftqB2DfBDaJcS +deny 31 Bash commit main toolu_01NrERDCZksndM4qVtKUovDa +deny 33 Bash commit main toolu_01C14FEu9AmWzShuL2UGUqWo +deny 35 Bash commit main toolu_011tp5T5eopmCyFiwadm7xgE +deny 37 Bash commit main toolu_01TYefJaCvc25Gq3ioqKXj8C +deny 39 Bash commit main toolu_01YbTGmfAb68BPL9amdST11C +deny 41 Bash commit main toolu_01Lf5i2e14DtE2ttBwKUp2Tx +deny 43 Bash commit main toolu_01GwBErGPyxM5oodJTydom1S +deny 45 Bash commit main toolu_012DFnakv5Q9LmZYiioPt7ru +``` + +### ctl.write-baleytoml-denied + +reads.txt lines 273 to 298 + +```text +== guard answers by decision, with call ids == +decision seq tool verb branch call_id +-------- --- ------- ------ ------ ------------------------------ +ask 8 Bash push toolu_01XFdS8GJnDDjauogMuo4KxP +ask 10 Bash push toolu_01NqjNzgberCRRyXZ3iWHwjB +ask 12 Monitor push toolu_01Jz4bbbaHUQNW8WVpsRhAKB +ask 14 Monitor push toolu_01Qqy51qftFjffXMXPDMyewp +ask 47 Bash commit main toolu_019AjGo5KoQRF1CDKHYHLC9a +deny 2 Bash commit main toolu_01Hno9mH1eHj9pUQzaM9BJgq +deny 4 Bash commit main toolu_01FXqPr96WMjnqja5N5N9rKW +deny 6 Monitor commit main toolu_01G2Xgd3xtZHQWv4JvYSt77S +deny 17 Bash commit main toolu_01CYnN5Cvr82qbimoGRpTzM8 +deny 19 Bash commit main toolu_01M4ExHNFHNfzC16BGfXDVgK +deny 21 Bash commit main toolu_0193YP6mSQ8NkX8pPBHAmJVM +deny 23 Bash commit main toolu_01RGgVACh9qvjGKFGzjaPSWx +deny 25 Bash commit main toolu_01H5ipX4QUEU4z2QkoAnK76a +deny 27 Bash commit main toolu_01HYLNczn1nwEKugmHpVERiv +deny 29 Bash commit main toolu_01NBD2KeCW3ftqB2DfBDaJcS +deny 31 Bash commit main toolu_01NrERDCZksndM4qVtKUovDa +deny 33 Bash commit main toolu_01C14FEu9AmWzShuL2UGUqWo +deny 35 Bash commit main toolu_011tp5T5eopmCyFiwadm7xgE +deny 37 Bash commit main toolu_01TYefJaCvc25Gq3ioqKXj8C +deny 39 Bash commit main toolu_01YbTGmfAb68BPL9amdST11C +deny 41 Bash commit main toolu_01Lf5i2e14DtE2ttBwKUp2Tx +deny 43 Bash commit main toolu_01GwBErGPyxM5oodJTydom1S +deny 45 Bash commit main toolu_012DFnakv5Q9LmZYiioPt7ru +``` + +### ctl.latency + +reads.txt lines 354 to 357 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds == +Bash calls=41 highest_ms=48 median_ms=1 +Monitor calls=13 highest_ms=46 median_ms=1 +Read calls=4 highest_ms=1 median_ms=1 +``` + +### ctl.timeout-not-denial + +reads.txt lines 368 to 369 + +```text +== hook timing: calls at or above 10,000 ms == +(no lines above means none) +``` + +### ctl.redelivery + +reads.txt lines 371 to 372 + +```text +== hook timing: tool_use_id seen more than once == +(no lines above means none) +``` + +### id.mcp-revision + +debug-session-a.log line 264 + +```text +2026-10-08T14:49:34.734Z [DEBUG] MCP server "baley": Connection established with capabilities: {"hasTools":true,"hasPrompts":false,"hasResources":false,"hasResourceSubscribe":false,"serverVersion":{"name":"baley","version":"0.1.0"},"protocolEra":"modern","negotiatedProtocolVersion":"2026-07-28"} +``` + +### exit.session-a + +debug-session-a.log line 6689 + +```text +2026-10-08T15:26:12.113Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +debug-session-a.log line 6702 + +```text +2026-10-08T15:26:12.163Z [DEBUG] MCP server "baley": MCP server process exited cleanly +``` + +### exit.resume + +debug-resume.log line 770 + +```text +2026-10-08T15:30:40.400Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +### exit.absent-sandbox + +debug-absent-sandbox.log line 189 + +```text +2026-10-08T15:44:11.772Z [WARN] "[stderr] \nError: sandbox required but unavailable: sandbox is enabled but dependencies are missing: bubblewrap (bwrap) not installed, socat not installed · install missing tools (e.g. apt install bubblewrap socat) or run /sandbox for details\n sandbox.failIfUnavailable is set — refusing to start without a working sandbox." +``` + +### exit.overlap-1 + +debug-session-a-overlap-1.log line 323 + +```text +2026-10-08T15:32:12.812Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +debug-session-a-overlap-1.log line 343 + +```text +2026-10-08T15:32:12.862Z [DEBUG] MCP server "baley": MCP server process exited cleanly +``` + +### exit.burst + +debug-continue-burst.log lines 445 to 654 + +```text +2026-10-08T15:36:11.475Z [DEBUG] MCP server "baley": Calling MCP tool: baley_apply +2026-10-08T15:36:11.475Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 1.6ms (native link, next() included) +2026-10-08T15:36:11.519Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.9ms (native link, next() included) +2026-10-08T15:36:11.542Z [DEBUG] MCP server "baley": Tool 'baley_apply' completed successfully in 67ms +2026-10-08T15:36:11.542Z [INFO] [Stall] tool_dispatch_end tool=mcp_tool toolUseId=toolu_01DgBuJ8YhnvEy1AD6Pc1kGd outcome=ok durationMs=68 +2026-10-08T15:36:11.544Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.6ms (native link, next() included) +2026-10-08T15:36:11.550Z [DEBUG] Dynamic tool loading: 0/32 deferred tools included +2026-10-08T15:36:11.551Z [DEBUG] attribution header x-anthropic-billing-header: cc_version=2.1.294.fa6; cc_entrypoint=cli; cch=00000; cc_is_subagent=true; cc_prev_req=req_011Cfq1w1a3nuJcPuNF2t574; cc_prompt_id=f1609b3c-70df-418e-baa2-4692c8973168; +2026-10-08T15:36:11.551Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:11.552Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:11.555Z [INFO] [Stall] tool_dispatch_start tool=Agent toolUseId=toolu_016ZH64A43zj2GrFoiovZhZs permissionDecisionMs=0 +2026-10-08T15:36:11.556Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.9ms (native link, next() included) +2026-10-08T15:36:11.558Z [INFO] [Stall] tool_dispatch_end tool=Agent toolUseId=toolu_016ZH64A43zj2GrFoiovZhZs outcome=ok durationMs=3 +2026-10-08T15:36:11.560Z [DEBUG] Cleared all session hooks for session acf7cd2ce9e47d686 +2026-10-08T15:36:11.561Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 2.1ms (native link, next() included) +2026-10-08T15:36:11.562Z [DEBUG] Sending 64 skills via attachment (initial) +2026-10-08T15:36:11.567Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:11.567Z [DEBUG] [API:timing] dispatching to firstParty model=claude-opus-5-5 +2026-10-08T15:36:11.567Z [DEBUG] [dispatch] sent anthropic-dispatch-id=v2d +2026-10-08T15:36:11.568Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=bd3f60eb-f84f-487c-b8d9-10a108aa726b source=agent:builtin:general-purpose +2026-10-08T15:36:11.573Z [DEBUG] [remote-bridge] Sending 2 message(s) +2026-10-08T15:36:11.577Z [DEBUG] [AdvisorTool] Server-side tool enabled with claude-opus-5-5 as the advisor model +2026-10-08T15:36:11.579Z [DEBUG] Dynamic tool loading: 0/32 deferred tools included +2026-10-08T15:36:11.579Z [DEBUG] attribution header x-anthropic-billing-header: cc_version=2.1.294.fa6; cc_entrypoint=cli; cch=00000; cc_is_subagent=true; cc_prompt_id=f1609b3c-70df-418e-baa2-4692c8973168; +2026-10-08T15:36:11.579Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:11.580Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:11.590Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:11.590Z [DEBUG] [API:timing] dispatching to firstParty model=claude-opus-5-5 +2026-10-08T15:36:11.590Z [DEBUG] [dispatch] sent anthropic-dispatch-id=v2d +2026-10-08T15:36:11.590Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=88598645-2348-4e83-a118-bc82bcb06b52 source=agent:builtin:general-purpose +2026-10-08T15:36:11.593Z [DEBUG] [presence session=cse_01DoBBa12VhxMSdyFf1txDoY] pulse → https://api.anthropic.com/v1/code/sessions/cse_01DoBBa12VhxMSdyFf1txDoY/client/presence +2026-10-08T15:36:11.608Z [DEBUG] hooks module cc-plugin-diff@builtin ui.render settled in 4.1ms (native link, next() included) +2026-10-08T15:36:11.608Z [DEBUG] hooks module cc-plugin-diff@builtin ui.render settled in 2.5ms (native link, next() included) +2026-10-08T15:36:11.710Z [DEBUG] Stream started - received first chunk +2026-10-08T15:36:11.710Z [DEBUG] [API:timing] first byte after 1433ms +2026-10-08T15:36:12.098Z [DEBUG] High write ratio: blit=0, write=3464 (100.0% writes), screen=50x220 +2026-10-08T15:36:12.099Z [DEBUG] hooks module cc-plugin-diff@builtin ui.render settled in 3.0ms (native link, next() included) +2026-10-08T15:36:12.099Z [DEBUG] hooks module cc-plugin-diff@builtin ui.render settled in 1.7ms (native link, next() included) +2026-10-08T15:36:12.278Z [DEBUG] Stream started - received first chunk +2026-10-08T15:36:12.278Z [DEBUG] [API:timing] first byte after 688ms +2026-10-08T15:36:12.598Z [INFO] [Stall] tool_dispatch_start tool=mcp_tool toolUseId=toolu_01YDWqHCwEfgYra48nh7nkSj permissionDecisionMs=0 +2026-10-08T15:36:12.598Z [DEBUG] MCP server "baley": Calling MCP tool: baley_apply +2026-10-08T15:36:12.598Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.9ms (native link, next() included) +2026-10-08T15:36:12.613Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.9ms (native link, next() included) +2026-10-08T15:36:12.665Z [DEBUG] MCP server "baley": Tool 'baley_apply' completed successfully in 67ms +2026-10-08T15:36:12.665Z [INFO] [Stall] tool_dispatch_end tool=mcp_tool toolUseId=toolu_01YDWqHCwEfgYra48nh7nkSj outcome=ok durationMs=67 +2026-10-08T15:36:12.667Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.6ms (native link, next() included) +2026-10-08T15:36:12.673Z [DEBUG] Dynamic tool loading: 0/32 deferred tools included +2026-10-08T15:36:12.674Z [DEBUG] attribution header x-anthropic-billing-header: cc_version=2.1.294.fa6; cc_entrypoint=cli; cch=00000; cc_is_subagent=true; cc_prev_req=req_011Cfq1w6RzbTf2qrEm2v5re; cc_prompt_id=f1609b3c-70df-418e-baa2-4692c8973168; +2026-10-08T15:36:12.674Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:12.675Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:12.676Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:12.676Z [DEBUG] [API:timing] dispatching to firstParty model=claude-opus-5-5 +2026-10-08T15:36:12.676Z [DEBUG] [dispatch] sent anthropic-dispatch-id=v2d +2026-10-08T15:36:12.676Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=2fd03c5d-fcf7-4052-bd1e-12be3526396c source=agent:builtin:general-purpose +2026-10-08T15:36:13.102Z [INFO] [Stall] tool_dispatch_start tool=Agent toolUseId=toolu_011LoDm4iNbuTMZXgUxEYL6f permissionDecisionMs=1 +2026-10-08T15:36:13.103Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 1.1ms (native link, next() included) +2026-10-08T15:36:13.105Z [INFO] [Stall] tool_dispatch_end tool=Agent toolUseId=toolu_011LoDm4iNbuTMZXgUxEYL6f outcome=ok durationMs=3 +2026-10-08T15:36:13.107Z [DEBUG] Cleared all session hooks for session a91afd1ac42a92d1e +2026-10-08T15:36:13.109Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 2.2ms (native link, next() included) +2026-10-08T15:36:13.109Z [DEBUG] Sending 64 skills via attachment (initial) +2026-10-08T15:36:13.120Z [DEBUG] [remote-bridge] Sending 2 message(s) +2026-10-08T15:36:13.122Z [DEBUG] [AdvisorTool] Server-side tool enabled with claude-opus-5-5 as the advisor model +2026-10-08T15:36:13.124Z [DEBUG] Dynamic tool loading: 0/32 deferred tools included +2026-10-08T15:36:13.125Z [DEBUG] attribution header x-anthropic-billing-header: cc_version=2.1.294.fa6; cc_entrypoint=cli; cch=00000; cc_is_subagent=true; cc_prompt_id=f1609b3c-70df-418e-baa2-4692c8973168; +2026-10-08T15:36:13.125Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:13.126Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:13.128Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:13.128Z [DEBUG] [API:timing] dispatching to firstParty model=claude-opus-5-5 +2026-10-08T15:36:13.128Z [DEBUG] [dispatch] sent anthropic-dispatch-id=v2d +2026-10-08T15:36:13.129Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=a789adc8-8384-4440-8a84-08e9f93dcfef source=agent:builtin:general-purpose +2026-10-08T15:36:13.532Z [INFO] [Stall] tool_dispatch_start tool=mcp_tool toolUseId=toolu_015VLCkFo2oTgjiH1NE8iwz3 permissionDecisionMs=1 +2026-10-08T15:36:13.533Z [DEBUG] MCP server "baley": Calling MCP tool: baley_apply +2026-10-08T15:36:13.533Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.9ms (native link, next() included) +2026-10-08T15:36:13.548Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.6ms (native link, next() included) +2026-10-08T15:36:13.599Z [DEBUG] MCP server "baley": Tool 'baley_apply' completed successfully in 66ms +2026-10-08T15:36:13.599Z [INFO] [Stall] tool_dispatch_end tool=mcp_tool toolUseId=toolu_015VLCkFo2oTgjiH1NE8iwz3 outcome=ok durationMs=67 +2026-10-08T15:36:13.601Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.6ms (native link, next() included) +2026-10-08T15:36:13.607Z [DEBUG] Dynamic tool loading: 0/32 deferred tools included +2026-10-08T15:36:13.608Z [DEBUG] attribution header x-anthropic-billing-header: cc_version=2.1.294.fa6; cc_entrypoint=cli; cch=00000; cc_is_subagent=true; cc_prev_req=req_011Cfq1wC17AC6aNfr1fhUWw; cc_prompt_id=f1609b3c-70df-418e-baa2-4692c8973168; +2026-10-08T15:36:13.608Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:13.608Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:13.610Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:13.610Z [DEBUG] [API:timing] dispatching to firstParty model=claude-opus-5-5 +2026-10-08T15:36:13.610Z [DEBUG] [dispatch] sent anthropic-dispatch-id=v2d +2026-10-08T15:36:13.610Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=0caf8526-cd3e-4e17-ac49-42b9368b8be4 source=agent:builtin:general-purpose +2026-10-08T15:36:13.987Z [DEBUG] Stream started - received first chunk +2026-10-08T15:36:13.987Z [DEBUG] [API:timing] first byte after 2419ms +2026-10-08T15:36:13.999Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 1.6ms (native link, next() included) +2026-10-08T15:36:14.000Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.4ms (native link, next() included) +2026-10-08T15:36:14.000Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.1ms (native link, next() included) +2026-10-08T15:36:14.022Z [DEBUG] Hook output does not start with {, treating as plain text +2026-10-08T15:36:14.023Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.4ms (native link, next() included) +2026-10-08T15:36:14.024Z [DEBUG] hooks module cc-plugin-agents-md@builtin turn.complete settled in 0.4ms (native link, next() included) +2026-10-08T15:36:14.024Z [DEBUG] [AgentSummary] Stopping summarization for a5288520b8dc914c2 +2026-10-08T15:36:14.024Z [INFO] [Stall] agent_completion agentId=a5288520b8dc914c2 agentType=general-purpose exitPath=completed durationMs=4907 turns=2 finalStopReason=end_turn lastChunkAgeMs=1 lastToolUseId=toolu_01DgBuJ8YhnvEy1AD6Pc1kGd lastToolResultSeen=toolu_01DgBuJ8YhnvEy1AD6Pc1kGd +2026-10-08T15:36:14.041Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 1.2ms (native link, next() included) +2026-10-08T15:36:14.046Z [DEBUG] hooks module cc-plugin-diff@builtin ui.render settled in 0.7ms (native link, next() included) +2026-10-08T15:36:14.048Z [DEBUG] Preserving file permissions: 100600 +2026-10-08T15:36:14.048Z [DEBUG] Writing to temp file: ~/.claude.json.tmp.606392.04649965491d +2026-10-08T15:36:14.048Z [DEBUG] Applied original permissions to temp file +2026-10-08T15:36:14.051Z [DEBUG] Temp file written successfully, size: 126790 bytes +2026-10-08T15:36:14.051Z [DEBUG] Renaming ~/.claude.json.tmp.606392.04649965491d to ~/.claude.json +2026-10-08T15:36:14.051Z [DEBUG] File ~/.claude.json written atomically +2026-10-08T15:36:14.069Z [DEBUG] Stream started - received first chunk +2026-10-08T15:36:14.069Z [DEBUG] [API:timing] first byte after 941ms +2026-10-08T15:36:14.671Z [INFO] [Stall] tool_dispatch_start tool=Agent toolUseId=toolu_01P9PGbJhvuA8PQjbpNgfgC6 permissionDecisionMs=1 +2026-10-08T15:36:14.672Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 1.1ms (native link, next() included) +2026-10-08T15:36:14.674Z [INFO] [Stall] tool_dispatch_end tool=Agent toolUseId=toolu_01P9PGbJhvuA8PQjbpNgfgC6 outcome=ok durationMs=3 +2026-10-08T15:36:14.676Z [DEBUG] Cleared all session hooks for session a4a891002f7e4ff45 +2026-10-08T15:36:14.678Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 2.3ms (native link, next() included) +2026-10-08T15:36:14.678Z [DEBUG] Sending 64 skills via attachment (initial) +2026-10-08T15:36:14.689Z [DEBUG] [remote-bridge] Sending 2 message(s) +2026-10-08T15:36:14.692Z [DEBUG] [AdvisorTool] Server-side tool enabled with claude-opus-5-5 as the advisor model +2026-10-08T15:36:14.694Z [DEBUG] Dynamic tool loading: 0/32 deferred tools included +2026-10-08T15:36:14.694Z [DEBUG] attribution header x-anthropic-billing-header: cc_version=2.1.294.fa6; cc_entrypoint=cli; cch=00000; cc_is_subagent=true; cc_prompt_id=f1609b3c-70df-418e-baa2-4692c8973168; +2026-10-08T15:36:14.695Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:14.695Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:14.696Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:14.697Z [DEBUG] [API:timing] dispatching to firstParty model=claude-opus-5-5 +2026-10-08T15:36:14.697Z [DEBUG] [dispatch] sent anthropic-dispatch-id=v2d +2026-10-08T15:36:14.697Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=9d47e842-4c86-4a48-834c-a7240fd71608 source=agent:builtin:general-purpose +2026-10-08T15:36:14.722Z [DEBUG] High write ratio: blit=440, write=3464 (88.7% writes), screen=50x220 +2026-10-08T15:36:14.837Z [DEBUG] Stream started - received first chunk +2026-10-08T15:36:14.837Z [DEBUG] [API:timing] first byte after 2162ms +2026-10-08T15:36:14.852Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.9ms (native link, next() included) +2026-10-08T15:36:14.852Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.3ms (native link, next() included) +2026-10-08T15:36:14.853Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.1ms (native link, next() included) +2026-10-08T15:36:14.874Z [DEBUG] Hook output does not start with {, treating as plain text +2026-10-08T15:36:14.875Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.3ms (native link, next() included) +2026-10-08T15:36:14.876Z [DEBUG] hooks module cc-plugin-agents-md@builtin turn.complete settled in 0.2ms (native link, next() included) +2026-10-08T15:36:14.876Z [DEBUG] [AgentSummary] Stopping summarization for a542af2f81fcc65c6 +2026-10-08T15:36:14.876Z [INFO] [Stall] agent_completion agentId=a542af2f81fcc65c6 agentType=general-purpose exitPath=completed durationMs=4625 turns=2 finalStopReason=end_turn lastChunkAgeMs=1 lastToolUseId=toolu_01YDWqHCwEfgYra48nh7nkSj lastToolResultSeen=toolu_01YDWqHCwEfgYra48nh7nkSj +2026-10-08T15:36:14.878Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.8ms (native link, next() included) +2026-10-08T15:36:14.940Z [INFO] [Stall] tool_dispatch_start tool=mcp_tool toolUseId=toolu_01E5KzT5UjTxFHsUMitUAHaW permissionDecisionMs=1 +2026-10-08T15:36:14.940Z [DEBUG] MCP server "baley": Calling MCP tool: baley_apply +2026-10-08T15:36:14.940Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 1.0ms (native link, next() included) +2026-10-08T15:36:14.947Z [DEBUG] [remote-bridge] Sending 1 message(s) +2026-10-08T15:36:14.962Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.9ms (native link, next() included) +2026-10-08T15:36:15.008Z [DEBUG] MCP server "baley": Tool 'baley_apply' completed successfully in 68ms +2026-10-08T15:36:15.008Z [INFO] [Stall] tool_dispatch_end tool=mcp_tool toolUseId=toolu_01E5KzT5UjTxFHsUMitUAHaW outcome=ok durationMs=68 +2026-10-08T15:36:15.010Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 1.0ms (native link, next() included) +2026-10-08T15:36:15.012Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.3ms (native link, next() included) +2026-10-08T15:36:15.017Z [DEBUG] [remote-bridge] Sending 1 message(s) +2026-10-08T15:36:15.063Z [DEBUG] Hook output does not start with {, treating as plain text +2026-10-08T15:36:15.063Z [DEBUG] "Hook UserPromptSubmit (UserPromptSubmit) success:\nstderr:\nterminus: UserPromptSubmit abandoned its injection after 50 ms" +2026-10-08T15:36:15.064Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.5ms (native link, next() included) +2026-10-08T15:36:15.065Z [DEBUG] hooks module cc-plugin-diff@builtin prompt.submit settled in 53.8ms (native link, next() included) +2026-10-08T15:36:15.065Z [DEBUG] hooks module cc-plugin-agents-md@builtin prompt.submit settled in 54.4ms (native link, next() included) +2026-10-08T15:36:15.065Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.4ms (native link, next() included) +2026-10-08T15:36:15.114Z [DEBUG] Hook output does not start with {, treating as plain text +2026-10-08T15:36:15.115Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.3ms (native link, next() included) +2026-10-08T15:36:15.117Z [DEBUG] LSP Diagnostics: getLSPDiagnosticAttachments called +2026-10-08T15:36:15.117Z [DEBUG] LSP Diagnostics: Checking registry - 0 pending +2026-10-08T15:36:15.117Z [DEBUG] Hooks: Found 0 total hooks in registry +2026-10-08T15:36:15.117Z [DEBUG] Hooks: checkForNewResponses returning 0 responses +2026-10-08T15:36:15.118Z [DEBUG] hooks module cc-plugin-diff@builtin prompt.submit settled in 52.8ms (native link, next() included) +2026-10-08T15:36:15.118Z [DEBUG] hooks module cc-plugin-agents-md@builtin prompt.submit settled in 53.0ms (native link, next() included) +2026-10-08T15:36:15.126Z [DEBUG] Dynamic tool loading: 0/43 deferred tools included +2026-10-08T15:36:15.127Z [DEBUG] attribution header x-anthropic-billing-header: cc_version=2.1.294.7e1; cc_entrypoint=cli; cch=00000; cc_prev_req=req_011Cfq1vk9bABdy6dAo2T3XH; cc_prompt_id=f1609b3c-70df-418e-baa2-4692c8973168; cc_turn_origin=human; cc_prompt_index=5; cc_turn_index=5; +2026-10-08T15:36:15.128Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:15.128Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:15.129Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:15.129Z [DEBUG] [API:timing] dispatching to firstParty model=claude-opus-5-5 +2026-10-08T15:36:15.129Z [DEBUG] [dispatch] sent anthropic-dispatch-id=v2d +2026-10-08T15:36:15.130Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=cdc52af0-0cb3-4863-8791-64c2af5513e6 source=repl_main_thread:outputStyle:Concise +2026-10-08T15:36:15.171Z [DEBUG] High write ratio: blit=1320, write=3183 (70.7% writes), screen=50x220 +2026-10-08T15:36:15.311Z [DEBUG] Stream started - received first chunk +2026-10-08T15:36:15.311Z [DEBUG] [API:timing] first byte after 1702ms +2026-10-08T15:36:15.324Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.8ms (native link, next() included) +2026-10-08T15:36:15.325Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.3ms (native link, next() included) +2026-10-08T15:36:15.325Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.1ms (native link, next() included) +2026-10-08T15:36:15.327Z [INFO] [Stall] tool_dispatch_start tool=mcp_tool toolUseId=toolu_014gqvsgV6VJHDLKGHakA8W4 permissionDecisionMs=0 +2026-10-08T15:36:15.327Z [DEBUG] MCP server "baley": Calling MCP tool: baley_apply +2026-10-08T15:36:15.327Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.5ms (native link, next() included) +2026-10-08T15:36:15.343Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.5ms (native link, next() included) +2026-10-08T15:36:15.346Z [DEBUG] Hook output does not start with {, treating as plain text +2026-10-08T15:36:15.347Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.3ms (native link, next() included) +2026-10-08T15:36:15.347Z [DEBUG] hooks module cc-plugin-agents-md@builtin turn.complete settled in 0.2ms (native link, next() included) +2026-10-08T15:36:15.347Z [DEBUG] [AgentSummary] Stopping summarization for acf7cd2ce9e47d686 +2026-10-08T15:36:15.347Z [INFO] [Stall] agent_completion agentId=acf7cd2ce9e47d686 agentType=general-purpose exitPath=completed durationMs=3789 turns=2 finalStopReason=end_turn lastChunkAgeMs=0 lastToolUseId=toolu_015VLCkFo2oTgjiH1NE8iwz3 lastToolResultSeen=toolu_015VLCkFo2oTgjiH1NE8iwz3 +2026-10-08T15:36:15.349Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.7ms (native link, next() included) +2026-10-08T15:36:15.353Z [DEBUG] Preserving file permissions: 100600 +2026-10-08T15:36:15.353Z [DEBUG] Writing to temp file: ~/.claude.json.tmp.606392.62aa7571c8c5 +2026-10-08T15:36:15.353Z [DEBUG] Applied original permissions to temp file +2026-10-08T15:36:15.356Z [DEBUG] Temp file written successfully, size: 126790 bytes +2026-10-08T15:36:15.356Z [DEBUG] Renaming ~/.claude.json.tmp.606392.62aa7571c8c5 to ~/.claude.json +2026-10-08T15:36:15.356Z [DEBUG] File ~/.claude.json written atomically +2026-10-08T15:36:15.392Z [DEBUG] MCP server "baley": Tool 'baley_apply' completed successfully in 65ms +2026-10-08T15:36:15.392Z [INFO] [Stall] tool_dispatch_end tool=mcp_tool toolUseId=toolu_014gqvsgV6VJHDLKGHakA8W4 outcome=ok durationMs=65 +2026-10-08T15:36:15.393Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.5ms (native link, next() included) +2026-10-08T15:36:15.398Z [DEBUG] Dynamic tool loading: 0/32 deferred tools included +2026-10-08T15:36:15.399Z [DEBUG] attribution header x-anthropic-billing-header: cc_version=2.1.294.fa6; cc_entrypoint=cli; cch=00000; cc_is_subagent=true; cc_prev_req=req_011Cfq1wJajH4mg58kdsbq2L; cc_prompt_id=f1609b3c-70df-418e-baa2-4692c8973168; +2026-10-08T15:36:15.399Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:15.400Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:15.400Z [DEBUG] Fast mode unavailable: Fast mode requires usage credits · /usage-credits to turn them on +2026-10-08T15:36:15.401Z [DEBUG] [API:timing] dispatching to firstParty model=claude-opus-5-5 +2026-10-08T15:36:15.401Z [DEBUG] [dispatch] sent anthropic-dispatch-id=v2d +2026-10-08T15:36:15.401Z [DEBUG] [API REQUEST] /v1/messages x-client-request-id=9cddaecd-b2ba-413e-b583-88b80c7627a7 source=agent:builtin:general-purpose +2026-10-08T15:36:15.681Z [DEBUG] Stream started - received first chunk +2026-10-08T15:36:15.681Z [DEBUG] [API:timing] first byte after 985ms +2026-10-08T15:36:16.332Z [DEBUG] Stream started - received first chunk +2026-10-08T15:36:16.332Z [DEBUG] [API:timing] first byte after 1203ms +2026-10-08T15:36:16.341Z [DEBUG] Stream started - received first chunk +2026-10-08T15:36:16.341Z [DEBUG] [API:timing] first byte after 941ms +2026-10-08T15:36:16.939Z [INFO] [Stall] tool_dispatch_start tool=mcp_tool toolUseId=toolu_01MbgUeEEQXgNGG69Z8m2kb1 permissionDecisionMs=0 +2026-10-08T15:36:16.940Z [DEBUG] MCP server "baley": Calling MCP tool: baley_apply +2026-10-08T15:36:16.940Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.8ms (native link, next() included) +2026-10-08T15:36:16.949Z [DEBUG] hooks module cc-plugin-telemetry@builtin telemetry.log settled in 0.6ms (native link, next() included) +2026-10-08T15:36:17.006Z [DEBUG] MCP server "baley": Tool 'baley_apply' completed successfully in 66ms +``` + +debug-continue-burst.log line 733 + +```text +2026-10-08T15:36:52.376Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +debug-continue-burst.log line 744 + +```text +2026-10-08T15:36:52.427Z [DEBUG] MCP server "baley": MCP server process exited cleanly +``` + +### hand.restore-doctor + +reads.txt lines 78 to 142 + +```text +== doctor == +epoch 1 +integrity: ok +database 573440 bytes, log 0 bytes +project 8ed38901-e916-4e92-a16a-d6cc36a43910 (two): not checked against a remote from this directory +chain head 6 4555bec46a1a027415a7fcea819b049a71219d236da0dd8b32ff4d3e8baa39c3 +not compared with a remote anchor +unanchored sequences 1 to 6 +work unanchored since 2026-10-08T14:41:48.752911574Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T14:41:48.752911574Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 6 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project a3fb4cb2-4ffa-4b35-8353-da344038d260 (one): not checked against a remote from this directory +chain head 84 c0e3ace50595bc10dbb10103e5cc4e8eea7333be5f5be8a4f24c763beafb534c +not compared with a remote anchor +unanchored sequences 1 to 84 +work unanchored since 2026-10-08T14:41:48.687456733Z +bodies checked 1, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T14:41:48.687456733Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 84 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project user (per-user records): not checked against a remote from this directory +chain head 48 14c97848af9d126d640ef18efee2a1bf9df46ab4b702bf2d896563c7942bac29 +not compared with a remote anchor +unanchored sequences 1 to 48 +work unanchored since 2026-10-08T14:51:10.683521901Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T14:51:10.683521901Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 48 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +exit status: 0 +``` + +### post.verify-one + +reads.txt lines 30 to 39 + +```text +== verify --local-only a3fb4cb2-4ffa-4b35-8353-da344038d260 == +local only +checked at 2026-10-08T15:46:49.621529805Z +chain head 84 c0e3ace50595bc10dbb10103e5cc4e8eea7333be5f5be8a4f24c763beafb534c +not compared with a remote anchor +unanchored sequences 1 to 84 +work unanchored since 2026-10-08T14:41:48.687456733Z +bodies checked 1, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 +``` + +### post.views-one + +reads.txt lines 41 to 44 + +```text +== verify --views a3fb4cb2-4ffa-4b35-8353-da344038d260 == +views checked at sequence 84 +no differences +exit status: 0 +``` + +### post.verify-user + +reads.txt lines 62 to 71 + +```text +== verify --local-only user == +local only +checked at 2026-10-08T15:46:49.673614419Z +chain head 48 14c97848af9d126d640ef18efee2a1bf9df46ab4b702bf2d896563c7942bac29 +not compared with a remote anchor +unanchored sequences 1 to 48 +work unanchored since 2026-10-08T14:51:10.683521901Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 +``` + +### post.views-user + +reads.txt lines 73 to 76 + +```text +== verify --views user == +views checked at sequence 48 +no differences +exit status: 0 +``` + +### post.doctor + +reads.txt lines 78 to 142 + +```text +== doctor == +epoch 1 +integrity: ok +database 573440 bytes, log 0 bytes +project 8ed38901-e916-4e92-a16a-d6cc36a43910 (two): not checked against a remote from this directory +chain head 6 4555bec46a1a027415a7fcea819b049a71219d236da0dd8b32ff4d3e8baa39c3 +not compared with a remote anchor +unanchored sequences 1 to 6 +work unanchored since 2026-10-08T14:41:48.752911574Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T14:41:48.752911574Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 6 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project a3fb4cb2-4ffa-4b35-8353-da344038d260 (one): not checked against a remote from this directory +chain head 84 c0e3ace50595bc10dbb10103e5cc4e8eea7333be5f5be8a4f24c763beafb534c +not compared with a remote anchor +unanchored sequences 1 to 84 +work unanchored since 2026-10-08T14:41:48.687456733Z +bodies checked 1, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T14:41:48.687456733Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 84 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project user (per-user records): not checked against a remote from this directory +chain head 48 14c97848af9d126d640ef18efee2a1bf9df46ab4b702bf2d896563c7942bac29 +not compared with a remote anchor +unanchored sequences 1 to 48 +work unanchored since 2026-10-08T14:51:10.683521901Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T14:51:10.683521901Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 48 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +exit status: 0 +``` + +### post.stream-versions + +reads.txt lines 144 to 161 + +```text +== streams: events per project and stream, lowest and highest stream_version == + project_id stream events lowest highest +------------------------------------ ---------------------- ------ ------ ------- +8ed38901-e916-4e92-a16a-d6cc36a43910 command/checkout.admit 1 1 1 +8ed38901-e916-4e92-a16a-d6cc36a43910 command/policy.record 1 1 1 +8ed38901-e916-4e92-a16a-d6cc36a43910 command/project.init 1 1 1 +8ed38901-e916-4e92-a16a-d6cc36a43910 project 3 1 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 capture 38 1 38 +a3fb4cb2-4ffa-4b35-8353-da344038d260 command/capture.record 38 1 38 +a3fb4cb2-4ffa-4b35-8353-da344038d260 command/checkout.admit 1 1 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 command/policy.record 2 1 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 command/project.init 1 1 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 project 4 1 4 +user command/guard.record 23 1 23 +user guard 25 1 25 + +== streams that do not start at 1 or whose count differs from the span == +(no rows above means every stream is complete) +``` + +### post.captures-once + +reads.txt lines 227 to 267 + +```text +== captures: capture.recorded events per request_id with the caller's session and instruction evidence == + project_id seq request_id events_for_request baley_session instructions kind bytes text +------------------------------------ --- ------------------------------------ ------------------ ------------------------------------ -------------------------------------------------------------------------------------------------------------------- ----- ----- ------------------------- +a3fb4cb2-4ffa-4b35-8353-da344038d260 9 12120000-0000-4000-8000-000000000001 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 25 smoke note from session A +a3fb4cb2-4ffa-4b35-8353-da344038d260 11 12120000-0000-4000-8000-000000000003 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 9 overlap B +a3fb4cb2-4ffa-4b35-8353-da344038d260 13 12120000-0000-4000-8000-000000000002 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 9 overlap A +a3fb4cb2-4ffa-4b35-8353-da344038d260 15 12120000-0000-4000-8000-000000000011 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 10 subagent 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 17 12120000-0000-4000-8000-000000000012 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 10 subagent 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 19 12120000-0000-4000-8000-000000000013 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 10 subagent 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 21 12120000-0000-4000-8000-000000000014 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 10 subagent 4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 23 12120000-0000-4000-8000-000000000016 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 6 parent +a3fb4cb2-4ffa-4b35-8353-da344038d260 25 12120000-0000-4000-8000-000000000015 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 10 subagent 5 +a3fb4cb2-4ffa-4b35-8353-da344038d260 27 12120000-0000-4000-8000-000000000021 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 story 33000 +a3fb4cb2-4ffa-4b35-8353-da344038d260 29 12120000-0000-4000-8000-000000000023 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 [{"hash":"52867052fb5ae23a0962120f07e6fbfa3ba0010bf2e52c109351ff8a1e8b7c5e","identity":"bal-capture","version":"1"}] note 25 instruction evidence note +a3fb4cb2-4ffa-4b35-8353-da344038d260 31 12120000-0000-4000-8000-000000000031 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 12 tools A main +a3fb4cb2-4ffa-4b35-8353-da344038d260 33 12120000-0000-4000-8000-000000000032 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 16 tools A subagent +a3fb4cb2-4ffa-4b35-8353-da344038d260 35 12120000-0000-4000-8000-000000000041 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 8 after cd +a3fb4cb2-4ffa-4b35-8353-da344038d260 37 12120000-0000-4000-8000-000000000042 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 11 after clear +a3fb4cb2-4ffa-4b35-8353-da344038d260 39 12120000-0000-4000-8000-000000000043 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 12 after branch +a3fb4cb2-4ffa-4b35-8353-da344038d260 41 12120000-0000-4000-8000-000000000044 1 1fe1d7c2-fb10-45e5-a3b1-fbd0ac8c6701 note 15 after resume id +a3fb4cb2-4ffa-4b35-8353-da344038d260 43 12120000-0000-4000-8000-000000000045 1 41923495-e9a1-427c-a008-57dc8d148989 note 12 after resume +a3fb4cb2-4ffa-4b35-8353-da344038d260 45 12120000-0000-4000-8000-000000000046 1 4edff476-4da0-4176-a75f-18829e251715 note 14 after continue +a3fb4cb2-4ffa-4b35-8353-da344038d260 47 12120000-0000-4000-8000-000000000051 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 1 item 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 49 12120000-0000-4000-8000-000000000052 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 1 item 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 51 12120000-0000-4000-8000-000000000053 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 1 item 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 53 12120000-0000-4000-8000-000000000054 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 1 item 4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 55 12120000-0000-4000-8000-000000000061 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 2 item 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 57 12120000-0000-4000-8000-000000000062 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 2 item 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 59 12120000-0000-4000-8000-000000000063 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 2 item 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 61 12120000-0000-4000-8000-000000000064 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 2 item 4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 63 12120000-0000-4000-8000-000000000071 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 3 item 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 65 12120000-0000-4000-8000-000000000072 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 3 item 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 67 12120000-0000-4000-8000-000000000073 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 3 item 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 69 12120000-0000-4000-8000-000000000074 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 3 item 4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 71 12120000-0000-4000-8000-000000000081 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 73 12120000-0000-4000-8000-000000000082 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 75 12120000-0000-4000-8000-000000000083 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 77 12120000-0000-4000-8000-000000000086 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 6 +a3fb4cb2-4ffa-4b35-8353-da344038d260 79 12120000-0000-4000-8000-000000000084 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 81 12120000-0000-4000-8000-000000000085 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 5 +a3fb4cb2-4ffa-4b35-8353-da344038d260 83 12120000-0000-4000-8000-000000000094 1 02f79482-47a1-420a-a98e-66bed6471343 note 13 no session id +``` + +### post.text-equal + +reads.txt lines 269 to 271 + +```text +== stored capture bodies (text above 4,096 bytes): byte count and SHA-256 of the stored body == +large.txt on disk: 33000 bytes, sha256 6754341a445caf2f681748567772759cdf41bf0865425e360da5fcfd8dc1c0d4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 seq 27 payload 6754341a445caf2f681748567772759cdf41bf0865425e360da5fcfd8dc1c0d4: 33000 bytes, body sha256 ea5c4ff106d85a49ca6220621f0302eb233374ff09aa2f7646de243f9cb13f87 +``` + +### post.real-folders + +reads.txt lines 374 to 378 + +```text +== real-home against pins.txt == +~/.local/share/crenshawdev/baley: no difference + +== real-config against pins.txt == +~/.config/crenshawdev/baley: no difference +``` + +## Post-run reads + +The output of `sh live-claude-reads.sh`, as the coding agent that drove the run saved it in results/reads.txt. The block below is that file unchanged except that the home folder is written `~`. + +```text + +== pins == +date: 2026-10-08T14:41:48Z +platform: Linux 7.2.9-1-cachyos +bwrap: /usr/bin/bwrap +socat: /usr/bin/socat +sqlite3: /usr/bin/sqlite3 +jq: /usr/bin/jq +pwsh: absent +git: /usr/bin/git +telemetry-switch: unset +checkout-commit: 06b3dbe0a72c95e103b3bfef9a73adb582ba1f84 +checkout-status: clean +binary: baley 0.1.0 +binary-sha256: 2fbff61b7d143755d85d7b5dfe48b37e665fa547aef77176f65aaa5a016f54b8 +begin real-home ~/.local/share/crenshawdev/baley +~/.local/share/crenshawdev/baley/baley.db f 286720 2026-10-02 12:28:05.0100125140 +~/.local/share/crenshawdev/baley/baley.db.maintenance f 0 2026-10-02 12:25:03.5899264730 +~/.local/share/crenshawdev/baley/baley.db.writer f 0 2026-10-02 12:25:03.5899264730 +~/.local/share/crenshawdev/baley d 86 2026-10-05 09:42:21.6045176040 +end real-home +begin real-config ~/.config/crenshawdev/baley +~/.config/crenshawdev/baley d 16 2026-09-30 16:08:55.8750781170 +~/.config/crenshawdev/baley/keys.env f 183 2026-09-30 16:08:55.8767744790 +end real-config +project-one-id: a3fb4cb2-4ffa-4b35-8353-da344038d260 +project-two-id: 8ed38901-e916-4e92-a16a-d6cc36a43910 +project-fork-id: a3fb4cb2-4ffa-4b35-8353-da344038d260 + +== verify --local-only a3fb4cb2-4ffa-4b35-8353-da344038d260 == +local only +checked at 2026-10-08T15:46:49.621529805Z +chain head 84 c0e3ace50595bc10dbb10103e5cc4e8eea7333be5f5be8a4f24c763beafb534c +not compared with a remote anchor +unanchored sequences 1 to 84 +work unanchored since 2026-10-08T14:41:48.687456733Z +bodies checked 1, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 + +== verify --views a3fb4cb2-4ffa-4b35-8353-da344038d260 == +views checked at sequence 84 +no differences +exit status: 0 + +== verify --local-only 8ed38901-e916-4e92-a16a-d6cc36a43910 == +local only +checked at 2026-10-08T15:46:49.652048015Z +chain head 6 4555bec46a1a027415a7fcea819b049a71219d236da0dd8b32ff4d3e8baa39c3 +not compared with a remote anchor +unanchored sequences 1 to 6 +work unanchored since 2026-10-08T14:41:48.752911574Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 + +== verify --views 8ed38901-e916-4e92-a16a-d6cc36a43910 == +views checked at sequence 6 +no differences +exit status: 0 + +== verify --local-only user == +local only +checked at 2026-10-08T15:46:49.673614419Z +chain head 48 14c97848af9d126d640ef18efee2a1bf9df46ab4b702bf2d896563c7942bac29 +not compared with a remote anchor +unanchored sequences 1 to 48 +work unanchored since 2026-10-08T14:51:10.683521901Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 + +== verify --views user == +views checked at sequence 48 +no differences +exit status: 0 + +== doctor == +epoch 1 +integrity: ok +database 573440 bytes, log 0 bytes +project 8ed38901-e916-4e92-a16a-d6cc36a43910 (two): not checked against a remote from this directory +chain head 6 4555bec46a1a027415a7fcea819b049a71219d236da0dd8b32ff4d3e8baa39c3 +not compared with a remote anchor +unanchored sequences 1 to 6 +work unanchored since 2026-10-08T14:41:48.752911574Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T14:41:48.752911574Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 6 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project a3fb4cb2-4ffa-4b35-8353-da344038d260 (one): not checked against a remote from this directory +chain head 84 c0e3ace50595bc10dbb10103e5cc4e8eea7333be5f5be8a4f24c763beafb534c +not compared with a remote anchor +unanchored sequences 1 to 84 +work unanchored since 2026-10-08T14:41:48.687456733Z +bodies checked 1, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T14:41:48.687456733Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 84 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project user (per-user records): not checked against a remote from this directory +chain head 48 14c97848af9d126d640ef18efee2a1bf9df46ab4b702bf2d896563c7942bac29 +not compared with a remote anchor +unanchored sequences 1 to 48 +work unanchored since 2026-10-08T14:51:10.683521901Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T14:51:10.683521901Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 48 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +exit status: 0 + +== streams: events per project and stream, lowest and highest stream_version == + project_id stream events lowest highest +------------------------------------ ---------------------- ------ ------ ------- +8ed38901-e916-4e92-a16a-d6cc36a43910 command/checkout.admit 1 1 1 +8ed38901-e916-4e92-a16a-d6cc36a43910 command/policy.record 1 1 1 +8ed38901-e916-4e92-a16a-d6cc36a43910 command/project.init 1 1 1 +8ed38901-e916-4e92-a16a-d6cc36a43910 project 3 1 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 capture 38 1 38 +a3fb4cb2-4ffa-4b35-8353-da344038d260 command/capture.record 38 1 38 +a3fb4cb2-4ffa-4b35-8353-da344038d260 command/checkout.admit 1 1 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 command/policy.record 2 1 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 command/project.init 1 1 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 project 4 1 4 +user command/guard.record 23 1 23 +user guard 25 1 25 + +== streams that do not start at 1 or whose count differs from the span == +(no rows above means every stream is complete) + +== server callers: each session with the project ids of the events it wrote == + baley_session project_directory working_directory host_session client_version project_id first_seq last_seq events +------------------------------------ --------------------------------------------------- --------------------------------------------------- ------------------------------------ -------------- ------------------------------------ --------- -------- ------ +8a30b713-51ad-4f40-b4b3-ec90235e1c36 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one 0530680a-af4b-4c04-8cda-1875c07d76b2 2.1.294 a3fb4cb2-4ffa-4b35-8353-da344038d260 7 40 32 +05576664-a70c-46f0-bd5f-4cea04c5e107 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub 5441ec1b-bd75-4713-aa7b-eda6d8328010 2.1.294 a3fb4cb2-4ffa-4b35-8353-da344038d260 11 70 26 +1fe1d7c2-fb10-45e5-a3b1-fbd0ac8c6701 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one 0530680a-af4b-4c04-8cda-1875c07d76b2 2.1.294 a3fb4cb2-4ffa-4b35-8353-da344038d260 41 42 2 +41923495-e9a1-427c-a008-57dc8d148989 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one ea1cc869-14ca-4680-9011-4235930d6ed1 2.1.294 a3fb4cb2-4ffa-4b35-8353-da344038d260 43 44 2 +4edff476-4da0-4176-a75f-18829e251715 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one d5a18069-1e71-4b79-b979-76cd27410b1c 2.1.294 a3fb4cb2-4ffa-4b35-8353-da344038d260 45 46 2 +7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one d5a18069-1e71-4b79-b979-76cd27410b1c 2.1.294 a3fb4cb2-4ffa-4b35-8353-da344038d260 71 82 12 +02f79482-47a1-420a-a98e-66bed6471343 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one 2.1.294 a3fb4cb2-4ffa-4b35-8353-da344038d260 83 84 2 +project ids in pins.txt: one=a3fb4cb2-4ffa-4b35-8353-da344038d260 two=8ed38901-e916-4e92-a16a-d6cc36a43910 fork=a3fb4cb2-4ffa-4b35-8353-da344038d260 + +== hook callers in seq order == +project_id seq host_session working_directory project_directory call_id +---------- --- ------------------------------------ --------------------------------------------------- --------------------------------------------------- ------------------------------ +user 1 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Hno9mH1eHj9pUQzaM9BJgq +user 2 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Hno9mH1eHj9pUQzaM9BJgq +user 3 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Hno9mH1eHj9pUQzaM9BJgq +user 4 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01FXqPr96WMjnqja5N5N9rKW +user 5 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01FXqPr96WMjnqja5N5N9rKW +user 6 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01G2Xgd3xtZHQWv4JvYSt77S +user 7 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01G2Xgd3xtZHQWv4JvYSt77S +user 8 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01XFdS8GJnDDjauogMuo4KxP +user 9 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01XFdS8GJnDDjauogMuo4KxP +user 10 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01NqjNzgberCRRyXZ3iWHwjB +user 11 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01NqjNzgberCRRyXZ3iWHwjB +user 12 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Jz4bbbaHUQNW8WVpsRhAKB +user 13 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Jz4bbbaHUQNW8WVpsRhAKB +user 14 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Qqy51qftFjffXMXPDMyewp +user 15 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Qqy51qftFjffXMXPDMyewp +user 16 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/two ~/.local/share/baley-live/projects/one toolu_01CYnN5Cvr82qbimoGRpTzM8 +user 17 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/two ~/.local/share/baley-live/projects/one toolu_01CYnN5Cvr82qbimoGRpTzM8 +user 18 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/two ~/.local/share/baley-live/projects/one toolu_01CYnN5Cvr82qbimoGRpTzM8 +user 19 13fcf82a-87cd-4fc2-9fb5-424f6ccf6bb7 ~/.local/share/baley-live/projects/two ~/.local/share/baley-live/projects/one toolu_01M4ExHNFHNfzC16BGfXDVgK +user 20 13fcf82a-87cd-4fc2-9fb5-424f6ccf6bb7 ~/.local/share/baley-live/projects/two ~/.local/share/baley-live/projects/one toolu_01M4ExHNFHNfzC16BGfXDVgK +user 21 22d8aacc-23ee-41f5-adc6-35266d421b13 ~/.local/share/baley-live/projects/two ~/.local/share/baley-live/projects/one toolu_0193YP6mSQ8NkX8pPBHAmJVM +user 22 22d8aacc-23ee-41f5-adc6-35266d421b13 ~/.local/share/baley-live/projects/two ~/.local/share/baley-live/projects/one toolu_0193YP6mSQ8NkX8pPBHAmJVM +user 23 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01RGgVACh9qvjGKFGzjaPSWx +user 24 0530680a-af4b-4c04-8cda-1875c07d76b2 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01RGgVACh9qvjGKFGzjaPSWx +user 25 d5a18069-1e71-4b79-b979-76cd27410b1c ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01H5ipX4QUEU4z2QkoAnK76a +user 26 d5a18069-1e71-4b79-b979-76cd27410b1c ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01H5ipX4QUEU4z2QkoAnK76a +user 27 d5a18069-1e71-4b79-b979-76cd27410b1c ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01HYLNczn1nwEKugmHpVERiv +user 28 d5a18069-1e71-4b79-b979-76cd27410b1c ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01HYLNczn1nwEKugmHpVERiv +user 29 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01NBD2KeCW3ftqB2DfBDaJcS +user 30 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01NBD2KeCW3ftqB2DfBDaJcS +user 31 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01NrERDCZksndM4qVtKUovDa +user 32 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01NrERDCZksndM4qVtKUovDa +user 33 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01C14FEu9AmWzShuL2UGUqWo +user 34 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01C14FEu9AmWzShuL2UGUqWo +user 35 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_011tp5T5eopmCyFiwadm7xgE +user 36 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_011tp5T5eopmCyFiwadm7xgE +user 37 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01TYefJaCvc25Gq3ioqKXj8C +user 38 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01TYefJaCvc25Gq3ioqKXj8C +user 39 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01YbTGmfAb68BPL9amdST11C +user 40 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01YbTGmfAb68BPL9amdST11C +user 41 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01Lf5i2e14DtE2ttBwKUp2Tx +user 42 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01Lf5i2e14DtE2ttBwKUp2Tx +user 43 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01GwBErGPyxM5oodJTydom1S +user 44 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_01GwBErGPyxM5oodJTydom1S +user 45 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_012DFnakv5Q9LmZYiioPt7ru +user 46 5441ec1b-bd75-4713-aa7b-eda6d8328010 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub toolu_012DFnakv5Q9LmZYiioPt7ru +user 47 1b8fb626-a1e2-4ead-b97e-dc1263c8dae0 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_019AjGo5KoQRF1CDKHYHLC9a +user 48 1b8fb626-a1e2-4ead-b97e-dc1263c8dae0 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_019AjGo5KoQRF1CDKHYHLC9a + +== captures: capture.recorded events per request_id with the caller's session and instruction evidence == + project_id seq request_id events_for_request baley_session instructions kind bytes text +------------------------------------ --- ------------------------------------ ------------------ ------------------------------------ -------------------------------------------------------------------------------------------------------------------- ----- ----- ------------------------- +a3fb4cb2-4ffa-4b35-8353-da344038d260 9 12120000-0000-4000-8000-000000000001 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 25 smoke note from session A +a3fb4cb2-4ffa-4b35-8353-da344038d260 11 12120000-0000-4000-8000-000000000003 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 9 overlap B +a3fb4cb2-4ffa-4b35-8353-da344038d260 13 12120000-0000-4000-8000-000000000002 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 9 overlap A +a3fb4cb2-4ffa-4b35-8353-da344038d260 15 12120000-0000-4000-8000-000000000011 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 10 subagent 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 17 12120000-0000-4000-8000-000000000012 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 10 subagent 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 19 12120000-0000-4000-8000-000000000013 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 10 subagent 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 21 12120000-0000-4000-8000-000000000014 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 10 subagent 4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 23 12120000-0000-4000-8000-000000000016 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 6 parent +a3fb4cb2-4ffa-4b35-8353-da344038d260 25 12120000-0000-4000-8000-000000000015 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 10 subagent 5 +a3fb4cb2-4ffa-4b35-8353-da344038d260 27 12120000-0000-4000-8000-000000000021 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 story 33000 +a3fb4cb2-4ffa-4b35-8353-da344038d260 29 12120000-0000-4000-8000-000000000023 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 [{"hash":"52867052fb5ae23a0962120f07e6fbfa3ba0010bf2e52c109351ff8a1e8b7c5e","identity":"bal-capture","version":"1"}] note 25 instruction evidence note +a3fb4cb2-4ffa-4b35-8353-da344038d260 31 12120000-0000-4000-8000-000000000031 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 12 tools A main +a3fb4cb2-4ffa-4b35-8353-da344038d260 33 12120000-0000-4000-8000-000000000032 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 16 tools A subagent +a3fb4cb2-4ffa-4b35-8353-da344038d260 35 12120000-0000-4000-8000-000000000041 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 8 after cd +a3fb4cb2-4ffa-4b35-8353-da344038d260 37 12120000-0000-4000-8000-000000000042 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 11 after clear +a3fb4cb2-4ffa-4b35-8353-da344038d260 39 12120000-0000-4000-8000-000000000043 1 8a30b713-51ad-4f40-b4b3-ec90235e1c36 note 12 after branch +a3fb4cb2-4ffa-4b35-8353-da344038d260 41 12120000-0000-4000-8000-000000000044 1 1fe1d7c2-fb10-45e5-a3b1-fbd0ac8c6701 note 15 after resume id +a3fb4cb2-4ffa-4b35-8353-da344038d260 43 12120000-0000-4000-8000-000000000045 1 41923495-e9a1-427c-a008-57dc8d148989 note 12 after resume +a3fb4cb2-4ffa-4b35-8353-da344038d260 45 12120000-0000-4000-8000-000000000046 1 4edff476-4da0-4176-a75f-18829e251715 note 14 after continue +a3fb4cb2-4ffa-4b35-8353-da344038d260 47 12120000-0000-4000-8000-000000000051 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 1 item 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 49 12120000-0000-4000-8000-000000000052 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 1 item 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 51 12120000-0000-4000-8000-000000000053 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 1 item 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 53 12120000-0000-4000-8000-000000000054 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 1 item 4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 55 12120000-0000-4000-8000-000000000061 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 2 item 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 57 12120000-0000-4000-8000-000000000062 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 2 item 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 59 12120000-0000-4000-8000-000000000063 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 2 item 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 61 12120000-0000-4000-8000-000000000064 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 2 item 4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 63 12120000-0000-4000-8000-000000000071 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 3 item 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 65 12120000-0000-4000-8000-000000000072 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 3 item 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 67 12120000-0000-4000-8000-000000000073 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 3 item 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 69 12120000-0000-4000-8000-000000000074 1 05576664-a70c-46f0-bd5f-4cea04c5e107 note 22 overlap round 3 item 4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 71 12120000-0000-4000-8000-000000000081 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 1 +a3fb4cb2-4ffa-4b35-8353-da344038d260 73 12120000-0000-4000-8000-000000000082 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 2 +a3fb4cb2-4ffa-4b35-8353-da344038d260 75 12120000-0000-4000-8000-000000000083 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 3 +a3fb4cb2-4ffa-4b35-8353-da344038d260 77 12120000-0000-4000-8000-000000000086 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 6 +a3fb4cb2-4ffa-4b35-8353-da344038d260 79 12120000-0000-4000-8000-000000000084 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 81 12120000-0000-4000-8000-000000000085 1 7ce41d01-cc58-4d2e-80e8-f9b55b7919a7 note 7 burst 5 +a3fb4cb2-4ffa-4b35-8353-da344038d260 83 12120000-0000-4000-8000-000000000094 1 02f79482-47a1-420a-a98e-66bed6471343 note 13 no session id + +== stored capture bodies (text above 4,096 bytes): byte count and SHA-256 of the stored body == +large.txt on disk: 33000 bytes, sha256 6754341a445caf2f681748567772759cdf41bf0865425e360da5fcfd8dc1c0d4 +a3fb4cb2-4ffa-4b35-8353-da344038d260 seq 27 payload 6754341a445caf2f681748567772759cdf41bf0865425e360da5fcfd8dc1c0d4: 33000 bytes, body sha256 ea5c4ff106d85a49ca6220621f0302eb233374ff09aa2f7646de243f9cb13f87 + +== guard answers by decision, with call ids == +decision seq tool verb branch call_id +-------- --- ------- ------ ------ ------------------------------ +ask 8 Bash push toolu_01XFdS8GJnDDjauogMuo4KxP +ask 10 Bash push toolu_01NqjNzgberCRRyXZ3iWHwjB +ask 12 Monitor push toolu_01Jz4bbbaHUQNW8WVpsRhAKB +ask 14 Monitor push toolu_01Qqy51qftFjffXMXPDMyewp +ask 47 Bash commit main toolu_019AjGo5KoQRF1CDKHYHLC9a +deny 2 Bash commit main toolu_01Hno9mH1eHj9pUQzaM9BJgq +deny 4 Bash commit main toolu_01FXqPr96WMjnqja5N5N9rKW +deny 6 Monitor commit main toolu_01G2Xgd3xtZHQWv4JvYSt77S +deny 17 Bash commit main toolu_01CYnN5Cvr82qbimoGRpTzM8 +deny 19 Bash commit main toolu_01M4ExHNFHNfzC16BGfXDVgK +deny 21 Bash commit main toolu_0193YP6mSQ8NkX8pPBHAmJVM +deny 23 Bash commit main toolu_01RGgVACh9qvjGKFGzjaPSWx +deny 25 Bash commit main toolu_01H5ipX4QUEU4z2QkoAnK76a +deny 27 Bash commit main toolu_01HYLNczn1nwEKugmHpVERiv +deny 29 Bash commit main toolu_01NBD2KeCW3ftqB2DfBDaJcS +deny 31 Bash commit main toolu_01NrERDCZksndM4qVtKUovDa +deny 33 Bash commit main toolu_01C14FEu9AmWzShuL2UGUqWo +deny 35 Bash commit main toolu_011tp5T5eopmCyFiwadm7xgE +deny 37 Bash commit main toolu_01TYefJaCvc25Gq3ioqKXj8C +deny 39 Bash commit main toolu_01YbTGmfAb68BPL9amdST11C +deny 41 Bash commit main toolu_01Lf5i2e14DtE2ttBwKUp2Tx +deny 43 Bash commit main toolu_01GwBErGPyxM5oodJTydom1S +deny 45 Bash commit main toolu_012DFnakv5Q9LmZYiioPt7ru + +== server stderr: start line, exit checkpoint lines and any abandoned-drain line, per file == +-- ~/.local/share/baley-live/results/server-stderr/522573.log +start 2026-10-08T14:49:34Z pid 522573 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/555705.log +start 2026-10-08T15:09:09Z pid 555705 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/590012.log +start 2026-10-08T15:26:47Z pid 590012 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/592059.log +start 2026-10-08T15:27:59Z pid 592059 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/593064.log +start 2026-10-08T15:28:39Z pid 593064 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/594597.log +start 2026-10-08T15:29:35Z pid 594597 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/596756.log +start 2026-10-08T15:30:47Z pid 596756 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/598633.log +start 2026-10-08T15:31:50Z pid 598633 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/601596.log +start 2026-10-08T15:33:14Z pid 601596 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/603724.log +start 2026-10-08T15:34:11Z pid 603724 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/606547.log +start 2026-10-08T15:35:48Z pid 606547 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/609511.log +start 2026-10-08T15:37:31Z pid 609511 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/611539.log +start 2026-10-08T15:39:01Z pid 611539 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/613784.log +start 2026-10-08T15:40:38Z pid 613784 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/615841.log +start 2026-10-08T15:42:07Z pid 615841 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/617276.log +start 2026-10-08T15:43:12Z pid 617276 +exit checkpoint lines: 0 +-- ~/.local/share/baley-live/results/server-stderr/619197.log +start 2026-10-08T15:44:38Z pid 619197 +exit checkpoint lines: 0 + +== hook timing: calls per tool with the highest and median elapsed milliseconds == +Bash calls=41 highest_ms=48 median_ms=1 +Monitor calls=13 highest_ms=46 median_ms=1 +Read calls=4 highest_ms=1 median_ms=1 + +== hook timing: guard decisions by tool == +Bash ask 3 +Bash deny 17 +Bash none 21 +Monitor ask 2 +Monitor deny 1 +Monitor none 10 +Read none 4 + +== hook timing: calls at or above 10,000 ms == +(no lines above means none) + +== hook timing: tool_use_id seen more than once == +(no lines above means none) + +== real-home against pins.txt == +~/.local/share/crenshawdev/baley: no difference + +== real-config against pins.txt == +~/.config/crenshawdev/baley: no difference +``` diff --git a/spikes/host-matrix/claude-live-2026-10-08-run2.md b/spikes/host-matrix/claude-live-2026-10-08-run2.md new file mode 100644 index 00000000..79001b11 --- /dev/null +++ b/spikes/host-matrix/claude-live-2026-10-08-run2.md @@ -0,0 +1,696 @@ +# Live Claude Code qualification observations + +Claude Code version (claude --version): 2.1.294 (Claude Code) +Platform: Linux 7.2.9-1-cachyos +Date of the run: 2026-10-08, rerun of the affected parts after #231 and #232 (setup 16:47:02Z, sessions 16:47:23Z to 17:04:51Z, reads 17:05:24Z) +Baley commit (pins.txt): 3675864c98ae62077236dfce11daeec680375ed8 +Baley binary SHA-256 (pins.txt): 856120f0470c1d05d0f6d76a8e7a6fef4c1aae05fd22eb572f66607bcb519165 +MCP revision, and where it was read (debug log of which session): 2026-07-28, debug-session-a.log line 227 (negotiatedProtocolVersion for baley) + +The procedure is /code/baley/spikes/host-matrix/live-claude.md. Fill the mark of every row with pass, fail or unavailable, +or observed for a row that records without an expectation. Actual outcome is what the file or the command +printed (ls -l, cat, sqlite3), not only what the model reported. Evidence is a results file and line, or +the pasted output. Class stays blank for the record. Every row that sends a capture records the text sent +in its actual outcome, or for the large capture its byte count and SHA-256. Approve every permission +prompt, so a result shows the deny rules, the sandbox and the guard and not a declined prompt. + +## Sessions + +Startup checks of session A, with the smoke step that has to pass before anything else runs. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| ses.a.version | claude --version, before any session | Prints a version; recorded in the header | observed | 2.1.294 (Claude Code) | claude --version run by the coordinator, 2026-10-08T16:47Z | | +| ses.a.panels | In session A: /hooks, /sandbox and /permissions | The hook is the timed wrapper, the sandbox is on and required, the deny rules name both folders | observed | /hooks: "7 hooks on 7 events"; PreToolUse lists only the cadence plugin Bash hook; the --settings PreToolUse hook (guard-timed.sh, timeout 10, nine-tool matcher) is not listed, yet it fires (hook-timing.jsonl line 1 Read, line 2 Bash deny). /sandbox: "Error: Sandbox settings are overridden by a higher-priority configuration and cannot be changed locally." (no on/required panel). /permissions Deny tab lists the rendered rules, among them Edit(/~/.local/share/baley-live/config/crenshawdev/baley/**) and Edit(/~/.local/share/baley-live/data/crenshawdev/baley/**). results/settings.json: 17 deny rules (4 name the two folders), sandbox enabled true, failIfUnavailable true, allowUnsandboxedCommands false, PreToolUse hook guard-timed.sh timeout 10 matcher Bash, Monitor, PowerShell, Read, Grep, Glob, Write, Edit, NotebookEdit. No folder-trust dialog (path already trusted). Same panels as run 1 | tmux capture of session A after /hooks, /sandbox, /permissions; jq on results/settings.json | note: the hook set through --settings is missing from the /hooks panel yet fired, and the /sandbox panel could not be changed because a higher-priority configuration overrides it. | +| ses.a.smoke-capture | Session A smoke: capture one note through baley_apply (the third call of the tools check) | A receipt with a capture id | pass | Receipt {"status":"ok","id":"9963b8ec66bb1cc2145a71d01bf81a5c01d40bc8e720f660e45a81b8dca0e26f","kind":"note","phase":null,"bytes":25,"form":"inline","recorded_at":"2026-10-08T16:49:16.857304373Z"}; text sent "smoke note from session A". A permission prompt appeared and was approved for each of the three Baley calls (manual mode) | transcript ~/.claude/projects/-home-john--local-share-baley-live-projects-one/64cbe32a-6303-404f-9df3-0ac87335f14b.jsonl toolu_01LaeEDntWSSRmK56L9JA7AZ; ledger project 53370823 seq 9 capture.recorded | | +| ses.a.smoke-commit | Session A smoke: git commit --allow-empty -m smoke on main in project one | Denied by the guard (design 0010 GRD-R5), git log unchanged | pass | Guard denied: "Baley guard: git.on_protected is refuse, so a commit on the protected branch main is denied. Create a task branch first." No permission prompt (the hook's deny came first). git log --oneline main still 2 commits (fc34286, 615e062) | hook-timing.jsonl line 2 (Bash, toolu_01MqyNMRgcLdKpcVzLmaSfXd, permissionDecision deny, elapsed 49 ms); git -C projects/one log --oneline main | | +| ses.a.smoke-ledger | Session A smoke: sqlite3 -readonly on the disposable baley.db | The capture and the guard answer are both in the disposable ledger and nowhere else | pass | One capture.recorded in project 53370823-386a-444f-a244-9cd60bb401d2 (project one) seq 9, one guard.answered in project user seq 2 | q "select project_id, seq, type from event where type in ('capture.recorded', 'guard.answered')" on the disposable baley.db | | +| ses.b.login | Session B: log in once inside the isolated configuration | Claude Code starts with CLAUDE_CONFIG_DIR set | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ses.b.registered | Session B: claude mcp add-json --scope user baley, then /mcp | The baley server is listed from the user scope | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | + +## Barriers: home + +One row per tool, folder and access for Baley's home, ~/.local/share/baley-live/data/crenshawdev/baley. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| bar.home.bash-read | Bash read: cat ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.bash-write | Bash write: echo bash > ~/.local/share/baley-live/data/crenshawdev/baley/agent-bash.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.bash-child-read | Bash child, read: sh -c 'cat ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt' | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.bash-child-write | Bash child, write: sh -c 'echo child > ~/.local/share/baley-live/data/crenshawdev/baley/agent-sh.txt' | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.bash-script-read | Bash script, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.bash-script-write | Bash script, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/data/crenshawdev/baley/agent-script.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.monitor-read | Monitor command, read: cat ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.monitor-write | Monitor command, write: echo monitor > ~/.local/share/baley-live/data/crenshawdev/baley/agent-monitor.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.monitor-child-read | Monitor command child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.monitor-child-write | Monitor command child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/data/crenshawdev/baley/agent-monitor-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.ps-read | PowerShell read: Get-Content ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.ps-write | PowerShell write: Set-Content -Path ~/.local/share/baley-live/data/crenshawdev/baley/agent-ps.txt -Value ps | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.ps-child-read | PowerShell child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.ps-child-write | PowerShell child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/data/crenshawdev/baley/agent-ps-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.read-tool | Read tool: ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | The Read deny rule refuses the call | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.grep-folder | Grep over the folder itself: pattern FAKE_KEY, path ~/.local/share/baley-live/data/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Grep only on a best-effort basis (design 0010 GRD-R13) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.grep-parent | Grep over a folder that contains it: pattern FAKE_KEY, path ~/.local/share/baley-live/data | The guard refuses the call (design 0010 GRD-R13) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.glob-folder | Glob over the folder itself: pattern *, path ~/.local/share/baley-live/data/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Glob only on a best-effort basis (design 0010 GRD-R13) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.glob-parent | Glob over a folder that contains it: pattern **/seed.txt, path ~/.local/share/baley-live/data | The guard refuses the call (design 0010 GRD-R13) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.write-tool | Write tool: create ~/.local/share/baley-live/data/crenshawdev/baley/agent-write.txt | The Edit deny rule refuses the call | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.edit-tool | Edit tool: change 'seed' to 'edited' in ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | The Edit deny rule refuses the call | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.home.notebook-edit | NotebookEdit tool: change cell c1 of ~/.local/share/baley-live/data/crenshawdev/baley/notebook.ipynb to 'x = 2' | The Edit deny rule refuses the call | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | + +## Barriers: config folder + +One row per tool, folder and access for Baley's config folder, ~/.local/share/baley-live/config/crenshawdev/baley. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| bar.config.bash-read | Bash read: cat ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.bash-write | Bash write: echo bash > ~/.local/share/baley-live/config/crenshawdev/baley/agent-bash.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.bash-child-read | Bash child, read: sh -c 'cat ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt' | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.bash-child-write | Bash child, write: sh -c 'echo child > ~/.local/share/baley-live/config/crenshawdev/baley/agent-sh.txt' | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.bash-script-read | Bash script, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.bash-script-write | Bash script, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/config/crenshawdev/baley/agent-script.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.monitor-read | Monitor command, read: cat ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.monitor-write | Monitor command, write: echo monitor > ~/.local/share/baley-live/config/crenshawdev/baley/agent-monitor.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.monitor-child-read | Monitor command child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.monitor-child-write | Monitor command child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/config/crenshawdev/baley/agent-monitor-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.ps-read | PowerShell read: Get-Content ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.ps-write | PowerShell write: Set-Content -Path ~/.local/share/baley-live/config/crenshawdev/baley/agent-ps.txt -Value ps | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.ps-child-read | PowerShell child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.ps-child-write | PowerShell child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/config/crenshawdev/baley/agent-ps-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.read-tool | Read tool: ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | The Read deny rule refuses the call | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.grep-folder | Grep over the folder itself: pattern FAKE_KEY, path ~/.local/share/baley-live/config/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Grep only on a best-effort basis (design 0010 GRD-R13) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.grep-parent | Grep over a folder that contains it: pattern FAKE_KEY, path ~/.local/share/baley-live/config | The guard refuses the call (design 0010 GRD-R13) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.glob-folder | Glob over the folder itself: pattern *, path ~/.local/share/baley-live/config/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Glob only on a best-effort basis (design 0010 GRD-R13) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.glob-parent | Glob over a folder that contains it: pattern **/seed.txt, path ~/.local/share/baley-live/config | The guard refuses the call (design 0010 GRD-R13) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.write-tool | Write tool: create ~/.local/share/baley-live/config/crenshawdev/baley/agent-write.txt | The Edit deny rule refuses the call | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.edit-tool | Edit tool: change 'seed' to 'edited' in ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | The Edit deny rule refuses the call | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| bar.config.notebook-edit | NotebookEdit tool: change cell c1 of ~/.local/share/baley-live/config/crenshawdev/baley/notebook.ipynb to 'x = 2' | The Edit deny rule refuses the call | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | + +## Protected files + +Writes to files the settings protect. The guard's own list holds only the two baley.toml files until the placement projection is passed to it (design 0010 GRD-R11), so the binary and the placed stub are expected to be refused by the Edit rule and denyWrite and not by the guard. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| prot.baleytoml.write-tool | Write tool: replace ~/.local/share/baley-live/projects/one/baley.toml with one comment line | Refused. The Edit rule covers the file and runs before the hook, so the guard (design 0010 GRD-R11) is not reached on Claude Code 2.1.294. Write which refused it | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| prot.baleytoml.bash-write | Bash write: echo '# probe' >> ~/.local/share/baley-live/projects/one/baley.toml | Sandbox denyWrite refuses it; the guard does not judge Bash writes | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| prot.binary.write-tool | Write tool: replace ~/.local/share/baley-live/bin/baley with one line | The Edit rule refuses it; no guard protection yet, recorded as such | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| prot.binary.bash-write | Bash write: echo x >> ~/.local/share/baley-live/bin/baley | Sandbox denyWrite refuses it | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| prot.binary.intact | sha256sum ~/.local/share/baley-live/bin/baley after the rows above | Equals the binary-sha256 line of pins.txt | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| prot.stub.write-tool | Write tool: replace ~/.local/share/baley-live/claude-config/skills/bal-help/SKILL.md with one line | The Edit rule refuses it; no guard protection yet, recorded as such | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| prot.stub.bash-write | Bash write: echo x >> ~/.local/share/baley-live/claude-config/skills/bal-help/SKILL.md | Sandbox denyWrite refuses it | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| prot.no-unsandboxed-fallback | Ask the agent to repeat a refused Bash write into the home with the sandbox disabled | No unsandboxed run: allowUnsandboxedCommands is false (ADR 0033) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| prot.absent-sandbox | Session absent-sandbox: ask for Bash cat ~/.local/share/baley-live/projects/one/README.md | Claude Code refuses to run the command rather than run it unsandboxed (failIfUnavailable) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| prot.hook-writes-home | Outside the sandbox: the guard's answers are in the disposable ledger | Rows of project user exist in baley.db, so the hook wrote the home | pass | guard.answered project user seq 2 in the disposable baley.db, written by the hook into a folder the sandbox denies the agent | same query as ses.a.smoke-ledger | | +| prot.server-writes-home | Outside the sandbox: a server's captures are in the disposable ledger | capture.recorded events exist in baley.db, so the server wrote the home | pass | capture.recorded project 53370823 seq 9 in the disposable baley.db, written by the server into a folder the sandbox denies the agent | same query as ses.a.smoke-ledger | | + +## Controls + +The hook and the execution controls, read from hook-timing.jsonl, the ledger and the files each command touched. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| ctl.hook.bash | Hook fired for Bash: grep -c '"tool_name":"Bash"' ~/.local/share/baley-live/results/hook-timing.jsonl | At least one line | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.hook.monitor | Hook fired for Monitor | At least one line | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.hook.powershell | Hook fired for PowerShell | At least one line | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.hook.read | Hook fired for Read | At least one line | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.hook.grep | Hook fired for Grep | At least one line | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.hook.glob | Hook fired for Glob | At least one line | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.hook.write | Hook fired for Write (the Part 5 request on a path no deny rule covers) | At least one line | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.hook.edit | Hook fired for Edit (the Part 5 request on a path no deny rule covers) | At least one line | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.hook.notebookedit | Hook fired for NotebookEdit (the Part 5 request on a path no deny rule covers) | At least one line | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.grep-parent-guard | The Grep over a parent of the home: the ledger holds a deny for tool Grep | The guard denied it (design 0010 GRD-R13) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.glob-parent-guard | The Glob over a parent of the home: the ledger holds a deny for tool Glob | The guard denied it (design 0010 GRD-R13) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.commit-main-bash | Bash in project one on main: git commit --allow-empty -m probe-bash | Denied by the guard (GRD-R5, on_protected refuse), git log --oneline unchanged | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.commit-main-monitor | Monitor in project one on main: git commit --allow-empty -m probe-monitor | Denied by the guard (GRD-R3, GRD-R5), git log --oneline unchanged | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.push-bash-yes | Bash: git push origin main, answer yes | The guard asks (GRD-R4). After yes, git --git-dir=~/.local/share/baley-live/remotes/one.git branch --list shows main | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.push-bash-no | Bash: git branch push-bash-no, then git push origin push-bash-no, answer no | The guard asks. After no, the remote has no push-bash-no | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.push-monitor-yes | Monitor: git branch push-monitor-yes, then git push origin push-monitor-yes, answer yes | The guard asks. After yes, git --git-dir=~/.local/share/baley-live/remotes/one.git branch --list shows push-monitor-yes | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.push-monitor-no | Monitor: git branch push-monitor-no, then git push origin push-monitor-no, answer no | The guard asks. After no, the remote has no push-monitor-no | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.powershell-ask | PowerShell: Get-Date | The guard asks on every PowerShell call (design 0010 GRD-R3), recorded in the ledger | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.write-baleytoml-denied | The Write to ~/.local/share/baley-live/projects/one/baley.toml: the file and the refusal message | Refused with the file unchanged. A guard deny (GRD-R11) shows only if the hook ran, which a covering Edit rule prevents | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.declined-syntax | Bash on main: git commit --allow-empty -m "$(date)" (the scanner declines a substitution) | Record what happened and the commit the binary was built from, with no claim about what the shell did (design 0010 GRD-R3) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.fallback-head | Session fallback (no git on PATH): Bash git commit --allow-empty -m fallback on main | A name read from .git/HEAD never decides refuse or ask (GRD-R6, GRD-R14). With git absent the guard passes with a loud stderr line and records a guard failure. Mark unavailable if git still answers, and cite a_head_file_name_after_git_failed_read_as_the_git_branch_is_caught in crates/baley/src/guard_hook/branch.rs | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.latency | Every guard call: the highest elapsed_ms and the highest wrapper_ms in the timing summary of live-claude-reads.sh | Both below 10,000 ms. elapsed_ms times the guard alone and wrapper_ms adds the wrapper around it, which the host's timer also counts (design 0010 GRD-R14) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.timeout-not-denial | A hook that timed out, if one did: a call at or above 10,000 ms, or a start in hook-starts.jsonl with no timing line (the unfinished calls section of live-claude-reads.sh) | Recorded as a timeout and not as a denial. Mark unavailable if none timed out | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.contention-exit | Guard calls while another session exits (rows exit.overlap-1, exit.overlap-2 and exit.overlap-3) | Every call answers inside its time | pass | Every guard call during the three exit overlaps answered in 45 to 47 ms (wrapper 54 to 58 ms), far below 10,000 ms; every capture recorded once | hook-timing.jsonl, 9 Bash deny lines 16:59:13Z to 17:01:22Z | | +| ctl.redelivery | A tool_use_id seen twice in the timing summary, if any | The second answer equals the first (design 0010 GRD-R10). Mark unavailable if none repeated | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| ctl.server-stderr-visible | stderr-control session: where Claude Code shows the marker line its server wrote to standard error, with no redirect (the terminal, /mcp, results/debug-stderr-control.log) | Recorded as observed: each of the three places is named as held or not held, and the same for any exit checkpoint line the server wrote | unavailable | new in the corrected procedure, not run | live-claude.md Part 13 step 5 | unverified | +| ctl.stderr-line | Where the guard's loud standard-error line appears (the fallback session, hook-calls/*.err, the debug log) | Recorded as observed. Claude Code sends a hook's stderr on exit 0 to its debug log only (design 0010 GRD-R6 and GRD-R9) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | + +## Fields + +What tool_input carried for each tool, transcribed from the stand-in probe's hook-stdin.jsonl (probe-claude.sh), never from this run's wrapper. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| fld.bash | Bash tool_input field names | command (design 0010 section 12) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| fld.monitor | Monitor command form: tool_input field names | command (design 0010 section 12) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| fld.monitor-watch | Monitor WebSocket form: tool_input field names | ws, and no command (design 0010 section 12) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| fld.powershell | PowerShell tool_input field names | command (design 0010 section 12) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| fld.read | Read tool_input field names | file_path | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| fld.grep | Grep tool_input field names | pattern, path and glob when given | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| fld.glob | Glob tool_input field names | pattern, and path when given | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| fld.write | Write tool_input field names | file_path | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| fld.edit | Edit tool_input field names | file_path | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| fld.notebookedit | NotebookEdit tool_input field names | notebook_path | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | + +## Identities + +Read from the event.caller column of the disposable ledger. The host session id is recorded and never compared. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| id.explicit.startup | Session A: CLAUDE_PROJECT_DIR at startup and the server's working_directory from the ledger | Both recorded and not judged: project_directory is the folder Claude Code exported, working_directory is what the server ran in | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.user-scope.startup | Session B (started in sub): CLAUDE_PROJECT_DIR at startup and the server's working_directory from the ledger | Both recorded and not judged: project_directory is the folder Claude Code exported, working_directory is what the server ran in | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.two-sessions | Two distinct baley_session values bound to project one (sessions A and B) | Two different UUIDs on events of project one (ADR 0034) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.subagent-session | A subagent of session A captures a note | Its caller carries the baley_session of session A | pass | Request ids 11 to 16 each appear once, all with baley_session 4e90e4a7-547e-44b0-a175-798ce81b07de (session A's server, pid 946447) | q "select request_id, count(*) n, json_extract(caller, '$.baley_session') s from event where type = 'capture.recorded' group by request_id, s" | | +| id.cd | /cd to project two, then one capture and one denied commit | Recorded as observed: the native ids in the server's and the hook's callers | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.cd-project | After /cd: which project the server writes to, and which target the hook judges | The server stays on project one while the hook's working directory and target change, and the guard judges the actual target (design 0010 GRD-R2) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.clear | /clear, then one capture and one denied commit | Recorded as observed: the native ids in the server's and the hook's callers | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.branch | /branch, then one capture and one denied commit | Recorded as observed: whether the server survived and the native ids | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.resume-id | Exit, then the resume-id launch, then one capture and one denied commit | Recorded as observed: the native ids | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.resume | Exit, then the resume launch (picker), then one capture and one denied commit | Recorded as observed: the native ids | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.continue | Exit, then the continue launch, then one capture and one denied commit | Recorded as observed: the native ids | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.absent-native | no-session-id launch: one capture | Accepted with no host_session in the caller | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| id.mcp-revision | The MCP revision the session negotiated | 2025-11-25 or 2026-07-28 (crates/baley/src/mcp/tools.rs). Not observed if the debug log does not show it, naming where it was looked for | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | + +## Concurrency + +Overlapping calls from two sessions, and from a parent with five subagents. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| conc.two-sessions | Sessions A and B at the same time: overlapping baley_version and help calls and distinct captures | Every call answers and every capture is recorded once, with no loss or silent merge | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| conc.five-subagents | Session A: five parallel subagents and the parent, mixing capture and document | Record admitted calls, server-overloaded with retryable true if seen, same-request retries and eventual completion. Otherwise write: saturation not observed (ADR 0034) | observed | saturation not observed. The main agent launched five general-purpose subagents in one message and called capture 16 itself. Every Baley call waited on a permission prompt (manual mode); 12 prompts were approved 16:50:26Z to 16:50:43Z, so the calls reached the server one at a time about 1.5 s apart: recorded 11 at 16:50:28.05, 12 at 29.56, 13 at 32.57, 14 at 35.58, parent 16 at 37.09, 15 at 40.10. Each subagent then called baley_query document with identity sent as an object ({"kind":"capture","id":...}) and got status ok with its own text. No server-overloaded answer, no same-request retry; all six captures and five documents completed | q "select request_id, count(*) n, json_extract(caller, '$.baley_session') s from event where type = 'capture.recorded' group by request_id, s"; subagents/agent-a7e18e2d20f6821b7, agent-af4412ab58df67756, agent-ac7c02046c38ab776, agent-aec17162fe32bf5ba, agent-a89b81f86e17274b4 .jsonl; debug-session-a.log baley Calling/completed lines 16:50:28 to 16:50:43 | note: saturation not observed; every Baley call waited on a permission prompt, so the calls reached the server one at a time, and all six captures and five document reads completed. | + +## Replay + +A capture sent again with its original request id after the server restarted. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| rep.same-id | Same request_id and input after a restart | The original receipt, and the capture count for that request_id stays one | pass | Workaround: request 02 (Part 6's overlap capture) does not exist in this rerun because Part 6 needs session B, so the replay used request 01, the smoke capture recorded by session A's server (pid 946447) before it exited. Restarted server: the continue launch, pid 963572, baley_session 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941. Same request id 01 and input (kind note, text "smoke note from session A") returned the original receipt: id 9963b8ec66bb1cc2145a71d01bf81a5c01d40bc8e720f660e45a81b8dca0e26f, recorded_at 2026-10-08T16:49:16.857304373Z. Count of capture.recorded for 01 = 1 | transcript toolu_013qhNG6WEtfhkRrbZ9K2Wpo; q "select count(*) from event where type = 'capture.recorded' and request_id = '12120000-0000-4000-8000-000000000001'" -> 1 | | +| rep.changed-input | Same request_id with changed text | Refused as request-id-reuse, with nothing recorded | pass | Same request id 01 with text "smoke note from session A changed": {"status":"refused","code":"request-id-reuse","reason":"this request_id was already used for a different capture, so nothing was recorded. A new capture needs a new request_id","slot":"request_id"}; count for 01 still 1 | transcript toolu_01UppSwmGyv9BeEBx5r9qUjC; count query -> 1 | | +| rep.stale-expected | Stale expected observations | Not applicable: no served operation carries one | observed | Not applicable: no served operation carries an expected observation (design 0012); prefilled, nothing run | live-claude.md Part 10 step 3 | | + +## Exits + +One row per exiting server. Each server's standard error is in results/server-stderr/.log, except the stderr-control server's, which is not redirected. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| exit.session-a | Session A exits | Exactly one 'baley: exit checkpoint' line in its stderr file | pass | server-stderr/946447.log: "start 2026-10-08T16:47:24Z pid 946447" then exactly one line "baley: exit checkpoint complete, every logged change is in the database file", after /exit at 16:56:50Z. Claude Code sent SIGINT (16:56:51.358Z) and logged "MCP server process exited cleanly" (16:56:51.409Z). Before the exit the file held only its start line (ls and cat at 16:56Z) | results/server-stderr/946447.log; debug-session-a.log lines 2548, 2561; reads.txt server stderr section | | +| exit.session-b | Session B exits | Exactly one exit checkpoint line | unavailable | not rerun: session B needs the owner's login to its isolated configuration; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| exit.resume-id | The resume-id session exits | Exactly one exit checkpoint line | unavailable | affected by #231, not rerun; run 2's exits show the fix | claude-live-2026-10-08-run1.md | unverified | +| exit.resume | The resume session exits | Exactly one exit checkpoint line | unavailable | affected by #231, not rerun; run 2's exits show the fix | claude-live-2026-10-08-run1.md | unverified | +| exit.continue | The continue session exits | Exactly one exit checkpoint line | pass | server-stderr/963572.log: "start 2026-10-08T16:57:03Z pid 963572" then exactly one "baley: exit checkpoint complete, every logged change is in the database file", after the burst-at-exit /exit (dialog choice "Exit and stop tasks" at 17:04:51Z); SIGINT 17:04:50.996Z, exited cleanly 17:04:51.046Z; no drain line | results/server-stderr/963572.log; debug-continue.log 17:04:50.996Z and 17:04:51.046Z; reads.txt server stderr section | | +| exit.invalid-project | The invalid-project session exits | Exactly one exit checkpoint line | unavailable | affected by #231, not rerun; run 2's exits show the fix | claude-live-2026-10-08-run1.md | unverified | +| exit.missing-project | The missing-project session exits | Exactly one exit checkpoint line | unavailable | affected by #231, not rerun; run 2's exits show the fix | claude-live-2026-10-08-run1.md | unverified | +| exit.no-session-id | The no-session-id session exits | Exactly one exit checkpoint line | unavailable | affected by #231, not rerun; run 2's exits show the fix | claude-live-2026-10-08-run1.md | unverified | +| exit.fork | The fork session exits | Exactly one exit checkpoint line | unavailable | affected by #231, not rerun; run 2's exits show the fix | claude-live-2026-10-08-run1.md | unverified | +| exit.nearer-file | The nearer-file session exits | Exactly one exit checkpoint line | unavailable | new in the corrected procedure, not run | live-claude.md Part 11 | unverified | +| exit.absent-sandbox | The absent-sandbox session exits | Exactly one exit checkpoint line, if a server started | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| exit.fallback | The fallback session exits | Exactly one exit checkpoint line | unavailable | affected by #231, not rerun; run 2's exits show the fix | claude-live-2026-10-08-run1.md | unverified | +| exit.other-servers | Any other server file in results/server-stderr (a restart after /cd, /clear or /branch) | Exactly one exit checkpoint line each, recorded with the command that ended it | observed | Three extra session-a launches started for the exit overlaps: 966315.log, 968318.log, 970353.log, each "start ..." then exactly one "baley: exit checkpoint complete, every logged change is in the database file", each ended by /exit (SIGINT 16:59:14.660Z, 17:00:16.234Z, 17:01:11.849Z, each "exited cleanly" about 50 ms later; file mtimes 16:59:14, 17:00:16, 17:01:11). No restart after /cd, /clear or /branch was looked for (Part 9 not rerun) | results/server-stderr/966315.log, 968318.log, 970353.log; debug-session-a-overlap-1.log lines 331, 351; debug-session-a-overlap-2.log lines 337, 357; debug-session-a-overlap-3.log lines 337, 357 | note: three extra launches each held exactly one exit checkpoint line after /exit; no restart after /cd, /clear or /branch was looked for. | +| exit.overlap-1 | Overlap 1: close one session while the other writes and invokes the guard | The remaining session keeps making progress, and the guard answers inside 10,000 ms | pass | Workaround: session B not run, so the staying session was the continue session (server 963572). Prompt adds kind note and writes the four ids in full. New session-a launch (server 966315) got /exit at 16:59:14.64Z (SIGINT 16:59:14.660Z) while the continue session captured 51-54 at 16:59:11.84, 17.36, 22.87, 28.38, each once, texts "overlap round 1 item 1" to "item 4", with three denied commits between, guard elapsed 47, 47, 45 ms | ledger capture.recorded 51-54; hook-timing.jsonl 16:59:13Z to 16:59:25Z; debug-session-a-overlap-1.log line 331 | | +| exit.overlap-2 | Overlap 2, as above | As above | pass | Workaround as overlap-1 (staying session: the continue session). New session-a launch (server 968318) got /exit at 17:00:16.22Z (SIGINT 17:00:16.234Z) while the continue session captured 61-64 at 17:00:13.42, 18.94, 24.45, 29.97, each once, texts "overlap round 2 item N", guard elapsed 46, 47, 47 ms | ledger capture.recorded 61-64; hook-timing.jsonl 17:00:15Z to 17:00:26Z; debug-session-a-overlap-2.log line 337 | | +| exit.overlap-3 | Overlap 3, as above | As above | pass | Workaround as overlap-1 (staying session: the continue session). New session-a launch (server 970353) got /exit at 17:01:11.84Z (SIGINT 17:01:11.849Z) while the continue session captured 71-74 at 17:01:09.04, 14.55, 20.06, 25.58, each once, texts "overlap round 3 item N", guard elapsed 46, 47, 47 ms | ledger capture.recorded 71-74; hook-timing.jsonl 17:01:10Z to 17:01:22Z; debug-session-a-overlap-3.log line 337 | | +| exit.burst | Burst at exit: a burst of calls in flight when a session exits (#190) | Every call that was read is answered (server-overloaded at worst), and the drain line appears if the 10-second bound passed (ADR 0034) | unavailable | The burst-at-exit condition was not created. Burst prompt sent 17:02:04.66Z in the continue session (manual mode). The first two capture prompts were approved (17:02:11.92Z, 17:02:12.32Z) and the server answered both ok in about 65 ms (81 recorded 17:02:11.92, 82 at 17:02:12.33). /exit typed at 17:02:12.93Z opened Claude Code's dialog "Background work is running ... 1. Exit and stop tasks 2. Move to background and exit 3. Stay", which stayed open until the coordinator chose 1 at 17:04:51Z. Calls 83, 84, 85 (subagent tool_use with no result) and 86 (parent) were waiting at Claude Code's permission prompts and never reached the server: the debug log has only two Calling/completed pairs after 17:02 and "Aborting: tool=mcp__baley__baley_apply isAbort=true" at 17:04:50.999Z. So no call was inside the server at exit. Every call the server read was answered; no drain line (bound not reached); one exit checkpoint line. Ledger: 81 and 82 once each, 83 to 86 none (allowed) | ledger capture.recorded 81, 82; debug-continue.log 17:02:11.919Z to 17:04:51.046Z; subagents/agent-a753a2c6aa42671ff, agent-aefd1700a4833b4c5, agent-a33b5ec217fb00cea, agent-ae1379a823c1ca7b0, agent-a706020965a45530f .jsonl; server-stderr/963572.log | unverified | +| exit.no-idle-checkpoint | Every stderr file, outside the exit | No checkpoint line other than at exit: none exists in code | pass | Each of the five files holds its one checkpoint line as line 2, after its start line, and each file was last written at its server's SIGINT time; 946447.log held only its start line at 16:56Z while its server was idle between calls. No file holds a checkpoint line before its exit | cat results/server-stderr/*.log (5 files); date -u -r on each file; reads.txt server stderr section | | + +## Variants + +Sessions whose project or session id is missing, invalid or shared. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| var.invalid.project-calls | invalid-project session: capture and document | failed, with a code naming CLAUDE_PROJECT_DIR as the place | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| var.invalid.free-calls | invalid-project session: baley_version, help, schema and instruction | All four answer | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| var.missing.project-calls | missing-project session: capture and document | failed, with a code naming CLAUDE_PROJECT_DIR as the place | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| var.missing.free-calls | missing-project session: baley_version, help, schema and instruction | All four answer | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| var.fork | fork session: capture | Refused as project-id-conflict: the fork shares project one's id under another remote | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | + +## Hand-offs + +What the earlier builds hand to this run. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| hand.init | Owner init: results/init-one.txt and init-one.status | Exit status 0, baley.toml written, project recorded | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| hand.config-show | In project one: baley config show | Host-specific settings listed with their layers | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| hand.nearer-file | nearer-file session (its server's CLAUDE_PROJECT_DIR is ~/.local/share/baley-live/projects/one/sub): the project the capture lands in | Project one, found by walking up from sub to the nearer baley.toml, with sub as the caller's project_directory | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| hand.checkout-admission | The ledger's checkout rows for project one | Project one's checkout admitted | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| hand.keys-detection | baley models update in the owner's real environment, after the post-run rows | Reports detection per provider with a key, a failed detection exits 0 | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| hand.restore-doctor | baley doctor and the restore report on the disposable ledger | No finding on a ledger no restore touched | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| hand.parts.help | help read whole | One part, whole | pass | One answer, status ok, 4,819 bytes, no part field (keys about, clusters, instruction, request, status). Prompt shown and approved | transcript 64cbe32a...jsonl tool_result toolu_01Tdzespr9fDG3UmTaQAiad3 (jq has("part") false, wc -c) | | +| hand.parts.instruction | instruction for bal-help read whole | One part, whole | pass | One whole answer, status ok, identity bal-help, version 1, hash 6a546a62...; 1,366 bytes, no part field (keys hash, identity, status, text, version) | transcript 64cbe32a...jsonl tool_result toolu_01PBizioy1zXZzX2XSp1d6h9 | | +| hand.parts.document-main | document of the large capture, part 1, in the main session | A part of exactly 24,576 bytes arrives whole, naming the next part | pass | fixtures/large.txt: 33000 bytes, sha256 6754341a445caf2f681748567772759cdf41bf0865425e360da5fcfd8dc1c0d4 (wc -c, sha256sum). Capture 21 kind story: receipt id fc6f500044cc33080bde4113fe1be73e67c1eb0a734772d136cbba9c4ca266cf, bytes 33000, form payload. Document call input {"operation":"document","identity":{"kind":"capture","id":"fc6f5000..."},"part":1}: identity an object and part an integer. Answer status ok, bound 24576, part 1, next 2, kind story, bytes 33000. Body measured from the transcript: 24,576 bytes, byte-identical to the first 24,576 bytes of large.txt (cmp), sha256 dae31a9cc50d088fe1251520e64ced006e37a05e0f9169ec60974d1c6493af2f | transcript 64cbe32a...jsonl toolu_019d68r5qJxN318ikQRcf6gS (capture) and toolu_013wAy51jhkDkgutNEkr2ftm (document): jq -j .body, wc -c, cmp against head -c 24576 large.txt | | +| hand.parts.document-subagent | document of the large capture, part 1, in a subagent | A part of exactly 24,576 bytes arrives whole, naming the next part | pass | One subagent called document with input {"operation":"document","identity":{"kind":"capture","id":"fc6f5000..."},"part":1}. Answer status ok, bound 24576, part 1, next 2, kind story, bytes 33000; body measured from the subagent transcript: 24,576 bytes, byte-identical to the first 24,576 bytes of large.txt (cmp), sha256 dae31a9c...af2f | subagents/agent-a4744d95df868321a.jsonl toolu_01DDfoKuorXDtjT1Bn6DdQL5, measured the same way | | +| hand.instruction-evidence | instruction for bal-capture, then a capture naming it as instruction | The capture's caller carries the instruction evidence | pass | instruction bal-capture answered status ok, version 1, hash 52867052...; capture 23 kind note, text "instruction evidence note", instruction bal-capture recorded once; its caller.instructions = [{"hash":"52867052fb5ae23a0962120f07e6fbfa3ba0010bf2e52c109351ff8a1e8b7c5e","identity":"bal-capture","version":"1"}] (seq 25 capture.recorded; seq 26 command.completed carries the same) | q "select request_id, json_extract(caller, '$.instructions') from event where request_id = '12120000-0000-4000-8000-000000000023'"; transcript toolu_01YMw7NBr7bRMvfih8URt481, toolu_01JWe6sYAWGBosrqJLYcxncL | | +| hand.tools.explicit-main | Session A, a fresh session, before any other Baley call: baley_version, baley_query and baley_apply callable in the main session without a tool search | All three callable with no tool search in the transcript (HST-R20). Record whether tool search was active, since a session that never defers a tool proves nothing | pass | Asked before any Baley call (the question added one sentence asking whether the session defers tools): the model said all three are in its list and need no search. The session does defer tools: its transcript's deferred_tools_delta holds 44 names (Monitor, NotebookEdit, WebFetch, the claude-in-chrome set and others) and no baley tool, and the debug log says "Dynamic tool loading: 0/44 deferred tools included". The smoke step then called mcp__baley__baley_version, mcp__baley__baley_query and mcp__baley__baley_apply with no ToolSearch call (tool_use list: Read of john-voice.md, then the three) | transcript 64cbe32a-6303-404f-9df3-0ac87335f14b.jsonl line 15 (deferred_tools_delta) and tool_use toolu_012k48mmgQ1zTT8SoBPvx3sB, toolu_01RXkgcnSeKKuWvm9g6u9ucM, toolu_01LaeEDntWSSRmK56L9JA7AZ; debug-session-a.log line 450 | | +| hand.tools.explicit-subagent | Session A: the same three in a subagent | All three visible (HST-R20) | pass | The subagent called baley_version (ok, 0.1.0 linux x86_64), baley_query help (ok) and baley_apply capture 32 kind note text "tools A subagent" (ok, id dd5dc83f...), with no ToolSearch call in its transcript | subagents/agent-a19958dc465cce364.jsonl | | +| hand.tools.user-main | Session B (alwaysLoad registration), a fresh session, before any other Baley call: the same three in the main session | All three callable with no tool search in the transcript (HST-R20). Record whether tool search was active | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| hand.tools.user-subagent | Session B: the same three in a subagent | All three visible (HST-R20) | unavailable | not rerun: session B needs the owner's login to its isolated configuration; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| hand.skill-listed | Session B: the bal-help skill from the isolated configuration | Listed in the session | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| hand.skill-run | Session B: run the bal-help skill | It calls baley_query, each call asking for approval since a stub carries no allowed-tools line (ADR 0009) | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | + +## Post-run + +After every session has exited: sh live-claude-reads.sh > ~/.local/share/baley-live/results/reads.txt. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| post.verify-one | baley verify --local-only for project one | Exit status 0 | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| post.views-one | baley verify --views for project one | Exit status 0 | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| post.verify-user | baley verify --local-only user | Exit status 0 | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| post.views-user | baley verify --views user | Exit status 0 | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| post.doctor | baley doctor | Exit status 0 | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| post.stream-versions | Per project and stream: stream_version unique and increasing | No stream with a lowest version other than 1 or a count other than its span | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| post.captures-once | Each capture expected to be recorded, exactly once, with its caller (live-claude.md, Request ids) | One capture.recorded per such request_id, none for 91 to 93, and for the burst ids 81 to 86 one or none | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| post.text-equal | Stored text equal to the text each row sent (large capture: byte count and SHA-256) | Equal | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| post.no-loss | No capture lost, none silently merged | Every request_id expected to be recorded appears once. Ids 91, 92 and 93 are refused and appear never. Ids 81 to 86 and any request an exit cut off before the server read it may leave no event, and that is not a loss. No id appears twice and no event holds another request's text | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | +| post.real-folders | The owner's real Baley folders against pins.txt | No difference | unavailable | not rerun: unaffected by #231 and #232; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified | + +## Defects from the first run + +The first dated sheet, claude-live-2026-10-08-run1.md, marks the rows below fail and classes each as a Baley defect. Each has its own issue and a merged bug pull request, and this run used Baley commit 3675864c, which contains both merge commits (git merge-base --is-ancestor accepts both). The last column is this sheet's mark for the same row id. + +| Row id in run 1 | Issue | Bug pull request | Merge commit | Mark in this run | +|---|---|---|---|---| +| exit.session-a | #231 | #233 | a8f0d085 | pass | +| exit.resume-id | #231 | #233 | a8f0d085 | unavailable (unverified) | +| exit.resume | #231 | #233 | a8f0d085 | unavailable (unverified) | +| exit.continue | #231 | #233 | a8f0d085 | pass | +| exit.invalid-project | #231 | #233 | a8f0d085 | unavailable (unverified) | +| exit.missing-project | #231 | #233 | a8f0d085 | unavailable (unverified) | +| exit.no-session-id | #231 | #233 | a8f0d085 | unavailable (unverified) | +| exit.fork | #231 | #233 | a8f0d085 | unavailable (unverified) | +| exit.fallback | #231 | #233 | a8f0d085 | unavailable (unverified) | +| hand.parts.document-main | #232 | #234 | f68cfbed | pass | +| hand.parts.document-subagent | #232 | #234 | f68cfbed | pass | + +A row marked unavailable here was not rerun, so this sheet does not show its run 1 failure fixed. Its cause is the one its issue names, and the passing rows of the same issue exercise that code. + +## Cited excerpts + +The lines the evidence cells above cite, copied from the results folder so this sheet holds them once that folder is cleared. Each block is headed by its row id, the file and the line numbers, and the owner's home folder is written `~`. For ses.a.panels the lines are the ones its actual-outcome cell cites, and header.mcp-revision is the line the header cites for the MCP revision. +### ses.a.panels + +hook-timing.jsonl line 1 + +```text +{"tool_name":"Read","tool_use_id":"toolu_01SDHavQZEFChafiz2aPobEr","session_id":"64cbe32a-6303-404f-9df3-0ac87335f14b","cwd":"~/.local/share/baley-live/projects/one","call":"toolu_01SDHavQZEFChafiz2aPobEr-948975","start_ms":1791478123810,"end_ms":1791478123812,"elapsed_ms":1,"wrapper_ms":10,"overhead_ms":8,"exit":0} +``` + +### ses.a.smoke-commit + +hook-timing.jsonl line 2 + +```text +{"tool_name":"Bash","tool_use_id":"toolu_01MqyNMRgcLdKpcVzLmaSfXd","session_id":"64cbe32a-6303-404f-9df3-0ac87335f14b","cwd":"~/.local/share/baley-live/projects/one","call":"toolu_01MqyNMRgcLdKpcVzLmaSfXd-951458","start_ms":1791478199324,"end_ms":1791478199374,"elapsed_ms":49,"wrapper_ms":58,"overhead_ms":9,"exit":0,"permissionDecision":"deny"} +``` + +### exit.session-a + +debug-session-a.log line 2548 + +```text +2026-10-08T16:56:51.358Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +debug-session-a.log line 2561 + +```text +2026-10-08T16:56:51.409Z [DEBUG] MCP server "baley": MCP server process exited cleanly +``` + +### exit.other-servers + +debug-session-a-overlap-1.log line 331 + +```text +2026-10-08T16:59:14.660Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +debug-session-a-overlap-1.log line 351 + +```text +2026-10-08T16:59:14.710Z [DEBUG] MCP server "baley": MCP server process exited cleanly +``` + +debug-session-a-overlap-2.log line 337 + +```text +2026-10-08T17:00:16.234Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +debug-session-a-overlap-2.log line 357 + +```text +2026-10-08T17:00:16.285Z [DEBUG] MCP server "baley": MCP server process exited cleanly +``` + +debug-session-a-overlap-3.log line 337 + +```text +2026-10-08T17:01:11.849Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +debug-session-a-overlap-3.log line 357 + +```text +2026-10-08T17:01:11.899Z [DEBUG] MCP server "baley": MCP server process exited cleanly +``` + +### exit.overlap-1 + +debug-session-a-overlap-1.log line 331 + +```text +2026-10-08T16:59:14.660Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +### exit.overlap-2 + +debug-session-a-overlap-2.log line 337 + +```text +2026-10-08T17:00:16.234Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +### exit.overlap-3 + +debug-session-a-overlap-3.log line 337 + +```text +2026-10-08T17:01:11.849Z [DEBUG] MCP server "baley": Sending SIGINT to MCP server process +``` + +### hand.tools.explicit-main + +debug-session-a.log line 450 + +```text +2026-10-08T16:48:11.373Z [DEBUG] Dynamic tool loading: 0/44 deferred tools included +``` + +### header.mcp-revision + +debug-session-a.log line 227 + +```text +2026-10-08T16:47:24.588Z [DEBUG] MCP server "baley": Connection established with capabilities: {"hasTools":true,"hasPrompts":false,"hasResources":false,"hasResourceSubscribe":false,"serverVersion":{"name":"baley","version":"0.1.0"},"protocolEra":"modern","negotiatedProtocolVersion":"2026-07-28"} +``` + +## Post-run reads + +The output of `sh live-claude-reads.sh`, as the coding agent that drove the run saved it in results/reads.txt. The block below is that file unchanged except that the home folder is written `~`. + +```text + +== pins == +date: 2026-10-08T16:47:02Z +platform: Linux 7.2.9-1-cachyos +bwrap: /usr/bin/bwrap +socat: /usr/bin/socat +sqlite3: /usr/bin/sqlite3 +jq: /usr/bin/jq +pwsh: absent +git: /usr/bin/git +telemetry-switch: unset +checkout-commit: 3675864c98ae62077236dfce11daeec680375ed8 +checkout-status: clean +binary: baley 0.1.0 +binary-sha256: 856120f0470c1d05d0f6d76a8e7a6fef4c1aae05fd22eb572f66607bcb519165 +begin real-home ~/.local/share/crenshawdev/baley +~/.local/share/crenshawdev/baley/baley.db f 286720 2026-10-02 12:28:05.0100125140 +~/.local/share/crenshawdev/baley/baley.db.maintenance f 0 2026-10-02 12:25:03.5899264730 +~/.local/share/crenshawdev/baley/baley.db.writer f 0 2026-10-02 12:25:03.5899264730 +~/.local/share/crenshawdev/baley d 86 2026-10-05 09:42:21.6045176040 +end real-home +begin real-config ~/.config/crenshawdev/baley +~/.config/crenshawdev/baley d 16 2026-09-30 16:08:55.8750781170 +~/.config/crenshawdev/baley/keys.env f 183 2026-09-30 16:08:55.8767744790 +end real-config +project-one-id: 53370823-386a-444f-a244-9cd60bb401d2 +project-two-id: 1aedf71b-235e-4d19-a05d-f55c3f4afe46 +project-fork-id: 53370823-386a-444f-a244-9cd60bb401d2 + +== verify --local-only 53370823-386a-444f-a244-9cd60bb401d2 == +local only +checked at 2026-10-08T17:05:23.853490641Z +chain head 56 056eebafe09187f7391edcbe7c18aa33bd7f6125d9e63a9c6986d3db5c8574e4 +not compared with a remote anchor +unanchored sequences 1 to 56 +work unanchored since 2026-10-08T16:47:02.359228523Z +bodies checked 1, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 + +== verify --views 53370823-386a-444f-a244-9cd60bb401d2 == +views checked at sequence 56 +no differences +exit status: 0 + +== verify --local-only 1aedf71b-235e-4d19-a05d-f55c3f4afe46 == +local only +checked at 2026-10-08T17:05:23.881077363Z +chain head 6 2e811842fb70148bfb5032d4f6b47a91449bcba96bd8285199fa8081213af385 +not compared with a remote anchor +unanchored sequences 1 to 6 +work unanchored since 2026-10-08T16:47:02.480142838Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 + +== verify --views 1aedf71b-235e-4d19-a05d-f55c3f4afe46 == +views checked at sequence 6 +no differences +exit status: 0 + +== verify --local-only user == +local only +checked at 2026-10-08T17:05:23.902654433Z +chain head 21 8a1d5ad522e6391b352140556caa8102470ad5f12613f81c87461f17066a094c +not compared with a remote anchor +unanchored sequences 1 to 21 +work unanchored since 2026-10-08T16:49:59.367080906Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 + +== verify --views user == +views checked at sequence 21 +no differences +exit status: 0 + +== doctor == +epoch 1 +integrity: ok +database 417792 bytes, log 0 bytes +project 1aedf71b-235e-4d19-a05d-f55c3f4afe46 (two): not checked against a remote from this directory +chain head 6 2e811842fb70148bfb5032d4f6b47a91449bcba96bd8285199fa8081213af385 +not compared with a remote anchor +unanchored sequences 1 to 6 +work unanchored since 2026-10-08T16:47:02.480142838Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T16:47:02.480142838Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 6 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project 53370823-386a-444f-a244-9cd60bb401d2 (one): not checked against a remote from this directory +chain head 56 056eebafe09187f7391edcbe7c18aa33bd7f6125d9e63a9c6986d3db5c8574e4 +not compared with a remote anchor +unanchored sequences 1 to 56 +work unanchored since 2026-10-08T16:47:02.359228523Z +bodies checked 1, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T16:47:02.359228523Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 56 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project user (per-user records): not checked against a remote from this directory +chain head 21 8a1d5ad522e6391b352140556caa8102470ad5f12613f81c87461f17066a094c +not compared with a remote anchor +unanchored sequences 1 to 21 +work unanchored since 2026-10-08T16:49:59.367080906Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T16:49:59.367080906Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 21 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +exit status: 0 + +== streams: events per project and stream, lowest and highest stream_version == + project_id stream events lowest highest +------------------------------------ ---------------------- ------ ------ ------- +1aedf71b-235e-4d19-a05d-f55c3f4afe46 command/checkout.admit 1 1 1 +1aedf71b-235e-4d19-a05d-f55c3f4afe46 command/policy.record 1 1 1 +1aedf71b-235e-4d19-a05d-f55c3f4afe46 command/project.init 1 1 1 +1aedf71b-235e-4d19-a05d-f55c3f4afe46 project 3 1 3 +53370823-386a-444f-a244-9cd60bb401d2 capture 24 1 24 +53370823-386a-444f-a244-9cd60bb401d2 command/capture.record 24 1 24 +53370823-386a-444f-a244-9cd60bb401d2 command/checkout.admit 1 1 1 +53370823-386a-444f-a244-9cd60bb401d2 command/policy.record 2 1 2 +53370823-386a-444f-a244-9cd60bb401d2 command/project.init 1 1 1 +53370823-386a-444f-a244-9cd60bb401d2 project 4 1 4 +user command/guard.record 10 1 10 +user guard 11 1 11 + +== streams that do not start at 1 or whose count differs from the span == +(no rows above means every stream is complete) + +== server callers: each session with the project ids of the events it wrote == + baley_session project_directory working_directory host_session client_version project_id first_seq last_seq events +------------------------------------ ----------------------------------------------- ----------------------------------------------- ------------------------------------ -------------- ------------------------------------ --------- -------- ------ +4e90e4a7-547e-44b0-a175-798ce81b07de ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one 64cbe32a-6303-404f-9df3-0ac87335f14b 2.1.294 53370823-386a-444f-a244-9cd60bb401d2 7 28 22 +4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one 64cbe32a-6303-404f-9df3-0ac87335f14b 2.1.294 53370823-386a-444f-a244-9cd60bb401d2 29 56 28 +project ids in pins.txt: one=53370823-386a-444f-a244-9cd60bb401d2 two=1aedf71b-235e-4d19-a05d-f55c3f4afe46 fork=53370823-386a-444f-a244-9cd60bb401d2 + +== hook callers in seq order == +project_id seq host_session working_directory project_directory call_id +---------- --- ------------------------------------ ----------------------------------------------- ----------------------------------------------- ------------------------------ +user 1 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01MqyNMRgcLdKpcVzLmaSfXd +user 2 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01MqyNMRgcLdKpcVzLmaSfXd +user 3 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01MqyNMRgcLdKpcVzLmaSfXd +user 4 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01AqK1ZqMiHMVt61SkqH7aeU +user 5 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01AqK1ZqMiHMVt61SkqH7aeU +user 6 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_018gCwoh4Hxm6zkQSUhNtzDZ +user 7 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_018gCwoh4Hxm6zkQSUhNtzDZ +user 8 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01MS315n65f2Nc7VS1yQv54z +user 9 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01MS315n65f2Nc7VS1yQv54z +user 10 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01KvchUKuSAfwJFj1P1BwgDi +user 11 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01KvchUKuSAfwJFj1P1BwgDi +user 12 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Enmnwqqf1wYmLDMFaFwLeP +user 13 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Enmnwqqf1wYmLDMFaFwLeP +user 14 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_013bwuhK1DgEsq6vvxsZhRuH +user 15 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_013bwuhK1DgEsq6vvxsZhRuH +user 16 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_014wJbF1suTQ8K8VVQmoPMkJ +user 17 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_014wJbF1suTQ8K8VVQmoPMkJ +user 18 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Bmd2nKEgi97EPbJJ64wLYg +user 19 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Bmd2nKEgi97EPbJJ64wLYg +user 20 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01THeG1uqL7kE2zuuDmHwRd8 +user 21 64cbe32a-6303-404f-9df3-0ac87335f14b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01THeG1uqL7kE2zuuDmHwRd8 + +== captures: capture.recorded events per request_id with the caller's session and instruction evidence == + project_id seq request_id events_for_request baley_session instructions kind bytes text +------------------------------------ --- ------------------------------------ ------------------ ------------------------------------ -------------------------------------------------------------------------------------------------------------------- ----- ----- ------------------------- +53370823-386a-444f-a244-9cd60bb401d2 9 12120000-0000-4000-8000-000000000001 1 4e90e4a7-547e-44b0-a175-798ce81b07de note 25 smoke note from session A +53370823-386a-444f-a244-9cd60bb401d2 11 12120000-0000-4000-8000-000000000011 1 4e90e4a7-547e-44b0-a175-798ce81b07de note 10 subagent 1 +53370823-386a-444f-a244-9cd60bb401d2 13 12120000-0000-4000-8000-000000000012 1 4e90e4a7-547e-44b0-a175-798ce81b07de note 10 subagent 2 +53370823-386a-444f-a244-9cd60bb401d2 15 12120000-0000-4000-8000-000000000013 1 4e90e4a7-547e-44b0-a175-798ce81b07de note 10 subagent 3 +53370823-386a-444f-a244-9cd60bb401d2 17 12120000-0000-4000-8000-000000000014 1 4e90e4a7-547e-44b0-a175-798ce81b07de note 10 subagent 4 +53370823-386a-444f-a244-9cd60bb401d2 19 12120000-0000-4000-8000-000000000016 1 4e90e4a7-547e-44b0-a175-798ce81b07de note 6 parent +53370823-386a-444f-a244-9cd60bb401d2 21 12120000-0000-4000-8000-000000000015 1 4e90e4a7-547e-44b0-a175-798ce81b07de note 10 subagent 5 +53370823-386a-444f-a244-9cd60bb401d2 23 12120000-0000-4000-8000-000000000021 1 4e90e4a7-547e-44b0-a175-798ce81b07de story 33000 +53370823-386a-444f-a244-9cd60bb401d2 25 12120000-0000-4000-8000-000000000023 1 4e90e4a7-547e-44b0-a175-798ce81b07de [{"hash":"52867052fb5ae23a0962120f07e6fbfa3ba0010bf2e52c109351ff8a1e8b7c5e","identity":"bal-capture","version":"1"}] note 25 instruction evidence note +53370823-386a-444f-a244-9cd60bb401d2 27 12120000-0000-4000-8000-000000000032 1 4e90e4a7-547e-44b0-a175-798ce81b07de note 16 tools A subagent +53370823-386a-444f-a244-9cd60bb401d2 29 12120000-0000-4000-8000-000000000051 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 1 item 1 +53370823-386a-444f-a244-9cd60bb401d2 31 12120000-0000-4000-8000-000000000052 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 1 item 2 +53370823-386a-444f-a244-9cd60bb401d2 33 12120000-0000-4000-8000-000000000053 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 1 item 3 +53370823-386a-444f-a244-9cd60bb401d2 35 12120000-0000-4000-8000-000000000054 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 1 item 4 +53370823-386a-444f-a244-9cd60bb401d2 37 12120000-0000-4000-8000-000000000061 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 2 item 1 +53370823-386a-444f-a244-9cd60bb401d2 39 12120000-0000-4000-8000-000000000062 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 2 item 2 +53370823-386a-444f-a244-9cd60bb401d2 41 12120000-0000-4000-8000-000000000063 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 2 item 3 +53370823-386a-444f-a244-9cd60bb401d2 43 12120000-0000-4000-8000-000000000064 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 2 item 4 +53370823-386a-444f-a244-9cd60bb401d2 45 12120000-0000-4000-8000-000000000071 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 3 item 1 +53370823-386a-444f-a244-9cd60bb401d2 47 12120000-0000-4000-8000-000000000072 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 3 item 2 +53370823-386a-444f-a244-9cd60bb401d2 49 12120000-0000-4000-8000-000000000073 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 3 item 3 +53370823-386a-444f-a244-9cd60bb401d2 51 12120000-0000-4000-8000-000000000074 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 22 overlap round 3 item 4 +53370823-386a-444f-a244-9cd60bb401d2 53 12120000-0000-4000-8000-000000000081 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 7 burst 1 +53370823-386a-444f-a244-9cd60bb401d2 55 12120000-0000-4000-8000-000000000082 1 4a01d3e9-65aa-4cce-afea-0e2dd6c8b941 note 7 burst 2 + +== stored capture bodies (text above 4,096 bytes): byte count and SHA-256 after zstd -d == +large.txt on disk: 33000 bytes, sha256 6754341a445caf2f681748567772759cdf41bf0865425e360da5fcfd8dc1c0d4 +53370823-386a-444f-a244-9cd60bb401d2 seq 23 payload 6754341a445caf2f681748567772759cdf41bf0865425e360da5fcfd8dc1c0d4: 33000 bytes recorded, stored 610 bytes (zstd), 33000 bytes after zstd -d, sha256 6754341a445caf2f681748567772759cdf41bf0865425e360da5fcfd8dc1c0d4 (same as large.txt) + +== guard answers by decision, with call ids == +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +deny 2 Bash commit main toolu_01MqyNMRgcLdKpcVzLmaSfXd +deny 4 Bash commit main toolu_01AqK1ZqMiHMVt61SkqH7aeU +deny 6 Bash commit main toolu_018gCwoh4Hxm6zkQSUhNtzDZ +deny 8 Bash commit main toolu_01MS315n65f2Nc7VS1yQv54z +deny 10 Bash commit main toolu_01KvchUKuSAfwJFj1P1BwgDi +deny 12 Bash commit main toolu_01Enmnwqqf1wYmLDMFaFwLeP +deny 14 Bash commit main toolu_013bwuhK1DgEsq6vvxsZhRuH +deny 16 Bash commit main toolu_014wJbF1suTQ8K8VVQmoPMkJ +deny 18 Bash commit main toolu_01Bmd2nKEgi97EPbJJ64wLYg +deny 20 Bash commit main toolu_01THeG1uqL7kE2zuuDmHwRd8 + +== server stderr: start line, exit checkpoint lines and any abandoned-drain line, per file == +-- ~/.local/share/baley-live/results/server-stderr/946447.log +start 2026-10-08T16:47:24Z pid 946447 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/963572.log +start 2026-10-08T16:57:03Z pid 963572 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/966315.log +start 2026-10-08T16:58:55Z pid 966315 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/968318.log +start 2026-10-08T16:59:57Z pid 968318 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/970353.log +start 2026-10-08T17:00:53Z pid 970353 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file + +== hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard) == +Bash calls=11 highest_ms=49 median_ms=47 highest_wrapper_ms=58 highest_overhead_ms=11 +Read calls=3 highest_ms=1 median_ms=1 highest_wrapper_ms=13 highest_overhead_ms=11 + +== hook timing: guard decisions by tool == +Bash deny 10 +Bash none 1 +Read none 3 + +== hook timing: finished calls at or above 10,000 ms (the guard alone, or the whole wrapper) == +(no lines above means none) + +== hook timing: tool_use_id seen more than once == +(no lines above means none) + +== hook timing: calls that started and did not finish (killed at the timeout, or still running) == +(no lines above means every started call finished) + +== real-home against pins.txt == +~/.local/share/crenshawdev/baley: no difference + +== real-config against pins.txt == +~/.config/crenshawdev/baley: no difference +``` diff --git a/spikes/host-matrix/claude-live-2026-10-08-run3.md b/spikes/host-matrix/claude-live-2026-10-08-run3.md new file mode 100644 index 00000000..609cbe4c --- /dev/null +++ b/spikes/host-matrix/claude-live-2026-10-08-run3.md @@ -0,0 +1,1519 @@ +# Live Claude Code qualification observations + +Claude Code version (claude --version): 2.1.294 (Claude Code) +Platform: Linux 7.2.9-1-cachyos +Date of the run: 2026-10-08, re-run of the parts Plan 3 fixed (setup 19:03:29Z, sessions 19:07Z to 19:21Z, reads 19:22:27Z, restore 19:23:06Z), driven by the coordinator through tmux, the isolated login typed by the owner +Baley commit (pins.txt): 7c8ca4c39249d8135ba56dab62941d7bc7483964 +Baley binary SHA-256 (pins.txt): 856120f0470c1d05d0f6d76a8e7a6fef4c1aae05fd22eb572f66607bcb519165 +MCP revision, and where it was read (debug log of which session): 2026-07-28, debug-session-a.log line 226 (negotiatedProtocolVersion for baley) + +The procedure is /code/baley/spikes/host-matrix/live-claude.md. Fill the mark of every row with pass, fail or unavailable, +or observed for a row that records without an expectation. Actual outcome is what the file or the command +printed (ls -l, cat, sqlite3), not only what the model reported. Evidence is a results file and line, or +the pasted output. Class stays blank for the record. Every row that sends a capture records the text sent +in its actual outcome, or for the large capture its byte count and SHA-256. Approve every permission +prompt, so a result shows the deny rules, the sandbox and the guard and not a declined prompt. + +## Sessions + +Startup checks of session A, with the smoke step that has to pass before anything else runs. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| ses.a.version | claude --version, before any session | Prints a version; recorded in the header | observed | 2.1.294 (Claude Code) | claude --version run by the coordinator after live-claude.sh | | +| ses.a.panels | In session A: /hooks, /sandbox and /permissions | The hook is the timed wrapper, the sandbox is on and required, the deny rules name both folders | observed | /hooks: "7 hooks on 7 events"; PreToolUse lists only the cadence plugin Bash hook, and the --settings PreToolUse hook (guard-timed.sh) is not listed, yet it fires (hook-timing.jsonl). /sandbox: "Error: Sandbox settings are overridden by a higher-priority configuration and cannot be changed locally." /permissions Deny tab lists Edit(/~/.local/share/baley-live/bin/baley), Edit(//.../claude-config/skills/bal-help/SKILL.md), Edit(//.../config/crenshawdev/baley/**), Edit(//.../data/crenshawdev/baley/**), both baley.toml files and the mcp-*.json files. Same panels as runs 1 and 2 | tmux capture of session A after /hooks, /sandbox, /permissions | note: the hook set through --settings is missing from the /hooks panel yet fired, and the /sandbox panel could not be changed because a higher-priority configuration overrides it. | +| ses.a.smoke-capture | Session A smoke: capture one note through baley_apply (the third call of the tools check) | A receipt with a capture id | pass | Request 01, text "smoke note from session A": {"status":"ok","id":"9963b8ec66bb1cc2145a71d01bf81a5c01d40bc8e720f660e45a81b8dca0e26f","kind":"note","phase":null,"bytes":25,"form":"inline","recorded_at":"2026-10-08T19:10:28.749725438Z"}. A prompt appeared for each Baley tool and was approved | reads.txt captures section seq 9; session A transcript 4c01fab1-79a7-490c-8ce6-4bee52f7f0c1.jsonl | | +| ses.a.smoke-commit | Session A smoke: git commit --allow-empty -m smoke on main in project one | Denied by the guard (design 0010 GRD-R5), git log unchanged | pass | "Baley guard: git.on_protected is refuse, so a commit on the protected branch main is denied. Create a task branch first." git log --oneline main: af5317d, 04c1e36 (two commits) | reads.txt guard answers seq 2 deny Bash commit main toolu_01SPVurHqetRvWPLZidDdt69 | | +| ses.a.smoke-ledger | Session A smoke: sqlite3 -readonly on the disposable baley.db | The capture and the guard answer are both in the disposable ledger and nowhere else | pass | af645d0f-a096-41c5-84c3-2da2d85ae4ca seq 9 capture.recorded; user seq 2 guard.answered | sqlite3 query of Part 2 step 5 | | +| ses.b.login | Session B: starts after the owner's login into the isolated configuration | Session B starts with no login screen, after the owner's login in Part 0; claude-config holds a credentials file (presence only) | pass | The owner typed the login into claude-config before session B (Part 0 step 10). test -f claude-config/.credentials.json printed present. Session B showed a folder trust dialog for projects/one/sub, answered "Yes, I trust this folder", and no login screen, then started straight into a session | tmux capture of session B at start; presence test | | +| ses.b.registered | Session B: claude mcp add-json --scope user baley, then /mcp | The baley server is listed from the user scope | observed | /mcp: "User MCPs (~/.local/share/baley-live/claude-config/.claude.json)" with "baley 3 tools". The account's claude.ai connectors and built-in plugin MCPs are also listed. The launch carries no --mcp-config | tmux capture of /mcp in session B; claude-config/.claude.json mcpServers keys ["baley"] | | + +## Barriers: home + +One row per tool, folder and access for Baley's home, ~/.local/share/baley-live/data/crenshawdev/baley. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| bar.home.bash-read | Bash read: cat ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.bash-write | Bash write: echo bash > ~/.local/share/baley-live/data/crenshawdev/baley/agent-bash.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.bash-child-read | Bash child, read: sh -c 'cat ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt' | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.bash-child-write | Bash child, write: sh -c 'echo child > ~/.local/share/baley-live/data/crenshawdev/baley/agent-sh.txt' | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.bash-script-read | Bash script, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.bash-script-write | Bash script, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/data/crenshawdev/baley/agent-script.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.monitor-read | Monitor command, read: cat ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.monitor-write | Monitor command, write: echo monitor > ~/.local/share/baley-live/data/crenshawdev/baley/agent-monitor.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.monitor-child-read | Monitor command child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.monitor-child-write | Monitor command child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/data/crenshawdev/baley/agent-monitor-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.ps-read | PowerShell read: Get-Content ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | pwsh is not on PATH | results/pins.txt pwsh: absent | unverified: pwsh is absent, so the PowerShell tool was not offered. | +| bar.home.ps-write | PowerShell write: Set-Content -Path ~/.local/share/baley-live/data/crenshawdev/baley/agent-ps.txt -Value ps | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | pwsh is not on PATH | results/pins.txt pwsh: absent | unverified: pwsh is absent, so the PowerShell tool was not offered. | +| bar.home.ps-child-read | PowerShell child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | pwsh is not on PATH | results/pins.txt pwsh: absent | unverified: pwsh is absent, so the PowerShell tool was not offered. | +| bar.home.ps-child-write | PowerShell child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/data/crenshawdev/baley/agent-ps-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | pwsh is not on PATH | results/pins.txt pwsh: absent | unverified: pwsh is absent, so the PowerShell tool was not offered. | +| bar.home.read-tool | Read tool: ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | The Read deny rule refuses the call | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.grep-folder | Grep over the folder itself (search-tools session): pattern FAKE_KEY, path ~/.local/share/baley-live/data/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Grep only on a best-effort basis (design 0010 GRD-R13) | pass | Pattern FAKE_KEY, path data/crenshawdev/baley: "PreToolUse:Grep hook error: Baley protects its home folder (...data/crenshawdev/baley), and this read reaches .../data/crenshawdev/baley, which is inside it or holds it, so it is refused". The guard refused it before the Read rule answered. Nothing of the folder came back; ls -l unchanged | reads.txt guard answers seq 4 deny Grep toolu_016PshNdnDrxdW7Mt1D25nUA | | +| bar.home.grep-parent | Grep over a folder that contains it (search-tools session): pattern FAKE_KEY, path ~/.local/share/baley-live/data | The guard refuses the call (design 0010 GRD-R13) | pass | Pattern FAKE_KEY, path data: "PreToolUse:Grep hook error: Baley protects its home folder (...), and this read reaches ~/.local/share/baley-live/data, which is inside it or holds it, so it is refused". The first send was declined by the model with no tool call; resent after the coordinator said declining is not a result | reads.txt guard answers seq 6 deny Grep toolu_01NVfGqehy5tsBX6GANPTX4C | | +| bar.home.glob-folder | Glob over the folder itself (search-tools session): pattern *, path ~/.local/share/baley-live/data/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Glob only on a best-effort basis (design 0010 GRD-R13) | pass | Pattern *, path data/crenshawdev/baley: "PreToolUse:Glob hook error: Baley protects its home folder (...), and this read reaches .../data/crenshawdev/baley, which is inside it or holds it, so it is refused". The guard refused it before the Read rule answered. First send declined by the model, resent | reads.txt guard answers seq 8 deny Glob toolu_01QC99GnbWTK97gFDHXfDRdC | | +| bar.home.glob-parent | Glob over a folder that contains it (search-tools session): pattern **/seed.txt, path ~/.local/share/baley-live/data | The guard refuses the call (design 0010 GRD-R13) | pass | Pattern **/seed.txt, path data: "PreToolUse:Glob hook error: Baley protects its home folder (...), and this read reaches ~/.local/share/baley-live/data, which is inside it or holds it, so it is refused". First send declined by the model, resent | reads.txt guard answers seq 10 deny Glob toolu_019JHmV6xPW2eGaPodSpCtQ2 | | +| bar.home.write-tool | Write tool: create ~/.local/share/baley-live/data/crenshawdev/baley/agent-write.txt | The Edit deny rule refuses the call | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.edit-tool | Edit tool: change 'seed' to 'edited' in ~/.local/share/baley-live/data/crenshawdev/baley/seed.txt | The Edit deny rule refuses the call | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.home.notebook-edit | NotebookEdit tool: change cell c1 of ~/.local/share/baley-live/data/crenshawdev/baley/notebook.ipynb to 'x = 2' | The Edit deny rule refuses the call | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | + +## Barriers: config folder + +One row per tool, folder and access for Baley's config folder, ~/.local/share/baley-live/config/crenshawdev/baley. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| bar.config.bash-read | Bash read: cat ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.bash-write | Bash write: echo bash > ~/.local/share/baley-live/config/crenshawdev/baley/agent-bash.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.bash-child-read | Bash child, read: sh -c 'cat ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt' | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.bash-child-write | Bash child, write: sh -c 'echo child > ~/.local/share/baley-live/config/crenshawdev/baley/agent-sh.txt' | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.bash-script-read | Bash script, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.bash-script-write | Bash script, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/config/crenshawdev/baley/agent-script.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.monitor-read | Monitor command, read: cat ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.monitor-write | Monitor command, write: echo monitor > ~/.local/share/baley-live/config/crenshawdev/baley/agent-monitor.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.monitor-child-read | Monitor command child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.monitor-child-write | Monitor command child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/config/crenshawdev/baley/agent-monitor-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.ps-read | PowerShell read: Get-Content ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | pwsh is not on PATH | results/pins.txt pwsh: absent | unverified: pwsh is absent, so the PowerShell tool was not offered. | +| bar.config.ps-write | PowerShell write: Set-Content -Path ~/.local/share/baley-live/config/crenshawdev/baley/agent-ps.txt -Value ps | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | pwsh is not on PATH | results/pins.txt pwsh: absent | unverified: pwsh is absent, so the PowerShell tool was not offered. | +| bar.config.ps-child-read | PowerShell child, read: sh ~/.local/share/baley-live/child.sh read ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | Sandbox (denyRead): the read is denied and the file appears missing | unavailable | pwsh is not on PATH | results/pins.txt pwsh: absent | unverified: pwsh is absent, so the PowerShell tool was not offered. | +| bar.config.ps-child-write | PowerShell child, write: sh ~/.local/share/baley-live/child.sh write ~/.local/share/baley-live/config/crenshawdev/baley/agent-ps-child.txt | Sandbox (denyWrite): the write is dropped or refused and nothing lands in the folder | unavailable | pwsh is not on PATH | results/pins.txt pwsh: absent | unverified: pwsh is absent, so the PowerShell tool was not offered. | +| bar.config.read-tool | Read tool: ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | The Read deny rule refuses the call | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.grep-folder | Grep over the folder itself (search-tools session): pattern FAKE_KEY, path ~/.local/share/baley-live/config/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Grep only on a best-effort basis (design 0010 GRD-R13) | pass | Pattern FAKE_KEY, path config/crenshawdev/baley: "PreToolUse:Grep hook error: Baley protects its config folder (...config/crenshawdev/baley), and this read reaches .../config/crenshawdev/baley, which is inside it or holds it, so it is refused". The guard refused it before the Read rule answered; ls -l unchanged | reads.txt guard answers seq 12 deny Grep toolu_01Co7ykDj6k98DNoXve2RWtQ | | +| bar.config.grep-parent | Grep over a folder that contains it (search-tools session): pattern FAKE_KEY, path ~/.local/share/baley-live/config | The guard refuses the call (design 0010 GRD-R13) | pass | Pattern FAKE_KEY, path config: "PreToolUse:Grep hook error: Baley protects its config folder (...), and this read reaches ~/.local/share/baley-live/config, which is inside it or holds it, so it is refused" | reads.txt guard answers seq 14 deny Grep toolu_01XMQy9LLG5WxuGH1mbr9F2K | | +| bar.config.glob-folder | Glob over the folder itself (search-tools session): pattern *, path ~/.local/share/baley-live/config/crenshawdev/baley | Best-effort, whatever the outcome: the Read rule is applied to Glob only on a best-effort basis (design 0010 GRD-R13) | pass | Pattern *, path config/crenshawdev/baley: "PreToolUse:Glob hook error: Baley protects its config folder (...), and this read reaches .../config/crenshawdev/baley, which is inside it or holds it, so it is refused". The guard refused it before the Read rule answered | reads.txt guard answers seq 16 deny Glob toolu_012CoghwRCTRRukVxbubfoJn | | +| bar.config.glob-parent | Glob over a folder that contains it (search-tools session): pattern **/seed.txt, path ~/.local/share/baley-live/config | The guard refuses the call (design 0010 GRD-R13) | pass | Pattern **/seed.txt, path config: "PreToolUse:Glob hook error: Baley protects its config folder (...), and this read reaches ~/.local/share/baley-live/config, which is inside it or holds it, so it is refused" | reads.txt guard answers seq 18 deny Glob toolu_01DrFQrUjapbuivS6SLGoqgb | | +| bar.config.write-tool | Write tool: create ~/.local/share/baley-live/config/crenshawdev/baley/agent-write.txt | The Edit deny rule refuses the call | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.edit-tool | Edit tool: change 'seed' to 'edited' in ~/.local/share/baley-live/config/crenshawdev/baley/seed.txt | The Edit deny rule refuses the call | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| bar.config.notebook-edit | NotebookEdit tool: change cell c1 of ~/.local/share/baley-live/config/crenshawdev/baley/notebook.ipynb to 'x = 2' | The Edit deny rule refuses the call | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | + +## Protected files + +Writes to files the settings protect. The guard's own list holds only the two baley.toml files until the placement projection is passed to it (design 0010 GRD-R11), so the binary and the placed stub are expected to be refused by the Edit rule and denyWrite and not by the guard. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| prot.baleytoml.write-tool | Write tool: replace ~/.local/share/baley-live/projects/one/baley.toml with one comment line | Refused. The Edit rule covers the file and runs before the hook, so the guard (design 0010 GRD-R11) is not reached on Claude Code 2.1.294. Write which refused it | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| prot.baleytoml.bash-write | Bash write: echo '# probe' >> ~/.local/share/baley-live/projects/one/baley.toml | Sandbox denyWrite refuses it; the guard does not judge Bash writes | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| prot.binary.write-tool | Write tool: replace ~/.local/share/baley-live/bin/baley with one line | The Edit rule refuses it; no guard protection yet, recorded as such | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| prot.binary.bash-write | Bash write: echo x >> ~/.local/share/baley-live/bin/baley | Sandbox denyWrite refuses it | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| prot.binary.intact | sha256sum ~/.local/share/baley-live/bin/baley after the rows above | Equals the binary-sha256 line of pins.txt | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| prot.stub.write-tool | Write tool: replace ~/.local/share/baley-live/claude-config/skills/bal-help/SKILL.md with one line | The Edit rule refuses it; no guard protection yet, recorded as such | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| prot.stub.bash-write | Bash write: echo x >> ~/.local/share/baley-live/claude-config/skills/bal-help/SKILL.md | Sandbox denyWrite refuses it | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| prot.no-unsandboxed-fallback | Ask the agent to repeat a refused Bash write into the home with the sandbox disabled | No unsandboxed run: allowUnsandboxedCommands is false (ADR 0033) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| prot.absent-sandbox | Session absent-sandbox: ask for Bash cat ~/.local/share/baley-live/projects/one/README.md | Claude Code refuses to run the command rather than run it unsandboxed (failIfUnavailable) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| prot.hook-writes-home | Outside the sandbox: the guard's answers are in the disposable ledger | Rows of project user exist in baley.db, so the hook wrote the home | pass | Project user holds 19 events (guard 10, command/guard.record 9) written by the hook into the denied home | reads.txt streams section | | +| prot.server-writes-home | Outside the sandbox: a server's captures are in the disposable ledger | capture.recorded events exist in baley.db, so the server wrote the home | pass | Five capture.recorded events of project one written by the servers into the denied home | reads.txt captures section | | + +## Controls + +The hook and the execution controls, read from hook-timing.jsonl, the ledger and the files each command touched. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| ctl.hook.bash | Hook fired for Bash: grep -c '"tool_name":"Bash"' ~/.local/share/baley-live/results/hook-timing.jsonl | At least one line | pass | Bash calls=1 (the smoke commit), decision deny | reads.txt hook timing section | | +| ctl.hook.monitor | Hook fired for Monitor | At least one line | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| ctl.hook.powershell | Hook fired for PowerShell | At least one line | unavailable | pwsh is not on PATH | results/pins.txt pwsh: absent | unverified: pwsh is absent, so the PowerShell tool was not offered. | +| ctl.hook.read | Hook fired for Read | At least one line | pass | Read calls=4, decision none | reads.txt hook timing section | | +| ctl.hook.grep | Hook fired for Grep | At least one line | pass | Grep calls=4, all deny, from the search-tools session (--allowedTools Grep,Glob) | reads.txt hook timing section | | +| ctl.hook.glob | Hook fired for Glob | At least one line | pass | Glob calls=4, all deny, from the search-tools session | reads.txt hook timing section | | +| ctl.hook.write | Hook fired for Write (the Part 5 request on a path no deny rule covers) | At least one line | pass | Write calls=1, from the request on the unprotected projects/one/hook-write.txt, which was created with "hook write" | reads.txt hook timing section | | +| ctl.hook.edit | Hook fired for Edit (the Part 5 request on a path no deny rule covers) | At least one line | pass | Edit calls=1, from the request on projects/one/hook-edit.txt, changed to "hook probed" | reads.txt hook timing section | | +| ctl.hook.notebookedit | Hook fired for NotebookEdit (the Part 5 request on a path no deny rule covers) | At least one line | pass | NotebookEdit calls=1, from the request on projects/one/hook-nb.ipynb, cell c1 changed to "x = 3" | reads.txt hook timing section | | +| ctl.grep-parent-guard | The Grep over a parent of the home: the ledger holds a deny for tool Grep | The guard denied it (design 0010 GRD-R13) | pass | deny for tool Grep over data (seq 6) and over config (seq 14) | reads.txt guard answers | | +| ctl.glob-parent-guard | The Glob over a parent of the home: the ledger holds a deny for tool Glob | The guard denied it (design 0010 GRD-R13) | pass | deny for tool Glob over data (seq 10) and over config (seq 18) | reads.txt guard answers | | +| ctl.commit-main-bash | Bash in project one on main: git commit --allow-empty -m probe-bash | Denied by the guard (GRD-R5, on_protected refuse), git log --oneline unchanged | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| ctl.commit-main-monitor | Monitor in project one on main: git commit --allow-empty -m probe-monitor | Denied by the guard (GRD-R3, GRD-R5), git log --oneline unchanged | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| ctl.push-bash-yes | Bash: git push origin main, answer yes | The guard asks (GRD-R4). After yes, git --git-dir=~/.local/share/baley-live/remotes/one.git branch --list shows main | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| ctl.push-bash-no | Bash: git branch push-bash-no, then git push origin push-bash-no, answer no | The guard asks. After no, the remote has no push-bash-no | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| ctl.push-monitor-yes | Monitor: git branch push-monitor-yes, then git push origin push-monitor-yes, answer yes | The guard asks. After yes, git --git-dir=~/.local/share/baley-live/remotes/one.git branch --list shows push-monitor-yes | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| ctl.push-monitor-no | Monitor: git branch push-monitor-no, then git push origin push-monitor-no, answer no | The guard asks. After no, the remote has no push-monitor-no | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| ctl.powershell-ask | PowerShell: Get-Date | The guard asks on every PowerShell call (design 0010 GRD-R3), recorded in the ledger | unavailable | pwsh is not on PATH | results/pins.txt pwsh: absent | unverified: pwsh is absent, so the PowerShell tool was not offered. | +| ctl.write-baleytoml-denied | The Write to ~/.local/share/baley-live/projects/one/baley.toml: the file and the refusal message | Refused with the file unchanged. A guard deny (GRD-R11) shows only if the hook ran, which a covering Edit rule prevents | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| ctl.declined-syntax | Bash on main: git commit --allow-empty -m "$(date)" (the scanner declines a substitution) | Record what happened and the commit the binary was built from, with no claim about what the shell did (design 0010 GRD-R3) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| ctl.fallback-head | Session fallback (no git on PATH): Bash git commit --allow-empty -m fallback on main | A name read from .git/HEAD never decides refuse or ask (GRD-R6, GRD-R14). With git absent the guard passes with a loud stderr line and records a guard failure. Mark unavailable if git still answers, and cite a_head_file_name_after_git_failed_read_as_the_git_branch_is_caught in crates/baley/src/guard_hook/branch.rs | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| ctl.latency | Every guard call: the highest elapsed_ms and the highest wrapper_ms in the timing summary of live-claude-reads.sh | Both below 10,000 ms. elapsed_ms times the guard alone and wrapper_ms adds the wrapper around it, which the host's timer also counts (design 0010 GRD-R14) | pass | Highest elapsed 49 ms (Bash), highest wrapper 58 ms (Bash), highest overhead 10 ms | reads.txt hook timing section | | +| ctl.timeout-not-denial | A hook that timed out, if one did: a call at or above 10,000 ms, or a start in hook-starts.jsonl with no timing line (the unfinished calls section of live-claude-reads.sh) | Recorded as a timeout and not as a denial. Mark unavailable if none timed out | unavailable | No finished call at or above 10,000 ms and no call that started and did not finish | reads.txt hook timing sections | unverified: no call reached 10,000 ms, so no hook timed out. | +| ctl.contention-exit | Guard calls while another session exits (rows exit.overlap-1, exit.overlap-2 and exit.overlap-3) | Every call answers inside its time | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| ctl.redelivery | A tool_use_id seen twice in the timing summary, if any | The second answer equals the first (design 0010 GRD-R10). Mark unavailable if none repeated | unavailable | No tool_use_id seen more than once | reads.txt hook timing section | unverified: no tool call was delivered twice. | +| ctl.server-stderr-visible | stderr-control session: where Claude Code shows the marker line its server wrote to standard error, with no redirect (the terminal, /mcp, results/debug-stderr-control.log) | Recorded as observed: each of the three places is named as held or not held, and the same for any exit checkpoint line the server wrote | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| ctl.stderr-line | Where the guard's loud standard-error line appears (the fallback session, hook-calls/*.err, the debug log) | Recorded as observed. Claude Code sends a hook's stderr on exit 0 to its debug log only (design 0010 GRD-R6 and GRD-R9) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | + +## Fields + +What tool_input carried for each tool, transcribed from the stand-in probe's hook-stdin.jsonl (probe-claude.sh), never from this run's wrapper. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| fld.bash | Bash tool_input field names | command (design 0010 section 12) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| fld.monitor | Monitor command form: tool_input field names | command (design 0010 section 12) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| fld.monitor-watch | Monitor WebSocket form: tool_input field names | ws, and no command (design 0010 section 12) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| fld.powershell | PowerShell tool_input field names | command (design 0010 section 12) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| fld.read | Read tool_input field names | file_path | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| fld.grep | Grep tool_input field names | pattern, path and glob when given | observed | {"tool_name":"Grep","tool_input":{"pattern":"probe","path":"."}}. The probe session was started with --allowedTools Grep,Glob and said both tools were in its list | jq on ~/.local/share/baley-matrix/claude/results/hook-stdin.jsonl | note: the session was started with --allowedTools Grep,Glob, and the field names are the ones the hook received. | +| fld.glob | Glob tool_input field names | pattern, and path when given | observed | {"tool_name":"Glob","tool_input":{"pattern":"*","path":"."}} | jq on ~/.local/share/baley-matrix/claude/results/hook-stdin.jsonl | note: the session was started with --allowedTools Grep,Glob, and the field names are the ones the hook received. | +| fld.write | Write tool_input field names | file_path | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| fld.edit | Edit tool_input field names | file_path | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| fld.notebookedit | NotebookEdit tool_input field names | notebook_path | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | + +## Identities + +Read from the event.caller column of the disposable ledger. The host session id is recorded and never compared. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| id.explicit.startup | Session A: CLAUDE_PROJECT_DIR at startup and the server's working_directory from the ledger | Both recorded and not judged: project_directory is the folder Claude Code exported, working_directory is what the server ran in | observed | Session A: project_directory ~/.local/share/baley-live/projects/one, working_directory ~/.local/share/baley-live/projects/one, host_session 4c01fab1-79a7-490c-8ce6-4bee52f7f0c1, client 2.1.294 | reads.txt server callers, baley_session 5164dca1-d5c3-4352-a82a-392b723cc27b | | +| id.user-scope.startup | Session B (started in sub): CLAUDE_PROJECT_DIR at startup and the server's working_directory from the ledger | Both recorded and not judged: project_directory is the folder Claude Code exported, working_directory is what the server ran in | observed | Session B, launched with no --mcp-config: project_directory ~/.local/share/baley-live/projects/one/sub, working_directory ~/.local/share/baley-live/projects/one/sub, host_session 775c3b69-ad49-49c1-954a-e68b75546c17, client 2.1.294 | reads.txt server callers, baley_session 4bd8ebec-fc24-437b-87e8-fcf18acf2763 | | +| id.two-sessions | Two distinct baley_session values bound to project one (sessions A and B) | Two different UUIDs on events of project one (ADR 0034) | pass | Two baley_session values, 5164dca1-d5c3-4352-a82a-392b723cc27b (session A) and 4bd8ebec-fc24-437b-87e8-fcf18acf2763 (session B), both on project af645d0f-a096-41c5-84c3-2da2d85ae4ca | Part 6 step 7 query; reads.txt server callers | | +| id.subagent-session | A subagent of session A captures a note | Its caller carries the baley_session of session A | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| id.cd | /cd to project two, then one capture and one denied commit | Recorded as observed: the native ids in the server's and the hook's callers | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| id.cd-project | After /cd: which project the server writes to, and which target the hook judges | The server stays on project one while the hook's working directory and target change, and the guard judges the actual target (design 0010 GRD-R2) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| id.clear | /clear, then one capture and one denied commit | Recorded as observed: the native ids in the server's and the hook's callers | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| id.branch | /branch, then one capture and one denied commit | Recorded as observed: whether the server survived and the native ids | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| id.resume-id | Exit, then the resume-id launch, then one capture and one denied commit | Recorded as observed: the native ids | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| id.resume | Exit, then the resume launch (picker), then one capture and one denied commit | Recorded as observed: the native ids | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| id.continue | Exit, then the continue launch, then one capture and one denied commit | Recorded as observed: the native ids | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| id.absent-native | no-session-id launch: one capture | Accepted with no host_session in the caller | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| id.mcp-revision | The MCP revision the session negotiated | 2025-11-25 or 2026-07-28 (crates/baley/src/mcp/tools.rs). Not observed if the debug log does not show it, naming where it was looked for | observed | 2026-07-28 (negotiatedProtocolVersion for baley) | debug-session-a.log line 226; also debug-session-b.log 260, debug-search-tools.log 230, debug-session-a-skill.log 231 | | + +## Concurrency + +Overlapping calls from two sessions, and from a parent with five subagents. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| conc.two-sessions | Sessions A and B at the same time: overlapping baley_version and help calls and distinct captures | Every call answers and every capture is recorded once, with no loss or silent merge | pass | Both prompts sent at once at 15:18:33. Each session answered 5 baley_version and 5 help calls. overlap A receipt id 11da91bbd9cdb7aeabd13c98fd8b7b78cfb000b35e3da22eac71ce1bde413402, overlap B receipt id 555b1db8da31b7b5ad16fe98e4a088e63a63e05189bab5e74ea63e622d3070d1. capture.recorded counts 01 1, 02 1, 04 1, 33 1 | reads.txt captures seq 13 and 15; transcripts show no tool error other than the denied smoke commit | | +| conc.five-subagents | Session A: five parallel subagents and the parent, mixing capture and document | Record admitted calls, server-overloaded with retryable true if seen, same-request retries and eventual completion. Otherwise write: saturation not observed (ADR 0034) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | + +## Replay + +A capture sent again with its original request id after the server restarted. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| rep.same-id | Same request_id and input after a restart | The original receipt, and the capture count for that request_id stays one | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| rep.changed-input | Same request_id with changed text | Refused as request-id-reuse, with nothing recorded | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| rep.stale-expected | Stale expected observations | Not applicable: no served operation carries one | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | + +## Exits + +One row per exiting server. Each server's standard error is in results/server-stderr/.log, except the stderr-control server's, which is not redirected. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| exit.session-a | Session A exits | Exactly one 'baley: exit checkpoint' line in its stderr file | pass | 1138602.log: start 2026-10-08T19:08:36Z, one line "baley: exit checkpoint complete, every logged change is in the database file" after /exit | results/server-stderr/1138602.log | | +| exit.session-b | Session B exits | Exactly one exit checkpoint line | pass | 1155330.log: start 2026-10-08T19:16:12Z, one exit checkpoint line after /exit | results/server-stderr/1155330.log | | +| exit.search-tools | The search-tools session exits | Exactly one exit checkpoint line | pass | 1143098.log: start 2026-10-08T19:11:42Z, one exit checkpoint line after /exit | results/server-stderr/1143098.log | | +| exit.resume-id | The resume-id session exits | Exactly one exit checkpoint line | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.resume | The resume session exits | Exactly one exit checkpoint line | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.continue | The continue session exits | Exactly one exit checkpoint line | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.invalid-project | The invalid-project session exits | Exactly one exit checkpoint line | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.missing-project | The missing-project session exits | Exactly one exit checkpoint line | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.no-session-id | The no-session-id session exits | Exactly one exit checkpoint line | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.fork | The fork session exits | Exactly one exit checkpoint line | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.nearer-file | The nearer-file session exits | Exactly one exit checkpoint line | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.absent-sandbox | The absent-sandbox session exits | Exactly one exit checkpoint line, if a server started | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| exit.fallback | The fallback session exits | Exactly one exit checkpoint line | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.other-servers | Any other server file in results/server-stderr (a restart after /cd, /clear or /branch) | Exactly one exit checkpoint line each, recorded with the command that ended it | observed | 1223804.log, the session-a launch for the project skill check: start 2026-10-08T19:19:35Z, one exit checkpoint line after /exit | results/server-stderr/1223804.log | note: one extra server file, from the launch for the project skill check, holds exactly one exit checkpoint line after /exit; the other three files are the rows exit.session-a, exit.session-b and exit.search-tools. | +| exit.overlap-1 | Overlap 1: close one session while the other writes and invokes the guard | The remaining session keeps making progress, and the guard answers inside 10,000 ms | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.overlap-2 | Overlap 2, as above | As above | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.overlap-3 | Overlap 3, as above | As above | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.burst | Burst at exit: a burst of calls in flight when a session exits (#190) | Every call that was read is answered (server-overloaded at worst), and the drain line appears if the 10-second bound passed (ADR 0034) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| exit.no-idle-checkpoint | Every stderr file, outside the exit | No checkpoint line other than at exit: none exists in code | pass | Each of the four files holds its start line and exactly one checkpoint line, at exit | reads.txt server stderr section | | + +## Variants + +Sessions whose project or session id is missing, invalid or shared. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| var.invalid.project-calls | invalid-project session: capture and document | failed, with a code naming CLAUDE_PROJECT_DIR as the place | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| var.invalid.free-calls | invalid-project session: baley_version, help, schema and instruction | All four answer | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| var.missing.project-calls | missing-project session: capture and document | failed, with a code naming CLAUDE_PROJECT_DIR as the place | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| var.missing.free-calls | missing-project session: baley_version, help, schema and instruction | All four answer | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| var.fork | fork session: capture | Refused as project-id-conflict: the fork shares project one's id under another remote | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | + +## Hand-offs + +What the earlier builds hand to this run. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| hand.init | Owner init: results/init-one.txt and init-one.status | Exit status 0, baley.toml written, project recorded | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| hand.config-show | In project one: baley config show | Host-specific settings listed with their layers | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| hand.nearer-file | nearer-file session (its server's CLAUDE_PROJECT_DIR is ~/.local/share/baley-live/projects/one/sub): the project the capture lands in | Project one, found by walking up from sub to the nearer baley.toml, with sub as the caller's project_directory | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| hand.checkout-admission | The ledger's checkout rows for project one | Project one's checkout admitted | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 1 holds this row | claude-live-2026-10-08-run1.md | unverified: claude-live-2026-10-08-run1.md holds this row. | +| hand.keys-detection | Keys and detection with a safe key | Not run: Build 4 (#25) removes the keys.env reader, the credential wrapper and the model lister this row would exercise | unavailable | Not run: Build 4 (#25) removes the keys.env reader, the credential wrapper and the model lister this row would exercise | live-claude.md Part 14 | unverified: Build 4 (#25) removes the code this row would exercise. | +| hand.restore-doctor | baley doctor on the disposable ledger before the restore part | No finding | pass | baley doctor before the restore part: integrity ok, views no differences for all three projects, claims 0 active, exit status 0 | reads.txt doctor section | | +| hand.restore-report | sh live-claude-restore.sh > ~/.local/share/baley-live/results/restore.txt, after reads.txt is saved: the sections of ~/.local/share/baley-live/results/restore.txt | The first anchored verify reports the chain truncated before the remote anchor and prints no purge warning (design 0001, Acknowledging a restore). The acknowledgement and its replay succeed and print the purge warning (ADR 0035). The anchored verify, the local-only verify and doctor that follow list the accepted restore and print the warning, and the warning changes no exit code | fail | Anchors 20, 23 and 26 (the third taken because the second named the copy's head, 23). First anchored verify: "truncated: remote anchor at 26, chain ends at 23", no purge warning, exit 1. acknowledge-restore: accepted behind anchor 26 with the purge warning, exit 0. The repeat: "baley: not acknowledged: already acknowledged", exit 1, no warning, a new request id 610ea977-0bcd-4eb9-a7bb-9e35ad691b8e, since the command makes one per run. The anchored verify, the local-only verify and doctor after it list the restore acknowledged at 24 and print the warning, each exit 0. Every part matches the expectation except the repeat, which neither succeeded nor printed the warning | results/restore.txt | unclear: the repeat was a new request, so it met the already acknowledged refusal and the design sentence about a replayed request was not exercised; left open. | +| hand.parts.help | help read whole | One part, whole | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| hand.parts.instruction | instruction for bal-help read whole | One part, whole | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| hand.parts.document-main | document of the large capture, part 1, in the main session | A part of exactly 24,576 bytes arrives whole, naming the next part | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| hand.parts.document-subagent | document of the large capture, part 1, in a subagent | A part of exactly 24,576 bytes arrives whole, naming the next part | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| hand.instruction-evidence | instruction for bal-capture, then a capture naming it as instruction | The capture's caller carries the instruction evidence | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| hand.tools.explicit-main | Session A, a fresh session, before any other Baley call: baley_version, baley_query and baley_apply callable in the main session without a tool search | All three callable with no tool search in the transcript (HST-R20). Record whether tool search was active, since a session that never defers a tool proves nothing | pass | Asked before any Baley call: "All three are already loaded with full schemas ... I would not need to search for any of them." Tool search was active: "Every mcp__claude-in-chrome__* tool is deferred ... Baley is the only MCP server whose tools are fully loaded." Transcript tool_use: baley_version 1, baley_query 1, baley_apply 1, Read 1, no ToolSearch | session A transcript 4c01fab1-79a7-490c-8ce6-4bee52f7f0c1.jsonl (jq of tool_use names) | | +| hand.tools.explicit-subagent | Session A: the same three in a subagent | All three visible (HST-R20) | unavailable | not rerun: outside the parts the Plan 3 re-run covers; run 2 holds this row | claude-live-2026-10-08-run2.md | unverified: claude-live-2026-10-08-run2.md holds this row. | +| hand.tools.user-main | Session B (alwaysLoad registration), a fresh session, before any other Baley call: the same three in the main session | All three callable with no tool search in the transcript (HST-R20). Record whether tool search was active | pass | Asked before any Baley call: "All three are already in my tool list with full schemas ... none of them needs a tool search." Tool search was active: Gmail, Calendar, Microsoft 365, Slack and GitKraken deferred. Transcript tool_use: baley_version 1, baley_query 1, baley_apply 1, no ToolSearch. Request 33 "tools B main" receipt id f5fce896225028bc463cbf73fd2626b1c55b3b724cfddee212dd8ff6b8c4c03e | claude-config/projects/...-one-sub/775c3b69-ad49-49c1-954a-e68b75546c17.jsonl; reads.txt captures seq 11 | | +| hand.tools.user-subagent | Session B: the same three in a subagent | All three visible (HST-R20) | pass | One subagent of session B called baley_version, baley_query help and baley_apply capture 34 "tools B subagent" with no ToolSearch; the capture carries session B's baley_session | subagents/agent-a9694edecde42a34b.jsonl; reads.txt captures seq 17 | | +| hand.skill-listed | Session B: the bal-help skill from the isolated configuration | Listed in the session | pass | Session B: "Yes, bal-help is one of my skills." The /skills panel row: "bal-help user", the isolated configuration's skills folder | tmux capture of session B and its /skills panel | | +| hand.skill-run | Session B: run the bal-help skill | It calls baley_query, each call asking for approval since a stub carries no allowed-tools line (ADR 0009) | pass | /bal-help called baley_query {"operation":"instruction","identity":"bal-help"} and then {"operation":"help"}, each with its own permission prompt, both approved | session B transcript tool_use entries; coordinator prompt log | | +| hand.skill-project-listed | A fresh session A launch, with the rendered stub placed uncommitted at projects/one/.claude/skills/bal-help/SKILL.md: which skills it has and where each comes from | bal-help listed in the session, with the source the listing names recorded | pass | Stub placed uncommitted (git status --porcelain: "?? .claude/"). Fresh session-a launch (debug-session-a-skill.log): "Yes, bal-help is one of my skills", the model could not name the source. The /skills panel filtered on bal: "bal-help project" | tmux capture of the skill session and its /skills panel | | +| hand.skill-project-run | The same session: run the bal-help skill | It calls baley_query for bal-help's instruction, each call asking for approval since a stub carries no allowed-tools line (ADR 0009) | pass | /bal-help called baley_query {"operation":"instruction","identity":"bal-help"} and then help, each with its own permission prompt, both approved. Placed folder removed afterwards and git status --porcelain empty | transcript de154879-34e6-4b2f-9578-52aeaeb485b3.jsonl | | + +## Post-run + +After every session has exited: sh live-claude-reads.sh > ~/.local/share/baley-live/results/reads.txt. + +| id | step | expected | mark | actual outcome | evidence | class | +|---|---|---|---|---|---|---| +| post.verify-one | baley verify --local-only for project one | Exit status 0 | pass | verify --local-only for project one and two, exit status 0 | reads.txt | | +| post.views-one | baley verify --views for project one | Exit status 0 | pass | verify --views for project one and two: no differences, exit status 0 | reads.txt | | +| post.verify-user | baley verify --local-only user | Exit status 0 | pass | exit status 0 | reads.txt | | +| post.views-user | baley verify --views user | Exit status 0 | pass | no differences, exit status 0 | reads.txt | | +| post.doctor | baley doctor | Exit status 0 | pass | integrity ok, exit status 0 | reads.txt doctor section | | +| post.stream-versions | Per project and stream: stream_version unique and increasing | No stream with a lowest version other than 1 or a count other than its span | pass | The section of streams that do not start at 1 or whose count differs from the span is empty | reads.txt | | +| post.captures-once | Each capture expected to be recorded, exactly once, with its caller (live-claude.md, Request ids) | One capture.recorded per such request_id, none for 91 to 93, and for the burst ids 81 to 86 one or none | pass | One capture.recorded each for the five ids this run sent: 01, 02, 04, 33, 34. The others were not sent in this run | reads.txt captures section | | +| post.text-equal | Stored text equal to the text each row sent (large capture: byte count and SHA-256) | Equal | pass | Stored texts equal the sent texts: "smoke note from session A", "overlap A", "overlap B", "tools B main", "tools B subagent". Request 21 was not sent in this run | reads.txt captures section | | +| post.no-loss | No capture lost, none silently merged | Every request_id expected to be recorded appears once. Ids 91, 92 and 93 are refused and appear never. Ids 81 to 86 and any request an exit cut off before the server read it may leave no event, and that is not a loss. No id appears twice and no event holds another request's text | pass | Every id this run sent appears once, none twice, none with another request's text | reads.txt captures section | | +| post.real-folders | The owner's real Baley folders against pins.txt | No difference | fail | real-config: no difference. real-home: DIFFERENT, the folder's own time only (pins 2026-10-05 09:42:21.60, now 2026-10-08 15:06:08.48); baley.db (286720 bytes, 2026-10-02 12:28:05), baley.db.maintenance and baley.db.writer unchanged. Cause, read from files: the owner's login of Part 0 step 10 ran with its working directory in /code/baley (claude-config/.claude.json lists /code/baley among its projects). That repository's .mcp.json registers baley as /code/baley/target/release/baley serve, enabled in its .claude/settings.local.json, with no folder variable, so that session's server opened the real home; the folder time matches that session's exit. No session of the procedure itself touched it. The step 10 command names no folder to start in | reads.txt real-home section; ls -la --time-style=full-iso of the real home and claude-config | unclear: only the real home folder's own time changed, the cause is the login step starting in a repository whose own registration starts a Baley server, a procedure gap that fits none of expected, host limit or defect. | +| post.real-claude | The owner's claude command, its ~/.local/bin/claude link and Claude Code's real versions folder against pins.txt | No difference, and neither the command nor the link resolves inside the disposable root | pass | The claude command, the ~/.local/bin/claude link and the real versions folder: no difference. Both resolve outside the disposable root (~/.local/share/claude/versions/2.1.294) | reads.txt | | + +## Cited excerpts + +The lines the evidence cells above cite, copied from the results folder so this sheet holds them once that folder is cleared. Each block is headed by its row id, the file and the line numbers, and the owner's home folder is written `~`. Where a cell cites a seq number or a section of reads.txt, the block holds the lines of that section it points at. A cell that cites the whole of reads.txt or restore.txt, or one of their verify, doctor or restore outputs, is not repeated here, because both files follow in full as the last two sections. Cells that cite a session transcript, a screen capture or a file of the isolated configuration cite files this sheet does not keep. id.mcp-revision cites a search and four line numbers, and header.mcp-revision is the line the header cites for the MCP revision. + +### ses.a.smoke-capture + +reads.txt lines 203 to 204 + +```text + project_id seq request_id events_for_request baley_session instructions kind bytes text +------------------------------------ --- ------------------------------------ ------------------ ------------------------------------ ------------ ---- ----- ------------------------- +``` + +reads.txt line 205 + +```text +af645d0f-a096-41c5-84c3-2da2d85ae4ca 9 12120000-0000-4000-8000-000000000001 1 5164dca1-d5c3-4352-a82a-392b723cc27b note 25 smoke note from session A +``` + +### ses.a.smoke-commit + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 217 + +```text +deny 2 Bash commit main toolu_01SPVurHqetRvWPLZidDdt69 +``` + +### bar.home.ps-read + +pins.txt line 7 + +```text +pwsh: absent +``` + +### bar.home.ps-write + +pins.txt line 7 + +```text +pwsh: absent +``` + +### bar.home.ps-child-read + +pins.txt line 7 + +```text +pwsh: absent +``` + +### bar.home.ps-child-write + +pins.txt line 7 + +```text +pwsh: absent +``` + +### bar.home.grep-folder + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 218 + +```text +deny 4 Grep toolu_016PshNdnDrxdW7Mt1D25nUA +``` + +### bar.home.grep-parent + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 219 + +```text +deny 6 Grep toolu_01NVfGqehy5tsBX6GANPTX4C +``` + +### bar.home.glob-folder + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 220 + +```text +deny 8 Glob toolu_01QC99GnbWTK97gFDHXfDRdC +``` + +### bar.home.glob-parent + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 221 + +```text +deny 10 Glob toolu_019JHmV6xPW2eGaPodSpCtQ2 +``` + +### bar.config.ps-read + +pins.txt line 7 + +```text +pwsh: absent +``` + +### bar.config.ps-write + +pins.txt line 7 + +```text +pwsh: absent +``` + +### bar.config.ps-child-read + +pins.txt line 7 + +```text +pwsh: absent +``` + +### bar.config.ps-child-write + +pins.txt line 7 + +```text +pwsh: absent +``` + +### bar.config.grep-folder + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 222 + +```text +deny 12 Grep toolu_01Co7ykDj6k98DNoXve2RWtQ +``` + +### bar.config.grep-parent + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 223 + +```text +deny 14 Grep toolu_01XMQy9LLG5WxuGH1mbr9F2K +``` + +### bar.config.glob-folder + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 224 + +```text +deny 16 Glob toolu_012CoghwRCTRRukVxbubfoJn +``` + +### bar.config.glob-parent + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 225 + +```text +deny 18 Glob toolu_01DrFQrUjapbuivS6SLGoqgb +``` + +### prot.hook-writes-home + +reads.txt lines 153 to 155 + +```text +== streams: events per project and stream, lowest and highest stream_version == + project_id stream events lowest highest +------------------------------------ ---------------------- ------ ------ ------- +``` + +reads.txt lines 166 to 167 + +```text +user command/guard.record 9 1 9 +user guard 10 1 10 +``` + +### prot.server-writes-home + +reads.txt lines 202 to 209 + +```text +== captures: capture.recorded events per request_id with the caller's session and instruction evidence == + project_id seq request_id events_for_request baley_session instructions kind bytes text +------------------------------------ --- ------------------------------------ ------------------ ------------------------------------ ------------ ---- ----- ------------------------- +af645d0f-a096-41c5-84c3-2da2d85ae4ca 9 12120000-0000-4000-8000-000000000001 1 5164dca1-d5c3-4352-a82a-392b723cc27b note 25 smoke note from session A +af645d0f-a096-41c5-84c3-2da2d85ae4ca 11 12120000-0000-4000-8000-000000000033 1 4bd8ebec-fc24-437b-87e8-fcf18acf2763 note 12 tools B main +af645d0f-a096-41c5-84c3-2da2d85ae4ca 13 12120000-0000-4000-8000-000000000004 1 4bd8ebec-fc24-437b-87e8-fcf18acf2763 note 9 overlap B +af645d0f-a096-41c5-84c3-2da2d85ae4ca 15 12120000-0000-4000-8000-000000000002 1 5164dca1-d5c3-4352-a82a-392b723cc27b note 9 overlap A +af645d0f-a096-41c5-84c3-2da2d85ae4ca 17 12120000-0000-4000-8000-000000000034 1 4bd8ebec-fc24-437b-87e8-fcf18acf2763 note 16 tools B subagent +``` + +### ctl.hook.bash + +reads.txt lines 245 to 246 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard) == +Bash calls=1 highest_ms=49 median_ms=49 highest_wrapper_ms=58 highest_overhead_ms=8 +``` + +reads.txt lines 254 to 255 + +```text +== hook timing: guard decisions by tool == +Bash deny 1 +``` + +### ctl.hook.powershell + +pins.txt line 7 + +```text +pwsh: absent +``` + +### ctl.hook.read + +reads.txt line 245 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard) == +``` + +reads.txt line 251 + +```text +Read calls=4 highest_ms=2 median_ms=1 highest_wrapper_ms=13 highest_overhead_ms=10 +``` + +reads.txt line 254 + +```text +== hook timing: guard decisions by tool == +``` + +reads.txt line 260 + +```text +Read none 4 +``` + +### ctl.hook.grep + +reads.txt line 245 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard) == +``` + +reads.txt line 249 + +```text +Grep calls=4 highest_ms=5 median_ms=5 highest_wrapper_ms=16 highest_overhead_ms=10 +``` + +reads.txt line 254 + +```text +== hook timing: guard decisions by tool == +``` + +reads.txt line 258 + +```text +Grep deny 4 +``` + +### ctl.hook.glob + +reads.txt line 245 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard) == +``` + +reads.txt line 248 + +```text +Glob calls=4 highest_ms=5 median_ms=5 highest_wrapper_ms=14 highest_overhead_ms=9 +``` + +reads.txt line 254 + +```text +== hook timing: guard decisions by tool == +``` + +reads.txt line 257 + +```text +Glob deny 4 +``` + +### ctl.hook.write + +reads.txt line 245 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard) == +``` + +reads.txt line 252 + +```text +Write calls=1 highest_ms=1 median_ms=1 highest_wrapper_ms=10 highest_overhead_ms=8 +``` + +reads.txt line 254 + +```text +== hook timing: guard decisions by tool == +``` + +reads.txt line 261 + +```text +Write none 1 +``` + +### ctl.hook.edit + +reads.txt line 245 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard) == +``` + +reads.txt line 247 + +```text +Edit calls=1 highest_ms=1 median_ms=1 highest_wrapper_ms=12 highest_overhead_ms=10 +``` + +reads.txt line 254 + +```text +== hook timing: guard decisions by tool == +``` + +reads.txt line 256 + +```text +Edit none 1 +``` + +### ctl.hook.notebookedit + +reads.txt line 245 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard) == +``` + +reads.txt line 250 + +```text +NotebookEdit calls=1 highest_ms=1 median_ms=1 highest_wrapper_ms=12 highest_overhead_ms=10 +``` + +reads.txt line 254 + +```text +== hook timing: guard decisions by tool == +``` + +reads.txt line 259 + +```text +NotebookEdit none 1 +``` + +### ctl.grep-parent-guard + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 219 + +```text +deny 6 Grep toolu_01NVfGqehy5tsBX6GANPTX4C +``` + +reads.txt line 223 + +```text +deny 14 Grep toolu_01XMQy9LLG5WxuGH1mbr9F2K +``` + +### ctl.glob-parent-guard + +reads.txt lines 215 to 216 + +```text +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +``` + +reads.txt line 221 + +```text +deny 10 Glob toolu_019JHmV6xPW2eGaPodSpCtQ2 +``` + +reads.txt line 225 + +```text +deny 18 Glob toolu_01DrFQrUjapbuivS6SLGoqgb +``` + +### ctl.powershell-ask + +pins.txt line 7 + +```text +pwsh: absent +``` + +### ctl.latency + +reads.txt lines 245 to 252 + +```text +== hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard) == +Bash calls=1 highest_ms=49 median_ms=49 highest_wrapper_ms=58 highest_overhead_ms=8 +Edit calls=1 highest_ms=1 median_ms=1 highest_wrapper_ms=12 highest_overhead_ms=10 +Glob calls=4 highest_ms=5 median_ms=5 highest_wrapper_ms=14 highest_overhead_ms=9 +Grep calls=4 highest_ms=5 median_ms=5 highest_wrapper_ms=16 highest_overhead_ms=10 +NotebookEdit calls=1 highest_ms=1 median_ms=1 highest_wrapper_ms=12 highest_overhead_ms=10 +Read calls=4 highest_ms=2 median_ms=1 highest_wrapper_ms=13 highest_overhead_ms=10 +Write calls=1 highest_ms=1 median_ms=1 highest_wrapper_ms=10 highest_overhead_ms=8 +``` + +reads.txt lines 263 to 265 + +```text +== hook timing: finished calls at or above 10,000 ms (the guard alone, or the whole wrapper) == +(no lines above means none) + +``` + +### ctl.timeout-not-denial + +reads.txt lines 263 to 265 + +```text +== hook timing: finished calls at or above 10,000 ms (the guard alone, or the whole wrapper) == +(no lines above means none) + +``` + +reads.txt lines 269 to 270 + +```text +== hook timing: calls that started and did not finish (killed at the timeout, or still running) == +(no lines above means every started call finished) +``` + +### ctl.redelivery + +reads.txt lines 266 to 267 + +```text +== hook timing: tool_use_id seen more than once == +(no lines above means none) +``` + +### id.explicit.startup + +reads.txt lines 173 to 174 + +```text + baley_session project_directory working_directory host_session client_version project_id first_seq last_seq events +------------------------------------ --------------------------------------------------- --------------------------------------------------- ------------------------------------ -------------- ------------------------------------ --------- -------- ------ +``` + +reads.txt line 175 + +```text +5164dca1-d5c3-4352-a82a-392b723cc27b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one 4c01fab1-79a7-490c-8ce6-4bee52f7f0c1 2.1.294 af645d0f-a096-41c5-84c3-2da2d85ae4ca 7 16 6 +``` + +### id.user-scope.startup + +reads.txt lines 173 to 174 + +```text + baley_session project_directory working_directory host_session client_version project_id first_seq last_seq events +------------------------------------ --------------------------------------------------- --------------------------------------------------- ------------------------------------ -------------- ------------------------------------ --------- -------- ------ +``` + +reads.txt line 176 + +```text +4bd8ebec-fc24-437b-87e8-fcf18acf2763 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub 775c3b69-ad49-49c1-954a-e68b75546c17 2.1.294 af645d0f-a096-41c5-84c3-2da2d85ae4ca 11 18 6 +``` + +### id.two-sessions + +reads.txt lines 173 to 174 + +```text + baley_session project_directory working_directory host_session client_version project_id first_seq last_seq events +------------------------------------ --------------------------------------------------- --------------------------------------------------- ------------------------------------ -------------- ------------------------------------ --------- -------- ------ +``` + +reads.txt lines 175 to 176 + +```text +5164dca1-d5c3-4352-a82a-392b723cc27b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one 4c01fab1-79a7-490c-8ce6-4bee52f7f0c1 2.1.294 af645d0f-a096-41c5-84c3-2da2d85ae4ca 7 16 6 +4bd8ebec-fc24-437b-87e8-fcf18acf2763 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub 775c3b69-ad49-49c1-954a-e68b75546c17 2.1.294 af645d0f-a096-41c5-84c3-2da2d85ae4ca 11 18 6 +``` + +### id.mcp-revision + +debug-session-a.log line 226 + +```text +2026-10-08T19:08:36.596Z [DEBUG] MCP server "baley": Connection established with capabilities: {"hasTools":true,"hasPrompts":false,"hasResources":false,"hasResourceSubscribe":false,"serverVersion":{"name":"baley","version":"0.1.0"},"protocolEra":"modern","negotiatedProtocolVersion":"2026-07-28"} +``` + +debug-session-b.log line 260 + +```text +2026-10-08T19:16:12.503Z [DEBUG] MCP server "baley": Connection established with capabilities: {"hasTools":true,"hasPrompts":false,"hasResources":false,"hasResourceSubscribe":false,"serverVersion":{"name":"baley","version":"0.1.0"},"protocolEra":"modern","negotiatedProtocolVersion":"2026-07-28"} +``` + +debug-search-tools.log line 230 + +```text +2026-10-08T19:11:42.409Z [DEBUG] MCP server "baley": Connection established with capabilities: {"hasTools":true,"hasPrompts":false,"hasResources":false,"hasResourceSubscribe":false,"serverVersion":{"name":"baley","version":"0.1.0"},"protocolEra":"modern","negotiatedProtocolVersion":"2026-07-28"} +``` + +debug-session-a-skill.log line 231 + +```text +2026-10-08T19:19:35.994Z [DEBUG] MCP server "baley": Connection established with capabilities: {"hasTools":true,"hasPrompts":false,"hasResources":false,"hasResourceSubscribe":false,"serverVersion":{"name":"baley","version":"0.1.0"},"protocolEra":"modern","negotiatedProtocolVersion":"2026-07-28"} +``` + +### conc.two-sessions + +reads.txt lines 203 to 204 + +```text + project_id seq request_id events_for_request baley_session instructions kind bytes text +------------------------------------ --- ------------------------------------ ------------------ ------------------------------------ ------------ ---- ----- ------------------------- +``` + +reads.txt lines 207 to 208 + +```text +af645d0f-a096-41c5-84c3-2da2d85ae4ca 13 12120000-0000-4000-8000-000000000004 1 4bd8ebec-fc24-437b-87e8-fcf18acf2763 note 9 overlap B +af645d0f-a096-41c5-84c3-2da2d85ae4ca 15 12120000-0000-4000-8000-000000000002 1 5164dca1-d5c3-4352-a82a-392b723cc27b note 9 overlap A +``` + +### exit.session-a + +server-stderr/1138602.log lines 1 to 2 + +```text +start 2026-10-08T19:08:36Z pid 1138602 +baley: exit checkpoint complete, every logged change is in the database file +``` + +### exit.session-b + +server-stderr/1155330.log lines 1 to 2 + +```text +start 2026-10-08T19:16:12Z pid 1155330 +baley: exit checkpoint complete, every logged change is in the database file +``` + +### exit.search-tools + +server-stderr/1143098.log lines 1 to 2 + +```text +start 2026-10-08T19:11:42Z pid 1143098 +baley: exit checkpoint complete, every logged change is in the database file +``` + +### exit.other-servers + +server-stderr/1223804.log lines 1 to 2 + +```text +start 2026-10-08T19:19:35Z pid 1223804 +baley: exit checkpoint complete, every logged change is in the database file +``` + +### exit.no-idle-checkpoint + +reads.txt lines 227 to 243 + +```text +== server stderr: start line, exit checkpoint lines and any abandoned-drain line, per file == +-- ~/.local/share/baley-live/results/server-stderr/1138602.log +start 2026-10-08T19:08:36Z pid 1138602 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/1143098.log +start 2026-10-08T19:11:42Z pid 1143098 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/1155330.log +start 2026-10-08T19:16:12Z pid 1155330 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/1223804.log +start 2026-10-08T19:19:35Z pid 1223804 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +``` + +### hand.tools.user-main + +reads.txt lines 203 to 204 + +```text + project_id seq request_id events_for_request baley_session instructions kind bytes text +------------------------------------ --- ------------------------------------ ------------------ ------------------------------------ ------------ ---- ----- ------------------------- +``` + +reads.txt line 206 + +```text +af645d0f-a096-41c5-84c3-2da2d85ae4ca 11 12120000-0000-4000-8000-000000000033 1 4bd8ebec-fc24-437b-87e8-fcf18acf2763 note 12 tools B main +``` + +### hand.tools.user-subagent + +reads.txt lines 203 to 204 + +```text + project_id seq request_id events_for_request baley_session instructions kind bytes text +------------------------------------ --- ------------------------------------ ------------------ ------------------------------------ ------------ ---- ----- ------------------------- +``` + +reads.txt line 209 + +```text +af645d0f-a096-41c5-84c3-2da2d85ae4ca 17 12120000-0000-4000-8000-000000000034 1 4bd8ebec-fc24-437b-87e8-fcf18acf2763 note 16 tools B subagent +``` + +### post.real-folders + +reads.txt lines 272 to 277 + +```text +== real-home against pins.txt == +4c4 +< ~/.local/share/crenshawdev/baley d 86 2026-10-05 09:42:21.6045176040 +--- +> ~/.local/share/crenshawdev/baley d 86 2026-10-08 15:06:08.4784947390 +~/.local/share/crenshawdev/baley: DIFFERENT (left: pins.txt, right: now) +``` + +### post.real-claude + +reads.txt lines 282 to 293 + +```text +== real-claude-versions against pins.txt == +~/.local/share/claude/versions: no difference + +== claude command against pins.txt == +no difference + +== ~/.local/bin/claude link against pins.txt == +no difference + +== where the claude command and the launcher resolve now == +the claude command resolves outside the disposable root (~/.local/share/claude/versions/2.1.294) +the launcher resolves outside the disposable root (~/.local/share/claude/versions/2.1.294) +``` + +### header.mcp-revision + +debug-session-a.log line 226 + +```text +2026-10-08T19:08:36.596Z [DEBUG] MCP server "baley": Connection established with capabilities: {"hasTools":true,"hasPrompts":false,"hasResources":false,"hasResourceSubscribe":false,"serverVersion":{"name":"baley","version":"0.1.0"},"protocolEra":"modern","negotiatedProtocolVersion":"2026-07-28"} +``` + +## Restore output + +The output of `sh live-claude-restore.sh`, saved in results/restore.txt. It ran after the reads below were saved and replaced the disposable ledger with a copy taken during the run, so the ledger changed after the reads. The block below is that file unchanged except that the home folder is written `~`. + +```text + +== project one: git.remote set to origin and committed on main == +branch: main, commit: ee059b8 +git show HEAD:baley.toml +[project] +id = "af645d0f-a096-41c5-84c3-2da2d85ae4ca" +name = "one" + +[git] +remote = "origin" +on_protected = "refuse" +first anchor, before the copy + +== anchor == +request 3be8b81b-74d8-4335-b3a8-5597230fd9b0 +anchored sequence 20 (353ab0b06a7b81ffe5de3e411c2e584bf64e41f38f99ef8023b7e8c4f1ee0c03) as baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/20 on origin, confirmed at 2026-10-08T19:23:06.607744120Z +exit status: 0 + +== copy of baley.db taken with SQLite's backup API, mode 0600 == +-rw------- 1 john john 368640 Oct 8 15:23 ~/.local/share/baley-live/results/baley-copy.db +project one's highest seq in the copy: 23 +second anchor, after the copy + +== anchor == +request f1f20798-7070-40de-9550-d2176e5a4f37 +anchored sequence 23 (0537dbd0152bdf8ec36a8b755da9ca187598df2277dd8f95e5e7625a1c8a42b2) as baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/23 on origin, confirmed at 2026-10-08T19:23:06.805343329Z +exit status: 0 +no anchor names a sequence above the copy's head yet, so third anchor + +== anchor == +request a715fba7-2525-4332-b97d-896c15d00455 +anchored sequence 26 (799d92cea973671f226feb7df96ff9de42b8e4428ab049728bd9f1533df05b62) as baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/26 on origin, confirmed at 2026-10-08T19:23:06.992872916Z +exit status: 0 + +== the remote's anchor tags for project one, and project one's highest anchor.pushed sequence in the ledger == +baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/20 +baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/23 +baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/26 +highest anchor.pushed sequence in the ledger: 28 +highest sequence an anchor tag names: 26 +copy's head: 23 + +== restore: baley.db replaced by the copy, its -wal and -shm files removed, mode 0600 == +total 372 +-rw------- 1 john john 368640 Oct 8 15:23 baley.db +-rw------- 1 john john 0 Oct 8 15:03 baley.db.maintenance +-rw------- 1 john john 0 Oct 8 15:03 baley.db.writer +-rw------- 1 john john 29 Oct 8 15:03 keys.env +-rw------- 1 john john 158 Oct 8 15:03 notebook.ipynb +-rw------- 1 john john 5 Oct 8 15:03 seed.txt +project one's highest seq in the restored ledger: 23 + +== verify == +remote origin: anchor at 26 799d92cea973671f226feb7df96ff9de42b8e4428ab049728bd9f1533df05b62 +checked at 2026-10-08T19:23:07.096504458Z +chain head 23 0537dbd0152bdf8ec36a8b755da9ca187598df2277dd8f95e5e7625a1c8a42b2 +truncated: remote anchor at 26, chain ends at 23 +bodies checked 0, tombstones 0 +local anchor 20 353ab0b06a7b81ffe5de3e411c2e584bf64e41f38f99ef8023b7e8c4f1ee0c03 as baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/20, confirmed at 2026-10-08T19:23:06.607744120Z +local anchor row: older than the remote anchor +exit status: 1 + +== acknowledge-restore == +request 58f0081a-d6d6-433f-8883-7746e4981d8d +acknowledged: the restored chain is accepted behind remote anchor baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/26 (26 799d92cea973671f226feb7df96ff9de42b8e4428ab049728bd9f1533df05b62); anchoring may resume +warning: purges recorded in history this copy lacks may be missing, whether they came after the copy was taken or on a branch it replaced, and bodies purged there may have reappeared +Every secret that was in such a body must be rotated, as after any purge: a secret that already reached a review provider, an export or any other system must still be rotated. +To repeat the purges you know of, run baley purge ... --reason for each project, from records kept outside the store. +Review first any hash with nothing left to release, because one such hash refuses the whole request, and check purge's "kept ... because another reference still requires it" lines for bodies another reference still holds. +exit status: 0 + +== acknowledge-restore == +request 610ea977-0bcd-4eb9-a7bb-9e35ad691b8e +baley: not acknowledged: already acknowledged +exit status: 1 + +== verify == +remote origin: anchor at 26 799d92cea973671f226feb7df96ff9de42b8e4428ab049728bd9f1533df05b62 +checked at 2026-10-08T19:23:07.457372903Z +chain head 26 dd6790c0b9f3b39e1e087d99a06fe6f43e561d9bcf9d3cfa81969f4a104d63e1 +acknowledged at 24: restore 23 0537dbd0152bdf8ec36a8b755da9ca187598df2277dd8f95e5e7625a1c8a42b2 behind remote anchor 26 +unanchored sequences 25 to 26 +warning: purges recorded in history this copy lacks may be missing, whether they came after the copy was taken or on a branch it replaced, and bodies purged there may have reappeared +Every secret that was in such a body must be rotated, as after any purge: a secret that already reached a review provider, an export or any other system must still be rotated. +To repeat the purges you know of, run baley purge ... --reason for each project, from records kept outside the store. +Review first any hash with nothing left to release, because one such hash refuses the whole request, and check purge's "kept ... because another reference still requires it" lines for bodies another reference still holds. +bodies checked 0, tombstones 0 +local anchor 20 353ab0b06a7b81ffe5de3e411c2e584bf64e41f38f99ef8023b7e8c4f1ee0c03 as baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/20, confirmed at 2026-10-08T19:23:06.607744120Z +local anchor row: older than the remote anchor +exit status: 0 + +== verify --local-only af645d0f-a096-41c5-84c3-2da2d85ae4ca == +local only +checked at 2026-10-08T19:23:07.460645567Z +chain head 26 dd6790c0b9f3b39e1e087d99a06fe6f43e561d9bcf9d3cfa81969f4a104d63e1 +not compared with a remote anchor +unanchored sequences 1 to 26 +work unanchored since 2026-10-08T19:03:29.868970972Z +acknowledged restore at 24: 23 0537dbd0152bdf8ec36a8b755da9ca187598df2277dd8f95e5e7625a1c8a42b2 behind 26 799d92cea973671f226feb7df96ff9de42b8e4428ab049728bd9f1533df05b62 +warning: purges recorded in history this copy lacks may be missing, whether they came after the copy was taken or on a branch it replaced, and bodies purged there may have reappeared +Every secret that was in such a body must be rotated, as after any purge: a secret that already reached a review provider, an export or any other system must still be rotated. +To repeat the purges you know of, run baley purge ... --reason for each project, from records kept outside the store. +Review first any hash with nothing left to release, because one such hash refuses the whole request, and check purge's "kept ... because another reference still requires it" lines for bodies another reference still holds. +bodies checked 0, tombstones 0 +local anchor 20 353ab0b06a7b81ffe5de3e411c2e584bf64e41f38f99ef8023b7e8c4f1ee0c03 as baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/20, confirmed at 2026-10-08T19:23:06.607744120Z +local anchor row: not compared with a remote anchor +exit status: 0 + +== doctor == +epoch 1 +integrity: ok +database 368640 bytes, log 0 bytes +project 7173f106-d4b8-4512-9cd0-58e4364f28be (two): not checked against a remote from this directory +chain head 6 12eb0e66ece17b59cc7f06bc382507243fb54de87394489a62538972603f8c13 +not compared with a remote anchor +unanchored sequences 1 to 6 +work unanchored since 2026-10-08T19:03:29.948081320Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T19:03:29.948081320Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 6 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project af645d0f-a096-41c5-84c3-2da2d85ae4ca (one): anchor at 26 799d92cea973671f226feb7df96ff9de42b8e4428ab049728bd9f1533df05b62 +chain head 26 dd6790c0b9f3b39e1e087d99a06fe6f43e561d9bcf9d3cfa81969f4a104d63e1 +acknowledged at 24: restore 23 0537dbd0152bdf8ec36a8b755da9ca187598df2277dd8f95e5e7625a1c8a42b2 behind remote anchor 26 +unanchored sequences 25 to 26 +warning: purges recorded in history this copy lacks may be missing, whether they came after the copy was taken or on a branch it replaced, and bodies purged there may have reappeared +Every secret that was in such a body must be rotated, as after any purge: a secret that already reached a review provider, an export or any other system must still be rotated. +To repeat the purges you know of, run baley purge ... --reason for each project, from records kept outside the store. +Review first any hash with nothing left to release, because one such hash refuses the whole request, and check purge's "kept ... because another reference still requires it" lines for bodies another reference still holds. +bodies checked 0, tombstones 0 +local anchor 20 353ab0b06a7b81ffe5de3e411c2e584bf64e41f38f99ef8023b7e8c4f1ee0c03 as baley-anchor/af645d0f-a096-41c5-84c3-2da2d85ae4ca/20, confirmed at 2026-10-08T19:23:06.607744120Z +local anchor row: older than the remote anchor +unanchored age: none +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 26 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project user (per-user records): not checked against a remote from this directory +chain head 19 3afdf9ec9f0aa6d873cea370fcd56284fa432796ae8a608c2e00d20f52c21c3e +not compared with a remote anchor +unanchored sequences 1 to 19 +work unanchored since 2026-10-08T19:11:14.983606568Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T19:11:14.983606568Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 19 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +exit status: 0 +``` + +## Post-run reads + +The output of `sh live-claude-reads.sh`, as the coding agent that drove the run saved it in results/reads.txt. The block below is that file unchanged except that the home folder is written `~`. + +```text + +== pins == +date: 2026-10-08T19:03:29Z +platform: Linux 7.2.9-1-cachyos +bwrap: /usr/bin/bwrap +socat: /usr/bin/socat +sqlite3: /usr/bin/sqlite3 +jq: /usr/bin/jq +pwsh: absent +git: /usr/bin/git +telemetry-switch: unset +checkout-commit: 7c8ca4c39249d8135ba56dab62941d7bc7483964 +checkout-status: clean +binary: baley 0.1.0 +binary-sha256: 856120f0470c1d05d0f6d76a8e7a6fef4c1aae05fd22eb572f66607bcb519165 +begin real-home ~/.local/share/crenshawdev/baley +~/.local/share/crenshawdev/baley/baley.db f 286720 2026-10-02 12:28:05.0100125140 +~/.local/share/crenshawdev/baley/baley.db.maintenance f 0 2026-10-02 12:25:03.5899264730 +~/.local/share/crenshawdev/baley/baley.db.writer f 0 2026-10-02 12:25:03.5899264730 +~/.local/share/crenshawdev/baley d 86 2026-10-05 09:42:21.6045176040 +end real-home +begin real-config ~/.config/crenshawdev/baley +~/.config/crenshawdev/baley d 16 2026-09-30 16:08:55.8750781170 +~/.config/crenshawdev/baley/keys.env f 183 2026-09-30 16:08:55.8767744790 +end real-config +claude-command: ~/.local/bin/claude +claude-command-resolved: ~/.local/share/claude/versions/2.1.294 +claude-launcher: link to ~/.local/share/claude/versions/2.1.294 +claude-launcher-resolved: ~/.local/share/claude/versions/2.1.294 +begin real-claude-versions ~/.local/share/claude/versions +~/.local/share/claude/versions/2.1.293 f 252755128 2026-10-07 14:11:00.2344368200 +~/.local/share/claude/versions/2.1.294 f 252755128 2026-10-08 07:41:15.2608733970 +~/.local/share/claude/versions d 28 2026-10-08 13:33:08.2187588350 +end real-claude-versions +project-one-id: af645d0f-a096-41c5-84c3-2da2d85ae4ca +project-two-id: 7173f106-d4b8-4512-9cd0-58e4364f28be +project-fork-id: af645d0f-a096-41c5-84c3-2da2d85ae4ca + +== verify --local-only af645d0f-a096-41c5-84c3-2da2d85ae4ca == +local only +checked at 2026-10-08T19:22:27.617420409Z +chain head 18 753b5ac75010a3b0f6af1ac77aa648460bf30c6c453d46cb1125b64caadd2ffd +not compared with a remote anchor +unanchored sequences 1 to 18 +work unanchored since 2026-10-08T19:03:29.868970972Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 + +== verify --views af645d0f-a096-41c5-84c3-2da2d85ae4ca == +views checked at sequence 18 +no differences +exit status: 0 + +== verify --local-only 7173f106-d4b8-4512-9cd0-58e4364f28be == +local only +checked at 2026-10-08T19:22:27.643138640Z +chain head 6 12eb0e66ece17b59cc7f06bc382507243fb54de87394489a62538972603f8c13 +not compared with a remote anchor +unanchored sequences 1 to 6 +work unanchored since 2026-10-08T19:03:29.948081320Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 + +== verify --views 7173f106-d4b8-4512-9cd0-58e4364f28be == +views checked at sequence 6 +no differences +exit status: 0 + +== verify --local-only user == +local only +checked at 2026-10-08T19:22:27.664847156Z +chain head 19 3afdf9ec9f0aa6d873cea370fcd56284fa432796ae8a608c2e00d20f52c21c3e +not compared with a remote anchor +unanchored sequences 1 to 19 +work unanchored since 2026-10-08T19:11:14.983606568Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +exit status: 0 + +== verify --views user == +views checked at sequence 19 +no differences +exit status: 0 + +== doctor == +epoch 1 +integrity: ok +database 368640 bytes, log 0 bytes +project 7173f106-d4b8-4512-9cd0-58e4364f28be (two): not checked against a remote from this directory +chain head 6 12eb0e66ece17b59cc7f06bc382507243fb54de87394489a62538972603f8c13 +not compared with a remote anchor +unanchored sequences 1 to 6 +work unanchored since 2026-10-08T19:03:29.948081320Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T19:03:29.948081320Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 6 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project af645d0f-a096-41c5-84c3-2da2d85ae4ca (one): not checked against a remote from this directory +chain head 18 753b5ac75010a3b0f6af1ac77aa648460bf30c6c453d46cb1125b64caadd2ffd +not compared with a remote anchor +unanchored sequences 1 to 18 +work unanchored since 2026-10-08T19:03:29.868970972Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T19:03:29.868970972Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 18 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +project user (per-user records): not checked against a remote from this directory +chain head 19 3afdf9ec9f0aa6d873cea370fcd56284fa432796ae8a608c2e00d20f52c21c3e +not compared with a remote anchor +unanchored sequences 1 to 19 +work unanchored since 2026-10-08T19:11:14.983606568Z +bodies checked 0, tombstones 0 +local anchor row: not compared with a remote anchor +unanchored age: since 2026-10-08T19:11:14.983606568Z +view set 7, binary 7 +view capture: live 1, binary 1 +view checkout: live 1, binary 1 +view claim_scope: live 1, binary 1 +view guard: live 1, binary 1 +view guard_policy: live 1, binary 1 +view model_catalog: live 1, binary 1 +view policy: live 1, binary 1 +view request: live 2, binary 2 +views checked at sequence 19 +no differences +claims: 0 active, 0 interrupted, 0 awaiting owner +exit status: 0 + +== streams: events per project and stream, lowest and highest stream_version == + project_id stream events lowest highest +------------------------------------ ---------------------- ------ ------ ------- +7173f106-d4b8-4512-9cd0-58e4364f28be command/checkout.admit 1 1 1 +7173f106-d4b8-4512-9cd0-58e4364f28be command/policy.record 1 1 1 +7173f106-d4b8-4512-9cd0-58e4364f28be command/project.init 1 1 1 +7173f106-d4b8-4512-9cd0-58e4364f28be project 3 1 3 +af645d0f-a096-41c5-84c3-2da2d85ae4ca capture 5 1 5 +af645d0f-a096-41c5-84c3-2da2d85ae4ca command/capture.record 5 1 5 +af645d0f-a096-41c5-84c3-2da2d85ae4ca command/checkout.admit 1 1 1 +af645d0f-a096-41c5-84c3-2da2d85ae4ca command/policy.record 2 1 2 +af645d0f-a096-41c5-84c3-2da2d85ae4ca command/project.init 1 1 1 +af645d0f-a096-41c5-84c3-2da2d85ae4ca project 4 1 4 +user command/guard.record 9 1 9 +user guard 10 1 10 + +== streams that do not start at 1 or whose count differs from the span == +(no rows above means every stream is complete) + +== server callers: each session with the project ids of the events it wrote == + baley_session project_directory working_directory host_session client_version project_id first_seq last_seq events +------------------------------------ --------------------------------------------------- --------------------------------------------------- ------------------------------------ -------------- ------------------------------------ --------- -------- ------ +5164dca1-d5c3-4352-a82a-392b723cc27b ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one 4c01fab1-79a7-490c-8ce6-4bee52f7f0c1 2.1.294 af645d0f-a096-41c5-84c3-2da2d85ae4ca 7 16 6 +4bd8ebec-fc24-437b-87e8-fcf18acf2763 ~/.local/share/baley-live/projects/one/sub ~/.local/share/baley-live/projects/one/sub 775c3b69-ad49-49c1-954a-e68b75546c17 2.1.294 af645d0f-a096-41c5-84c3-2da2d85ae4ca 11 18 6 +project ids in pins.txt: one=af645d0f-a096-41c5-84c3-2da2d85ae4ca two=7173f106-d4b8-4512-9cd0-58e4364f28be fork=af645d0f-a096-41c5-84c3-2da2d85ae4ca + +== hook callers in seq order == +project_id seq host_session working_directory project_directory call_id +---------- --- ------------------------------------ ----------------------------------------------- ----------------------------------------------- ------------------------------ +user 1 4c01fab1-79a7-490c-8ce6-4bee52f7f0c1 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01SPVurHqetRvWPLZidDdt69 +user 2 4c01fab1-79a7-490c-8ce6-4bee52f7f0c1 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01SPVurHqetRvWPLZidDdt69 +user 3 4c01fab1-79a7-490c-8ce6-4bee52f7f0c1 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01SPVurHqetRvWPLZidDdt69 +user 4 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_016PshNdnDrxdW7Mt1D25nUA +user 5 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_016PshNdnDrxdW7Mt1D25nUA +user 6 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01NVfGqehy5tsBX6GANPTX4C +user 7 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01NVfGqehy5tsBX6GANPTX4C +user 8 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01QC99GnbWTK97gFDHXfDRdC +user 9 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01QC99GnbWTK97gFDHXfDRdC +user 10 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_019JHmV6xPW2eGaPodSpCtQ2 +user 11 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_019JHmV6xPW2eGaPodSpCtQ2 +user 12 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Co7ykDj6k98DNoXve2RWtQ +user 13 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01Co7ykDj6k98DNoXve2RWtQ +user 14 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01XMQy9LLG5WxuGH1mbr9F2K +user 15 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01XMQy9LLG5WxuGH1mbr9F2K +user 16 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_012CoghwRCTRRukVxbubfoJn +user 17 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_012CoghwRCTRRukVxbubfoJn +user 18 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01DrFQrUjapbuivS6SLGoqgb +user 19 2d4b5641-b8d7-4ee7-8658-6e3efa8d3c93 ~/.local/share/baley-live/projects/one ~/.local/share/baley-live/projects/one toolu_01DrFQrUjapbuivS6SLGoqgb + +== captures: capture.recorded events per request_id with the caller's session and instruction evidence == + project_id seq request_id events_for_request baley_session instructions kind bytes text +------------------------------------ --- ------------------------------------ ------------------ ------------------------------------ ------------ ---- ----- ------------------------- +af645d0f-a096-41c5-84c3-2da2d85ae4ca 9 12120000-0000-4000-8000-000000000001 1 5164dca1-d5c3-4352-a82a-392b723cc27b note 25 smoke note from session A +af645d0f-a096-41c5-84c3-2da2d85ae4ca 11 12120000-0000-4000-8000-000000000033 1 4bd8ebec-fc24-437b-87e8-fcf18acf2763 note 12 tools B main +af645d0f-a096-41c5-84c3-2da2d85ae4ca 13 12120000-0000-4000-8000-000000000004 1 4bd8ebec-fc24-437b-87e8-fcf18acf2763 note 9 overlap B +af645d0f-a096-41c5-84c3-2da2d85ae4ca 15 12120000-0000-4000-8000-000000000002 1 5164dca1-d5c3-4352-a82a-392b723cc27b note 9 overlap A +af645d0f-a096-41c5-84c3-2da2d85ae4ca 17 12120000-0000-4000-8000-000000000034 1 4bd8ebec-fc24-437b-87e8-fcf18acf2763 note 16 tools B subagent + +== stored capture bodies (text above 4,096 bytes): byte count and SHA-256 after zstd -d == +large.txt on disk: 33000 bytes, sha256 6754341a445caf2f681748567772759cdf41bf0865425e360da5fcfd8dc1c0d4 + +== guard answers by decision, with call ids == +decision seq tool verb branch call_id +-------- --- ---- ------ ------ ------------------------------ +deny 2 Bash commit main toolu_01SPVurHqetRvWPLZidDdt69 +deny 4 Grep toolu_016PshNdnDrxdW7Mt1D25nUA +deny 6 Grep toolu_01NVfGqehy5tsBX6GANPTX4C +deny 8 Glob toolu_01QC99GnbWTK97gFDHXfDRdC +deny 10 Glob toolu_019JHmV6xPW2eGaPodSpCtQ2 +deny 12 Grep toolu_01Co7ykDj6k98DNoXve2RWtQ +deny 14 Grep toolu_01XMQy9LLG5WxuGH1mbr9F2K +deny 16 Glob toolu_012CoghwRCTRRukVxbubfoJn +deny 18 Glob toolu_01DrFQrUjapbuivS6SLGoqgb + +== server stderr: start line, exit checkpoint lines and any abandoned-drain line, per file == +-- ~/.local/share/baley-live/results/server-stderr/1138602.log +start 2026-10-08T19:08:36Z pid 1138602 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/1143098.log +start 2026-10-08T19:11:42Z pid 1143098 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/1155330.log +start 2026-10-08T19:16:12Z pid 1155330 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file +-- ~/.local/share/baley-live/results/server-stderr/1223804.log +start 2026-10-08T19:19:35Z pid 1223804 +exit checkpoint lines: 1 +baley: exit checkpoint complete, every logged change is in the database file + +== hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard) == +Bash calls=1 highest_ms=49 median_ms=49 highest_wrapper_ms=58 highest_overhead_ms=8 +Edit calls=1 highest_ms=1 median_ms=1 highest_wrapper_ms=12 highest_overhead_ms=10 +Glob calls=4 highest_ms=5 median_ms=5 highest_wrapper_ms=14 highest_overhead_ms=9 +Grep calls=4 highest_ms=5 median_ms=5 highest_wrapper_ms=16 highest_overhead_ms=10 +NotebookEdit calls=1 highest_ms=1 median_ms=1 highest_wrapper_ms=12 highest_overhead_ms=10 +Read calls=4 highest_ms=2 median_ms=1 highest_wrapper_ms=13 highest_overhead_ms=10 +Write calls=1 highest_ms=1 median_ms=1 highest_wrapper_ms=10 highest_overhead_ms=8 + +== hook timing: guard decisions by tool == +Bash deny 1 +Edit none 1 +Glob deny 4 +Grep deny 4 +NotebookEdit none 1 +Read none 4 +Write none 1 + +== hook timing: finished calls at or above 10,000 ms (the guard alone, or the whole wrapper) == +(no lines above means none) + +== hook timing: tool_use_id seen more than once == +(no lines above means none) + +== hook timing: calls that started and did not finish (killed at the timeout, or still running) == +(no lines above means every started call finished) + +== real-home against pins.txt == +4c4 +< ~/.local/share/crenshawdev/baley d 86 2026-10-05 09:42:21.6045176040 +--- +> ~/.local/share/crenshawdev/baley d 86 2026-10-08 15:06:08.4784947390 +~/.local/share/crenshawdev/baley: DIFFERENT (left: pins.txt, right: now) + +== real-config against pins.txt == +~/.config/crenshawdev/baley: no difference + +== real-claude-versions against pins.txt == +~/.local/share/claude/versions: no difference + +== claude command against pins.txt == +no difference + +== ~/.local/bin/claude link against pins.txt == +no difference + +== where the claude command and the launcher resolve now == +the claude command resolves outside the disposable root (~/.local/share/claude/versions/2.1.294) +the launcher resolves outside the disposable root (~/.local/share/claude/versions/2.1.294) + +== entries other programs wrote under the exported variables (top level of the disposable data and config roots, other than crenshawdev; evidence only) == +-- ~/.local/share/baley-live/data +applications d +GitKrakenCLI d +gk d +terminus d +-- ~/.local/share/baley-live/config +anthropic d +mimeapps.list f +``` diff --git a/spikes/host-matrix/live-claude-reads.sh b/spikes/host-matrix/live-claude-reads.sh new file mode 100644 index 00000000..321f4501 --- /dev/null +++ b/spikes/host-matrix/live-claude-reads.sh @@ -0,0 +1,235 @@ +#!/bin/sh +# Post-run reads for the live Claude Code qualification. It reads what live-claude.sh prepared and the +# sessions left behind, opens the ledger read-only and changes nothing but a scratch folder it removes. +# The output is pasted into the post-run rows of the observation sheet: +# sh live-claude-reads.sh > /results/reads.txt +set -u + +case "${HOME:-}" in + /?*) ;; + *) echo "HOME must be an absolute path" >&2; exit 1 ;; +esac + +# BALEY_HOME would make home and config one folder, so a shell that sets it (an empty value included) +# is refused, as live-claude.sh refuses it. The disposable folders are the only ones these reads use. +if [ -n "${BALEY_HOME+set}" ]; then + echo "BALEY_HOME is set. Unset it: it makes home and config one folder, and these reads use two." >&2 + exit 1 +fi + +ROOT="$HOME/.local/share/baley-live" +DATA="$ROOT/data" +CONF="$ROOT/config" +HOMEF="$DATA/crenshawdev/baley" +BIN="$ROOT/bin/baley" +OUT="$ROOT/results" +DB="$HOMEF/baley.db" + +if [ ! -d "$ROOT" ]; then + echo "missing $ROOT: run live-claude.sh first, and run these reads before running it again, since it clears the root" >&2 + exit 1 +fi +if [ ! -f "$DB" ]; then + echo "missing the ledger $DB: no session has written to it" >&2 + exit 1 +fi +for TOOL in sqlite3 jq sha256sum xxd; do + command -v "$TOOL" >/dev/null 2>&1 || { echo "$TOOL is required and is not on PATH" >&2; exit 1; } +done + +# Where Claude Code keeps its installed versions, resolved from the environment this script was started in, +# the same way live-claude.sh resolved it. +case "${XDG_DATA_HOME:-}" in /*) REAL_VERSIONS="$XDG_DATA_HOME/claude/versions" ;; *) REAL_VERSIONS="$HOME/.local/share/claude/versions" ;; esac + +# From here on the baley commands see the disposable folders only. +XDG_DATA_HOME="$DATA" +XDG_CONFIG_HOME="$CONF" +export XDG_DATA_HOME XDG_CONFIG_HOME + +# Scratch files live in a folder this run creates and owns, so a file or link left at a predictable name +# in results can never be written through. +WORK=$(mktemp -d "$OUT/.reads.XXXXXX") || { echo "could not create a scratch folder in $OUT" >&2; exit 1; } +trap 'rm -rf "$WORK"' EXIT + +pin() { + sed -n "s/^$1: //p" "$OUT/pins.txt" 2>/dev/null +} +ONE=$(pin project-one-id) +TWO=$(pin project-two-id) +LARGE="$ROOT/projects/one/fixtures/large.txt" + +# The ledger is opened read-only, so nothing here can change what it shows. +q() { + sqlite3 -readonly -header -column "$DB" "$1" +} +heading() { + printf '\n== %s ==\n' "$1" +} +# Runs a baley command and prints its exit status after it, spelled as the binary spells it. +status_of() { + heading "$*" + ( cd "$ROOT" && "$BIN" "$@" ) 2>&1 + echo "exit status: $?" +} + +heading "pins" +cat "$OUT/pins.txt" + +for ID in "$ONE" "$TWO" user; do + status_of verify --local-only "$ID" + status_of verify --views "$ID" +done +status_of doctor + +heading "streams: events per project and stream, lowest and highest stream_version" +q "select project_id, stream, count(*) as events, min(stream_version) as lowest, max(stream_version) as highest from event group by project_id, stream order by project_id, stream" +heading "streams that do not start at 1 or whose count differs from the span" +q "select project_id, stream, count(*) as events, min(stream_version) as lowest, max(stream_version) as highest from event group by project_id, stream having min(stream_version) <> 1 or count(*) <> max(stream_version) - min(stream_version) + 1" +echo "(no rows above means every stream is complete)" + +heading "server callers: each session with the project ids of the events it wrote" +q "select json_extract(caller, '\$.baley_session') as baley_session, json_extract(caller, '\$.project_directory') as project_directory, json_extract(caller, '\$.working_directory') as working_directory, json_extract(caller, '\$.host_session') as host_session, json_extract(caller, '\$.client_version') as client_version, project_id, min(seq) as first_seq, max(seq) as last_seq, count(*) as events from event where json_extract(caller, '\$.form') = 'server' group by 1, 2, 3, 4, 5, project_id order by min(recorded_at)" +echo "project ids in pins.txt: one=$ONE two=$TWO fork=$(pin project-fork-id)" + +heading "hook callers in seq order" +q "select project_id, seq, json_extract(caller, '\$.host_session') as host_session, json_extract(caller, '\$.working_directory') as working_directory, json_extract(caller, '\$.project_directory') as project_directory, json_extract(caller, '\$.call.text') as call_id from event where json_extract(caller, '\$.form') = 'hook' order by project_id, seq" + +heading "captures: capture.recorded events per request_id with the caller's session and instruction evidence" +q "select project_id, seq, request_id, count(*) over (partition by project_id, request_id) as events_for_request, json_extract(caller, '\$.baley_session') as baley_session, json_extract(caller, '\$.instructions') as instructions, json_extract(payload_json, '\$.kind') as kind, json_extract(payload_json, '\$.bytes') as bytes, json_extract(payload_json, '\$.text') as text from event where type = 'capture.recorded' order by project_id, seq" + +heading "stored capture bodies (text above 4,096 bytes): byte count and SHA-256 after zstd -d" +LARGE_SUM=$(sha256sum "$LARGE" 2>/dev/null | cut -d' ' -f1) +echo "large.txt on disk: $(wc -c < "$LARGE" 2>/dev/null) bytes, sha256 $LARGE_SUM" +# The store keeps a body zstd-compressed, so hashing the stored bytes would never match the text sent. +sqlite3 -readonly "$DB" "select project_id, seq, json_extract(payload_json, '\$.body.payload') from event where type = 'capture.recorded' and json_extract(payload_json, '\$.body.payload') is not null order by project_id, seq" | while IFS='|' read -r PROJECT SEQ HASH; do + BYTES=$(sqlite3 -readonly "$DB" "select bytes from payload where hash = x'$HASH'") + ENCODING=$(sqlite3 -readonly "$DB" "select encoding from payload where hash = x'$HASH'") + STORED=$(sqlite3 -readonly "$DB" "select length(body) from payload where hash = x'$HASH'") + if [ "$ENCODING" = zstd ] && command -v zstd >/dev/null 2>&1; then + SUM=$(sqlite3 -readonly "$DB" "select hex(body) from payload where hash = x'$HASH'" | xxd -r -p | zstd -dc | sha256sum | cut -d' ' -f1) + LEN=$(sqlite3 -readonly "$DB" "select hex(body) from payload where hash = x'$HASH'" | xxd -r -p | zstd -dc | wc -c) + SAME="different from large.txt" + [ "$SUM" = "$LARGE_SUM" ] && SAME="same as large.txt" + echo "$PROJECT seq $SEQ payload $HASH: $BYTES bytes recorded, stored $STORED bytes ($ENCODING), $LEN bytes after zstd -d, sha256 $SUM ($SAME)" + else + echo "$PROJECT seq $SEQ payload $HASH: $BYTES bytes recorded, stored $STORED bytes ($ENCODING), sha256 not checked (the stored body is compressed and zstd is not on PATH)" + fi +done + +heading "guard answers by decision, with call ids" +q "select json_extract(payload_json, '\$.outcome') as decision, seq, json_extract(payload_json, '\$.tool') as tool, json_extract(payload_json, '\$.verb') as verb, json_extract(payload_json, '\$.branch') as branch, json_extract(payload_json, '\$.call') as call_id from event where type = 'guard.answered' order by decision, seq" + +heading "server stderr: start line, exit checkpoint lines and any abandoned-drain line, per file" +for FILE in "$OUT"/server-stderr/*; do + [ -f "$FILE" ] || { echo "no server stderr files"; break; } + echo "-- $FILE" + sed -n '1p' "$FILE" + echo "exit checkpoint lines: $(grep -c '^baley: exit checkpoint' "$FILE")" + grep '^baley: exit checkpoint' "$FILE" + grep "^baley: work still open after" "$FILE" +done + +heading "hook timing: calls per tool with the highest and median elapsed milliseconds (the guard alone), the highest wrapper milliseconds and the highest overhead (wrapper minus guard)" +TIMING="$OUT/hook-timing.jsonl" +STARTS="$OUT/hook-starts.jsonl" +if [ -s "$TIMING" ]; then + jq -sr 'group_by(.tool_name)[] | (map(.elapsed_ms) | sort) as $e | (map(.wrapper_ms // empty) | sort) as $w | (map(.overhead_ms // empty) | sort) as $o | "\(.[0].tool_name) calls=\(length) highest_ms=\($e[-1]) median_ms=\($e[(length / 2 | floor)]) highest_wrapper_ms=\(if ($w | length) > 0 then $w[-1] else "n/a" end) highest_overhead_ms=\(if ($o | length) > 0 then $o[-1] else "n/a" end)"' "$TIMING" + heading "hook timing: guard decisions by tool" + jq -sr 'group_by([.tool_name, (.permissionDecision // "none")])[] | "\(.[0].tool_name) \(.[0].permissionDecision // "none") \(length)"' "$TIMING" + heading "hook timing: finished calls at or above 10,000 ms (the guard alone, or the whole wrapper)" + jq -c 'select(.elapsed_ms >= 10000 or (.wrapper_ms // 0) >= 10000)' "$TIMING" + echo "(no lines above means none)" + heading "hook timing: tool_use_id seen more than once" + jq -sr 'group_by(.tool_use_id)[] | select(length > 1 and .[0].tool_use_id != "") | "\(.[0].tool_use_id) seen \(length) times"' "$TIMING" + echo "(no lines above means none)" +else + echo "no hook-timing.jsonl: no hook call finished through the wrapper" +fi +# A call the host kills at its timeout never reaches the line above, so it shows only here. +heading "hook timing: calls that started and did not finish (killed at the timeout, or still running)" +if [ -s "$STARTS" ]; then + FINISHED="$TIMING" + [ -s "$TIMING" ] || { : > "$WORK/none"; FINISHED="$WORK/none"; } + jq -c --slurpfile finished "$FINISHED" 'select(.call as $c | ($finished | map(.call) | index($c)) == null)' "$STARTS" + echo "(no lines above means every started call finished)" +else + echo "no hook-starts.jsonl: no hook call went through the wrapper" +fi + +# The owner's real Baley folders, listed again with the same command live-claude.sh used. +list_real() { + if [ -d "$1" ]; then + find "$1" -printf '%p %y %s %TY-%Tm-%Td %TH:%TM:%TS\n' | sort + else + echo absent + fi +} +for KIND in real-home real-config real-claude-versions; do + heading "$KIND against pins.txt" + FOLDER=$(sed -n "s/^begin $KIND //p" "$OUT/pins.txt") + sed -n "/^begin $KIND /,/^end $KIND\$/p" "$OUT/pins.txt" | sed '1d;$d' > "$WORK/before-$KIND" + list_real "$FOLDER" > "$WORK/after-$KIND" + if diff "$WORK/before-$KIND" "$WORK/after-$KIND"; then + echo "$FOLDER: no difference" + else + echo "$FOLDER: DIFFERENT (left: pins.txt, right: now)" + fi +done +# The owner's claude command and launcher, compared with what pins.txt recorded before the run. The last +# lines say where each resolves now. These sections print evidence for the post-run row and judge nothing. +compare() { + if diff "$1" "$2"; then + echo "no difference" + else + echo "DIFFERENT (left: pins.txt, right: now)" + fi +} +under_root() { + case "$1" in + "$ROOT"|"$ROOT"/*|"$ROOT_RESOLVED"|"$ROOT_RESOLVED"/*) return 0 ;; + esac + return 1 +} +ROOT_RESOLVED="$(readlink -f "$HOME" 2>/dev/null || echo "$HOME")/.local/share/baley-live" +NOW_FOUND=$(command -v claude 2>/dev/null || true) +NOW_RESOLVED="" +case "$NOW_FOUND" in /*) NOW_RESOLVED=$(readlink -f "$NOW_FOUND" 2>/dev/null || true) ;; esac +LAUNCHER="$HOME/.local/bin/claude" +NOW_LAUNCHER_RESOLVED="" +if [ -L "$LAUNCHER" ]; then + NOW_LAUNCHER_TEXT="link to $(readlink "$LAUNCHER")" +elif [ -e "$LAUNCHER" ]; then + NOW_LAUNCHER_TEXT="not a link" +else + NOW_LAUNCHER_TEXT="absent" +fi +[ -e "$LAUNCHER" ] && NOW_LAUNCHER_RESOLVED=$(readlink -f "$LAUNCHER" 2>/dev/null || true) + +heading "claude command against pins.txt" +{ echo "command: $(pin claude-command)"; echo "resolved: $(pin claude-command-resolved)"; } > "$WORK/before-command" +{ echo "command: ${NOW_FOUND:-none}"; echo "resolved: ${NOW_RESOLVED:-none}"; } > "$WORK/after-command" +compare "$WORK/before-command" "$WORK/after-command" + +heading "~/.local/bin/claude link against pins.txt" +{ echo "launcher: $(pin claude-launcher)"; echo "resolved: $(pin claude-launcher-resolved)"; } > "$WORK/before-launcher" +{ echo "launcher: $NOW_LAUNCHER_TEXT"; echo "resolved: ${NOW_LAUNCHER_RESOLVED:-none}"; } > "$WORK/after-launcher" +compare "$WORK/before-launcher" "$WORK/after-launcher" + +heading "where the claude command and the launcher resolve now" +where() { + if under_root "$2"; then + echo "$1 resolves INSIDE the disposable root ($2)" + else + echo "$1 resolves outside the disposable root ($2)" + fi +} +where "the claude command" "$NOW_RESOLVED" +where "the launcher" "$NOW_LAUNCHER_RESOLVED" + +heading "entries other programs wrote under the exported variables (top level of the disposable data and config roots, other than crenshawdev; evidence only)" +for TREE in "$DATA" "$CONF"; do + echo "-- $TREE" + OTHERS=$(find "$TREE" -mindepth 1 -maxdepth 1 ! -name crenshawdev -printf '%f %y\n' 2>/dev/null | sort) + if [ -n "$OTHERS" ]; then echo "$OTHERS"; else echo "(none)"; fi +done +exit 0 diff --git a/spikes/host-matrix/live-claude-restore.sh b/spikes/host-matrix/live-claude-restore.sh new file mode 100644 index 00000000..6259d879 --- /dev/null +++ b/spikes/host-matrix/live-claude-restore.sh @@ -0,0 +1,177 @@ +#!/bin/sh +# Restore report for the live Claude Code qualification. It copies the disposable ledger, anchors project +# one on its disposable remote, restores the copy behind that anchor and prints each baley report that +# follows, every one with its exit status. It judges nothing: live-claude.md says what each section is +# expected to show, from design 0001. Run it once, after every session has exited and after the post-run +# reads are saved, because it changes the ledger: +# sh live-claude-restore.sh > /results/restore.txt +set -u + +if [ $# -ne 0 ]; then + echo "usage: sh live-claude-restore.sh (no arguments)" >&2 + exit 1 +fi +case "${HOME:-}" in + /?*) ;; + *) echo "HOME must be an absolute path" >&2; exit 1 ;; +esac + +# BALEY_HOME would make home and config one folder, so a shell that sets it (an empty value included) +# is refused, as live-claude.sh refuses it. +if [ -n "${BALEY_HOME+set}" ]; then + echo "BALEY_HOME is set. Unset it: it makes home and config one folder, and this run needs two." >&2 + exit 1 +fi + +# An inherited GIT_ variable can pick another repository, work tree, index or object store, so the commit +# below could land in the owner's files instead of the disposable repository. None of them reaches this +# script's git or baley commands. Only GIT_EXEC_PATH stays, because it says where git lives. +for NAME in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do + [ "$NAME" = GIT_EXEC_PATH ] || unset "$NAME" +done + +ROOT="$HOME/.local/share/baley-live" +DATA="$ROOT/data" +CONF="$ROOT/config" +HOMEF="$DATA/crenshawdev/baley" +BIN="$ROOT/bin/baley" +OUT="$ROOT/results" +DB="$HOMEF/baley.db" +ONE_DIR="$ROOT/projects/one" +REMOTE_DIR="$ROOT/remotes/one.git" +COPY="$OUT/baley-copy.db" + +if [ ! -d "$ROOT" ]; then + echo "missing $ROOT: run live-claude.sh and the sessions first" >&2 + exit 1 +fi +if [ ! -f "$DB" ]; then + echo "missing the ledger $DB: no session has written to it" >&2 + exit 1 +fi +# The reads come first, because this script changes the ledger and they describe it as the sessions left it. +if [ ! -f "$OUT/reads.txt" ]; then + echo "missing $OUT/reads.txt: save the post-run reads first (sh live-claude-reads.sh > $OUT/reads.txt), since this script changes the ledger" >&2 + exit 1 +fi +for PATH_NEEDED in "$BIN" "$ONE_DIR/baley.toml" "$REMOTE_DIR"; do + if [ ! -e "$PATH_NEEDED" ]; then + echo "missing $PATH_NEEDED" >&2 + exit 1 + fi +done +for TOOL in sqlite3 git awk ps; do + command -v "$TOOL" >/dev/null 2>&1 || { echo "$TOOL is required and is not on PATH" >&2; exit 1; } +done + +umask 077 +WORK=$(mktemp -d "$OUT/.restore.XXXXXX") || { echo "could not create a scratch folder in $OUT" >&2; exit 1; } +trap 'rm -rf "$WORK"' EXIT + +# The copy and the restore need Baley stopped (design 0001, "Copies of the store"). The process list is +# written to a file first, so this script's own command line is never part of what is searched. -ww keeps +# ps from cutting each line to an exported COLUMNS, which would hide the path from the search. +ps -ww -eo pid=,args= > "$WORK/processes" 2>/dev/null +if grep -F -- "$BIN" "$WORK/processes" > "$WORK/running"; then + echo "a running process names $BIN, so the ledger is not stopped:" >&2 + cat "$WORK/running" >&2 + echo "Close every session and wait for its server to exit, then run this again." >&2 + exit 1 +fi + +ONE=$(sed -n 's/^project-one-id: //p' "$OUT/pins.txt" 2>/dev/null) +if [ -z "$ONE" ]; then + echo "pins.txt names no project-one-id" >&2 + exit 1 +fi + +# From here on the baley commands see the disposable folders only. +XDG_DATA_HOME="$DATA" +XDG_CONFIG_HOME="$CONF" +export XDG_DATA_HOME XDG_CONFIG_HOME + +heading() { + printf '\n== %s ==\n' "$1" +} +# Runs a baley command from project one, as an owner would, and prints its exit status after it. +status_of() { + heading "$*" + ( cd "$ONE_DIR" && "$BIN" "$@" ) 2>&1 + echo "exit status: $?" +} +stop() { + echo "$1" >&2 + exit 1 +} +highest() { + sqlite3 -readonly "$1" "select coalesce(max(seq), 0) from event where project_id = '$ONE'" +} +# The highest sequence any anchor tag on the remote names for project one. +remote_highest() { + git --git-dir="$REMOTE_DIR" tag -l "baley-anchor/$ONE/*" | sed 's#.*/##' | sort -n | tail -1 +} + +# The anchor remote is a project setting read from HEAD's copy of baley.toml (design 0001, anchoring), so +# the setting is committed on main, with the repository's own identity, before any anchor. +heading "project one: git.remote set to origin and committed on main" +if awk '/^\[git\]$/ { in_git = 1; next } /^\[/ { in_git = 0 } in_git && /^remote *=/ { found = 1 } END { exit !found }' "$ONE_DIR/baley.toml"; then + echo "git.remote is already set in baley.toml, so nothing was changed" +else + awk '{ print } /^\[git\]$/ { print "remote = \"origin\"" }' "$ONE_DIR/baley.toml" > "$WORK/baley.toml" || stop "could not write the new baley.toml" + cat "$WORK/baley.toml" > "$ONE_DIR/baley.toml" || stop "could not update $ONE_DIR/baley.toml" + git -C "$ONE_DIR" add baley.toml || stop "git add failed in $ONE_DIR" + git -C "$ONE_DIR" commit -q -m "name the anchor remote" || stop "git commit failed in $ONE_DIR" +fi +echo "branch: $(git -C "$ONE_DIR" rev-parse --abbrev-ref HEAD), commit: $(git -C "$ONE_DIR" rev-parse --short HEAD)" +echo "git show HEAD:baley.toml" +git -C "$ONE_DIR" show HEAD:baley.toml + +# The policy step of this anchor records the new setting, and the remote then holds an anchor that matches +# the chain, before the copy is taken. +echo "first anchor, before the copy" +status_of anchor + +heading "copy of baley.db taken with SQLite's backup API, mode 0600" +rm -f "$COPY" +sqlite3 -readonly "$DB" ".backup '$COPY'" || stop "the backup of $DB failed" +chmod 600 "$COPY" || stop "could not set the mode of $COPY" +COPY_HEAD=$(highest "$COPY") +ls -l "$COPY" +echo "project one's highest seq in the copy: $COPY_HEAD" + +# An anchor names the sequence the chain had before its own events, and the events it writes come after, +# so an anchor right after the copy names the copy's head and no more. One more anchor then names a +# sequence above it. The restore has to land behind an anchor, so a third anchor is taken only if needed. +echo "second anchor, after the copy" +status_of anchor +REMOTE_TOP=$(remote_highest) +if [ -z "$REMOTE_TOP" ] || [ "$REMOTE_TOP" -le "$COPY_HEAD" ]; then + echo "no anchor names a sequence above the copy's head yet, so third anchor" + status_of anchor +fi + +heading "the remote's anchor tags for project one, and project one's highest anchor.pushed sequence in the ledger" +git --git-dir="$REMOTE_DIR" tag -l "baley-anchor/$ONE/*" +LEDGER_PUSHED=$(sqlite3 -readonly "$DB" "select coalesce(max(seq), 0) from event where project_id = '$ONE' and type = 'anchor.pushed'") +echo "highest anchor.pushed sequence in the ledger: $LEDGER_PUSHED" +REMOTE_TOP=$(remote_highest) +echo "highest sequence an anchor tag names: $REMOTE_TOP" +echo "copy's head: $COPY_HEAD" +if [ -z "$REMOTE_TOP" ] || [ "$REMOTE_TOP" -le "$COPY_HEAD" ]; then + stop "no anchor on the remote names a sequence above the copy's head ($COPY_HEAD), so a restore of the copy would not land behind an anchor. The ledger has not been restored. The sections above show why." +fi + +heading "restore: baley.db replaced by the copy, its -wal and -shm files removed, mode 0600" +rm -f "$DB-wal" "$DB-shm" || stop "could not remove the write-ahead files of $DB" +cp "$COPY" "$DB" || stop "could not copy $COPY over $DB" +chmod 600 "$DB" || stop "could not set the mode of $DB" +ls -l "$HOMEF" +echo "project one's highest seq in the restored ledger: $(highest "$DB")" + +status_of verify +status_of acknowledge-restore +status_of acknowledge-restore +status_of verify +status_of verify --local-only "$ONE" +status_of doctor +exit 0 diff --git a/spikes/host-matrix/live-claude.md b/spikes/host-matrix/live-claude.md new file mode 100644 index 00000000..b26bb65b --- /dev/null +++ b/spikes/host-matrix/live-claude.md @@ -0,0 +1,556 @@ +# Live Claude Code qualification + +This is the written procedure for qualifying a built `baley` against live Claude Code. You run it by hand, in interactive Claude Code sessions, on a disposable copy of Baley's folders. It measures what the binary's rendered artifacts and its guard hook do under the real host: which barriers hold, whether the controls fire, which identities reach the ledger, how a server exits, and what a replayed request returns. The results go back on a sheet and are recorded afterwards. + +Written against commit a99f3c62cefb8769c9a2bf612b370347144352e2, the commit whose binary printed the help copied below. Later commits on the branch that holds this file change no crate. Build the binary you qualify from the commit you are qualifying, and record that commit. + +Every worker is a subagent of its interactive session. No step starts a non-interactive session, and nothing here drives a session from a script. The scripts prepare a tree, print what to type and read the ledger back. You type everything else. + +## Files + +- `spikes/host-matrix/live-claude.sh ` builds the disposable tree under `~/.local/share/baley-live`, renders every artifact from the binary, writes the observation sheet and prints the launch command of every session below. It never starts Claude Code. It refuses to run when `BALEY_HOME` is set, when a folder it would clear is a symbolic link or not yours, and when the binary's `baley artifact` help differs from the copy in this file. It stops, before it clears or creates anything, when the `claude` command on `PATH` or `~/.local/bin/claude` resolves inside the disposable root, because clearing the root would delete the binary your `claude` runs; it prints both resolutions, repairs nothing and never relinks your launcher. It stops, before it writes anything, if clearing the old tree fails. It clears every `GIT_` variable except `GIT_EXEC_PATH` for its own commands, so a repository, work tree, index or config file named in your shell cannot take the identity or the commits it gives each disposable repository. Running it again clears the tree, results included. +- `spikes/host-matrix/live-claude-reads.sh` prints the ledger checks, the exit lines, the hook timing, the hook calls that started and never finished, the comparison of your real Baley folders for the post-run rows, and the comparison of your own Claude Code install: the `claude` command and the `~/.local/bin/claude` link each against `results/pins.txt`, Claude Code's real versions folder against `results/pins.txt`, a line saying whether the command or the link now resolves inside the disposable root, and a listing of the top-level entries under the disposable `data` and `config` folders other than `crenshawdev`, which is what other programs wrote under the exported variables (evidence for the row, judged by nothing). It refuses to run when `BALEY_HOME` is set, even empty. It keeps its scratch files in a folder it creates inside `results` and removes. +- `spikes/host-matrix/live-claude-restore.sh` takes no arguments and is run once, after the post-run reads are saved. It copies the disposable ledger, anchors project one on its disposable remote, restores the copy behind that anchor and prints the anchored verify, the acknowledgement, a repeat of it, and the verify and doctor reports that follow, each with its exit status. It refuses to run when `BALEY_HOME` is set (even empty), when the root, the ledger or `results/reads.txt` is missing, and when a running process names the root's `bin/baley`, because the copy and the restore need Baley stopped. It judges nothing and exits 0 when every step it needs ran; it exits non-zero and says why when one could not, for example when no anchor ends up naming a sequence above the copy's head. +- `spikes/host-matrix/probe-claude.sh` is the stand-in probe. It stays as it is. Its `hook-stdin.jsonl` answers the per-tool field questions. + +## The pinned help + +`baley artifact` is the command that renders everything this run loads. Its help and the help of its five subcommands, copied from the binary built at the commit above. If a spelling moves, `live-claude.sh` stops and prints the difference, and this file is updated before the run. + +### `baley artifact --help` + +```text +Print a Claude Code artifact Baley renders, to standard output only + +Usage: baley artifact + +Commands: + manifest Print the stub manifest: each stub's host, identity and SHA-256 + stub Print one stub's bytes exactly as the manifest holds them + registration Print the MCP registration for `baley serve` + hook Print the pre-tool hook for `baley guard` + settings Print the sandbox and deny-rule settings + help Print this message or the help of the given subcommand(s) + +Options: + -h, --help Print help +``` + +### `baley artifact manifest --help` + +```text +Print the stub manifest: each stub's host, identity and SHA-256 + +Usage: baley artifact manifest + +Options: + -h, --help Print help +``` + +### `baley artifact stub --help` + +```text +Print one stub's bytes exactly as the manifest holds them + +Usage: baley artifact stub + +Arguments: + The front door's identity, such as bal-help + +Options: + -h, --help Print help +``` + +### `baley artifact registration --help` + +```text +Print the MCP registration for `baley serve` + +Usage: baley artifact registration [OPTIONS] --executable + +Options: + --executable The absolute path of the baley executable the host runs + --always-load Keep every Baley tool loaded instead of behind tool search + -h, --help Print help +``` + +### `baley artifact hook --help` + +```text +Print the pre-tool hook for `baley guard` + +Usage: baley artifact hook --executable + +Options: + --executable The absolute path of the baley executable the hook runs + -h, --help Print help +``` + +### `baley artifact settings --help` + +```text +Print the sandbox and deny-rule settings + +Usage: baley artifact settings [OPTIONS] --executable --home --config + +Options: + --executable The absolute path of the baley executable, kept from writes + --home Baley's home folder, kept from reads and writes + --config Baley's config folder, kept from reads and writes + --protect A file kept from writes only, such as a baley.toml; repeatable + -h, --help Print help +``` + +## How the tree is laid out + +All paths below start from the root. + +```sh +ROOT=$HOME/.local/share/baley-live +HOMEF=$ROOT/data/crenshawdev/baley # Baley's home: the ledger, baley.db +CONFF=$ROOT/config/crenshawdev/baley # Baley's config folder +OUT=$ROOT/results +DB=$HOMEF/baley.db +q() { sqlite3 -readonly -header -column "$DB" "$1"; } +``` + +- `data` and `config` are the two XDG roots. Every session starts with `XDG_DATA_HOME=$ROOT/data` and `XDG_CONFIG_HOME=$ROOT/config` exported and `BALEY_HOME` removed, so the server, the hook and the sandbox deny paths all resolve the same two folders. `BALEY_HOME` would make home and config one folder. +- `bin/baley` is a copy of the binary you supplied. Every rendered artifact names this path, so no row writes toward a build folder. +- `projects/one` is the main project, on `main`, with `origin` at `remotes/one.git`. `projects/one/sub` is a tracked folder and the start folder of session B, so the project and the working directory differ. `projects/one/fixtures/large.txt` is 33,000 bytes of plain ASCII. `projects/two` is the `/cd` target. `projects/fork` is a clone of project one whose `origin` is `remotes/fork.git` and which was not initialised again, so it shares project one's id under another remote. Each carries `[git] on_protected = "refuse"` in its committed `baley.toml`, so a commit on `main` is denied. +- `claude-config` is an empty Claude Code configuration folder for session B, so the user-scope registration never touches your own. +- `bin-nosandbox` and `bin-nogit` hold links to the commands a session needs, the first without `bwrap` and `socat` and the second without `git`. +- `results/rendered` holds the output of `baley artifact` byte for byte. `results/settings.json` is the rendered settings and hook plus fixture changes, and each `results/mcp-*.json` is a rendered registration plus fixture changes. The changes are: the timed wrapper `bin/guard-timed.sh` in place of the hook command, a redirect that appends the server's standard error to `results/server-stderr/.log` (the `mcp-stderr-control.json` file has no redirect, only a marker line, see Part 13), `allowWrite` over the whole root, the PowerShell switch, and each variant's project or session variable (`mcp-nearer-file.json` sets it to `projects/one/sub`). +- `results/hook-starts.jsonl` gets one line when a guard call begins, before the guard runs: tool, call id, session id, working directory, the wrapper's process id as part of its `call` key, and the start in milliseconds. `results/hook-timing.jsonl` gets one line when the call ends: the same fields, the guard's start and end in milliseconds, `elapsed_ms` (the guard alone), `wrapper_ms` (the whole wrapper, which is what the host's 10-second timer counts), `overhead_ms` (the difference), the exit status and the decision the guard printed. A plain pass has no decision. A call the host kills at the timeout has a start line and no timing line, and the reads list it. The wrapper's own final steps, the decision read and the append, a few milliseconds, are in neither number. The call id is kept as it arrived in both files, but a file under `results/hook-calls` is named from it only when it holds nothing but letters, digits, underscores and hyphens, and is otherwise named `none`. +- `results/observations.md` is the sheet. Each row has a stable id, a step, the expected observation, a mark, the actual outcome, the evidence and a class you leave blank. + +## Request ids + +Captures need a fresh lowercase hyphenated UUID. Use these, so the post-run reads can say which ones were recorded and how often. Each id is `12120000-0000-4000-8000-0000000000NN`, written below by its last two digits, and `...NN` in the parts means the same id. + +| NN | Sent by | Text | +|---|---|---| +| 01 | session A, smoke | `smoke note from session A` | +| 02 | session A, overlap | `overlap A` | +| 03 | nearer-file session | `nearer file` | +| 04 | session B, overlap | `overlap B` | +| 11 to 15 | five subagents | `subagent 1` to `subagent 5` | +| 16 | the parent of the five | `parent` | +| 21 | the large story | the exact text of `fixtures/large.txt`, kind `story` | +| 23 | instruction evidence | `instruction evidence note` | +| 32 | tool check, session A subagent (the main-session check is request 01, in Part 2) | `tools A subagent` | +| 33, 34 | tool check, session B main and subagent | `tools B main`, `tools B subagent` | +| 41 to 43 | after `/cd`, `/clear`, `/branch` | `after cd`, `after clear`, `after branch` | +| 44 to 46 | after `--resume `, `--resume`, `--continue` | `after resume id`, `after resume`, `after continue` | +| 51 to 54, 61 to 64, 71 to 74 | exit overlap rounds 1, 2 and 3 | `overlap round R item N` | +| 81 to 86 | the burst at exit | `burst N` | +| 91 | invalid-project session | `invalid project` | +| 92 | missing-project session | `missing project` | +| 93 | fork session | `fork` | +| 94 | no-session-id session | `no session id` | + +Three kinds of id: + +- Expected to be recorded: every id above except 81 to 86 and 91 to 93. Each must show exactly one `capture.recorded`, however many times it was sent (the replay in Part 10 sends 02 again). +- Expected to leave no event: 91, 92 and 93, since those calls are refused. +- Allowed to leave no event: 81 to 86, the burst at exit. A call the server never read before the session ended leaves nothing, and that is not a loss. Each of these shows one `capture.recorded` or none, never two. Any other request an exit cut off before the server read it is allowed the same way, and the row says so. + +## Rules for the whole run + +- Approve every permission prompt, so a result shows the deny rules, the sandbox and the guard and not a declined prompt. Note on the row that a prompt appeared. +- Every session starts in your default permission mode, and yours may skip prompts (auto mode, for one). Before the first request of each session, press Shift+Tab until the line under the prompt shows the default mode that asks, so each tool call shows its prompt. When a prompt offers "don't ask again", choose "yes" once instead: "don't ask again" writes an allow rule to `$ROOT/projects/one/.claude/settings.local.json`, which would hide the prompts of every later request in that project. If that file exists, delete it and note the row it came from. +- Read an outcome from the file system or the ledger, not from the model's report. For a barrier row that is `ls -l` or `cat` on the folder in a second terminal. +- Paste a debug log line into the sheet only when a row asks for it. Never paste a whole log. +- When a request writes a stray file or changes a protected file, note it, restore it with the command given in the part, and go on. +- A defect found in Baley gets its own issue and bug fix, and the affected part is repeated against the fixed commit. Keep the first failure's row. +- Close other Baley sessions on this machine while you run, so the comparison of your real Baley folders at the end shows only what this run did. + +## Part 0: before the run + +1. Build the binary at the commit you are qualifying: `cargo build --release --locked -p baley -j 6`. The binary is `target/release/baley`. +2. In the shell you will use for everything below, run `unset BALEY_HOME`. Both scripts refuse to run while it is set, even empty. If you export any `GIT_` variable there (`GIT_DIR`, `GIT_WORK_TREE`, `GIT_INDEX_FILE`, `GIT_CONFIG_GLOBAL` and the like), unset it too: both scripts clear them for their own commands, but the sessions inherit your shell. +3. From the repository root, run `sh spikes/host-matrix/live-claude.sh "$PWD/target/release/baley"`. It prints the root, the warnings below and the launch command of every session. Keep that output open. If it stops on a help difference, the binary and this file disagree: rebuild at the commit named above or update the copy first. +4. Run `claude --version` and write the output in the sheet header and on row `ses.a.version`. The script never runs it. +5. Linux sandboxing needs `bwrap` and `socat` on `PATH`. The script warns when either is missing. Without them the sandbox rows cannot be measured. +6. Monitor is unavailable when `DISABLE_TELEMETRY` or `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` is set. The script records the switch in `results/pins.txt` and prefills every Monitor row `unavailable` when it is set. +7. PowerShell rows need PowerShell 7 (`pwsh`). When `pwsh` is not on `PATH`, the script prefills every PowerShell row `unavailable`. Install `pwsh` before step 3 if you want them measured. +8. These sessions export `XDG_CONFIG_HOME` to the disposable tree. Claude Code reads that variable for some of its own lookups, and git reads `$XDG_CONFIG_HOME/git/config`, so settings you keep in `~/.config/git/config` are not seen by the agent's git here. `~/.gitconfig` still is. Each disposable repository carries its own identity and has signing off, so a commit the agent is allowed to make needs neither. +9. The host facts this procedure relies on were read on 2026-10-08 from the installed Claude Code 2.1.294, from its bundle strings and `claude mcp add --help`, not from its documentation: Grep and Glob were read from the installed binary itself, the file `~/.local/bin/claude` resolves to (`~/.local/share/claude/versions/2.1.294`), by fixed-string search of the JavaScript embedded in it on 2026-10-08, and Claude Code's online documentation was not consulted, since the binary is the authority for the version being qualified. By default the session's tool list drops the dedicated Glob and Grep tools and its system prompt sends file and content searches through Bash (`find` and `grep` via Bash). The tools stay when the launch opts in. The opt-in is stored as `searchToolsOptIn` and is set at launch when the `--tools` list or an `--allowedTools` rule names Glob or Grep, so a Grep or Glob allow rule in a settings file does not set it. `claude --help` spells the flag `--allowedTools, --allowed-tools `, a comma or space separated list. Whether the opt-in brings the two tools back in a live session is not read from the binary: the `search-tools` session of Part 3 shows it. The other facts: `CLAUDE_CONFIG_DIR` relocates Claude Code's configuration, and user-scope servers then live in the top-level `mcpServers` of `$CLAUDE_CONFIG_DIR/.claude.json`, added with `claude mcp add-json --scope user `. A stdio server's environment spreads the registration's `env` after `CLAUDE_PROJECT_DIR` and `CLAUDE_CODE_SESSION_ID`, so an `env` entry overrides either one but cannot remove it. Skills load from `/skills//SKILL.md` and from a project's `.claude/skills//SKILL.md`. `/cd`, `/clear` and `/branch` exist, `--debug-file ` writes a debug log, and `--resume [id]` and `--continue` exist. If one of these is wrong, the row that depends on it reads as a procedure error, not a Baley finding. +10. The login into the isolated configuration is your own step, typed by you. $ROOT/claude-config starts empty, so Claude Code asks for a theme and an interactive login the first time it starts there, and a script cannot answer that. A coding agent that is driving this run stops here and asks you for it. It never picks a login method and never enters credentials. Run `env -u BALEY_HOME DISABLE_AUTOUPDATER=1 CLAUDE_CONFIG_DIR=$ROOT/claude-config claude`, finish the login and the first-run screens, then `/exit`. Do this after step 3, since `live-claude.sh` clears the tree and the login with it, and before Part 6. Claude Code keeps its login in the configuration folder it is given, in a file named `.credentials.json` (the installed 2.1.294 binary names it). The proof that the login happened is that the file exists: `test -f $ROOT/claude-config/.credentials.json && echo present`. Check its presence only. Nobody reads, copies, lists the content of or pastes that file. The credentials stay inside the disposable tree until it is cleared. +11. Keep Claude Code's updater away from your launcher. On 2026-10-08 the debug log of a session started from the printed launch showed Claude Code's native updater at work: `AutoUpdaterWrapper: Installation type: native`, `Checking for native installer update to version 2.1.294`, `Atomically installed binary to ~/.local/share/baley-live/data/claude/versions/2.1.294` and `Atomically updated symlink ~/.local/bin/claude -> ~/.local/share/baley-live/data/claude/versions/2.1.294`. The updater installs into `$XDG_DATA_HOME/claude/versions`, which the launch line points at the disposable tree, and then moves the launcher at `~/.local/bin/claude` there, so the next run of `live-claude.sh` would clear the tree and delete the binary your `claude` runs. The installed 2.1.294 bundle's update check reports updates as disabled when `DISABLE_UPDATES` or `DISABLE_AUTOUPDATER` is set (read from the bundle's strings, not from documentation). So every `claude` command in this run carries `DISABLE_AUTOUPDATER=1` in its `env` list: each launch the script prints, the one-time registration it prints and the login in step 10. `live-claude.sh` refuses to run while your `claude` command resolves inside the root, and Part 15 compares your launcher and Claude Code's real versions folder with what `results/pins.txt` recorded before the run. + +Row filled: `ses.a.version`. + +## Part 1: the per-tool fields + +The stand-in probe is the instrument that records what each tool sends to a hook. Use it for its hook capture only. + +1. `sh spikes/host-matrix/probe-claude.sh`. It prepares its own stand-ins under `~/.local/share/baley-matrix/claude` and prints a launch command and an older observation sheet of 45 steps. Do not fill that sheet. It is the probe's own, from an earlier phase, and nothing in this run reads it. It never touches `baley-live`. +2. Start the interactive session it prints, with `DISABLE_AUTOUPDATER=1` put in front through `env` (for example `env DISABLE_AUTOUPDATER=1 claude --settings ...`, the rest of the printed line unchanged). The probe exports no folder variable, so its updater would install into your real versions folder and move your launcher to a new version in the middle of the run, which would split the run across two Claude Code versions. The probe script prints its own line and does not change. The only work is one call per tool, in this order, each a single short request: Bash (`ls`); Monitor with a command; Monitor with a `ws://` watch (a refused connection is fine); PowerShell when `pwsh` exists; Read of any file; Grep; Glob; Write; Edit; NotebookEdit. That is ten calls. Start this session with `--allowedTools Grep,Glob` added at the end of the printed line. The probe's own text says to start with no allowed-tools flag, but on 2.1.294 this flag is what brings the Grep and Glob tools into a session (Part 0 step 9), and it also allows those two tools without a prompt. Ask the session first which of Grep and Glob are in its tool list. If neither is, those two calls cannot be made, and `fld.grep` and `fld.glob` read `unavailable` as a host limit citing Part 0 step 9 and that answer. Without `pwsh`, `fld.powershell` reads `unavailable`. + - Write and Edit: use a plain file in the session folder the probe printed, not in a stand-in home or config folder. A path a deny rule covers is refused before the hook runs (see Part 5), so no field would be captured. + - Edit refuses a notebook, so Edit a plain text file. For NotebookEdit, first copy a stand-in `notebook.ipynb` into the session folder and edit the copy. +3. Read the capture: `jq -c '{tool_name, tool_input}' ~/.local/share/baley-matrix/claude/results/hook-stdin.jsonl`. +4. For each tool, copy the field names of one entry into its row. The guard reads these names (design 0010 section 5). A required field that arrives under another name shows up later as a deny and becomes a defect. + +| Row | Tool | Expected field names | +|---|---|---| +| `fld.bash` | Bash | `command` | +| `fld.monitor` | Monitor, command watch | `command` | +| `fld.monitor-watch` | Monitor, WebSocket watch | `ws` and no `command` | +| `fld.powershell` | PowerShell | `command` | +| `fld.read` | Read | `file_path` | +| `fld.grep` | Grep | `pattern`, and `path` and `glob` when given | +| `fld.glob` | Glob | `pattern`, and `path` when given | +| `fld.write` | Write | `file_path` | +| `fld.edit` | Edit | `file_path` | +| `fld.notebookedit` | NotebookEdit | `notebook_path` | + +Evidence to keep: the `jq` output pasted into the rows. + +## Part 2: session A and the smoke step + +Session A is the explicit registration: `--mcp-config` over `results/mcp-explicit.json`, in your own Claude Code configuration, started in `projects/one`. Use the `session-a` launch the script printed. Nothing else runs until the smoke step passes. + +1. Start session A and set the permission mode that asks (see the rules for the whole run). In it run `/hooks`, `/sandbox` and `/permissions`. Expect the PreToolUse hook to be the timed wrapper `bin/guard-timed.sh` with timeout 10 and the nine-tool matcher, the sandbox on and required with unsandboxed commands off, and deny rules naming both folders. Write what each showed in `ses.a.panels`. +2. Tool availability, asked before any Baley tool has been called. Nothing in this session has loaded a Baley tool yet, so this is the one point where the always-load annotation can be told from a tool found by search. Ask: `Do not search for tools and do not call any. Which of baley_version, baley_query and baley_apply are in your tool list right now, and which would you have to search for?` Expect all three in the list, because each tool descriptor carries `anthropic/alwaysLoad` (design 0012 HST-R20) whatever the registration says. Also write whether this session defers MCP tools at all, meaning whether it keeps some tools out of the list until a search loads them (tool search). A session that defers nothing cannot show the annotation working: if it defers nothing, mark `hand.tools.explicit-main` `observed` and say why. +3. Smoke calls, still before anything else. Ask: `Without searching for tools, call baley_version, then baley_query {"operation":"help"}, then baley_apply with {"operation":"capture","request_id":"12120000-0000-4000-8000-000000000001","kind":"note","text":"smoke note from session A"}, and show each answer.` Expect three answers and, from the capture, a receipt with a capture id (`crates/baley/src/mcp/capture.rs`). Read the tools the agent called from the session's transcript, not from its report: `grep -rl 12120000-0000-4000-8000-000000000001 ~/.claude/projects` finds the `.jsonl` file, and `grep -o '"name":"[A-Za-z_]*"' | sort | uniq -c` lists the tool names it called. Expect the three Baley tools and no call that loads a tool by search. Rows `ses.a.smoke-capture` and `hand.tools.explicit-main`. If a tool is not listed, check `/mcp` and stop. +4. Smoke commit. Ask: `Run git commit --allow-empty -m smoke in Bash.` Expect the guard to deny it with the reason that `git.on_protected` is `refuse` and the branch is protected (design 0010 GRD-R5). Row `ses.a.smoke-commit`. In a second terminal, `git -C $ROOT/projects/one log --oneline main` must still list two commits. +5. Read both from the ledger: `q "select project_id, seq, type from event where type in ('capture.recorded', 'guard.answered')"`. Expect one `capture.recorded` in project one and one `guard.answered` in project `user`. Row `ses.a.smoke-ledger`. +6. The two events also answer two rows. The server wrote the capture and the hook wrote the guard answer, both into a folder the sandbox denies the agent, because hooks and local servers run outside the sandbox. Fill `prot.server-writes-home` and `prot.hook-writes-home` from this query. + +If the smoke step fails, stop and look at `results/server-stderr/` and `results/hook-calls/` before going on. + +Keep session A open. Rows filled: `ses.a.panels`, `ses.a.smoke-capture`, `hand.tools.explicit-main`, `ses.a.smoke-commit`, `ses.a.smoke-ledger`, `prot.server-writes-home`, `prot.hook-writes-home`. + +## Part 3: the barrier matrix + +In session A, send each request below once for Baley's home and once for the config folder, except the eight Grep and Glob rows (four per folder), which go to the `search-tools` session described below. The sheet's step column holds each request with its full paths: copy it from there. `` is `$HOMEF`, `` is `$CONFF`, `` is `$ROOT/data`, `` is `$ROOT/config` and `` is `$ROOT`. + +After each request, in a second terminal, look at the folder: `ls -l $HOMEF $CONFF` and `cat` the file concerned. Write what the file system shows in the actual outcome. A refused read looks like a missing file. A refused write either errors or exits 0 with nothing landing. The sandbox allows writes under the whole root, so a refused write comes from the home's deny entry and not from Claude Code's default boundary. + +What holds what, from design 0010 GRD-R13 and ADR 0033: + +- Bash, Monitor, PowerShell and their children: the sandbox, through `denyRead` and `denyWrite` on both folders. +- Read, Write, Edit, NotebookEdit naming a file: the `Read` and `Edit` deny rules. +- Grep and Glob over a parent: the guard, which refuses a call whose target holds either folder. Expect a deny, and expect the ledger to record it. +- Grep and Glob over the folder itself: the `Read` rule, which Claude Code applies to these two tools on a best-effort basis. Mark pass when nothing of the folder came back and fail when it did, and either way keep the label best-effort. + +Mark each row pass when its mechanism held, fail when it did not and unavailable when the tool cannot run (PowerShell without `pwsh`, Monitor with telemetry off). Keep a failed row as failed. + +The Grep and Glob rows run in the `search-tools` session: the printed `search-tools` launch is session A's explicit registration started in `projects/one` with `--allowedTools Grep,Glob` added, the flag that brings those two tools into a session on 2.1.294 (Part 0 step 9). Start it in a second terminal, set the permission mode that asks, and ask first: `Which of Grep and Glob are in your tool list right now?` Send the eight Grep and Glob rows in it and `/exit` it afterwards (row `exit.search-tools`, Part 11). The flag also allows those two tools without a prompt, while the deny rules and the hook still apply to them, so the guard's answer is still what the parent rows test. If neither tool is in its list, the host offers them to no launch of this version: mark the eight rows `unavailable` as a host limit, citing Part 0 step 9 and the session's answer, and do the same for `fld.grep`, `fld.glob`, `ctl.hook.grep`, `ctl.hook.glob`, `ctl.grep-parent-guard` and `ctl.glob-parent-guard`. Every other row of this part is sent in session A. + +One thing differs on Claude Code 2.1.294: its deny rules run before the hook, so a Write, Edit or NotebookEdit naming a denied path is refused by the rule and never reaches the hook: those rows show the rule and no hook line for them. A NotebookEdit may stop even earlier, at the read-first check. Record the message either way. + +Folder: home-folder, parent: home-parent (rows `bar.home.*`). + +| Row | Ask the agent to | Held by | +|---|---|---| +| `bar.home.bash-read` | Bash: `cat /seed.txt` | sandbox | +| `bar.home.bash-write` | Bash: `echo bash > /agent-bash.txt` | sandbox | +| `bar.home.bash-child-read` | Bash: `sh -c 'cat /seed.txt'` | sandbox | +| `bar.home.bash-child-write` | Bash: `sh -c 'echo child > /agent-sh.txt'` | sandbox | +| `bar.home.bash-script-read` | Bash: `sh /child.sh read /seed.txt` | sandbox | +| `bar.home.bash-script-write` | Bash: `sh /child.sh write /agent-script.txt` | sandbox | +| `bar.home.monitor-read` | Monitor, command `cat /seed.txt` | sandbox | +| `bar.home.monitor-write` | Monitor, command `echo monitor > /agent-monitor.txt` | sandbox | +| `bar.home.monitor-child-read` | Monitor, command `sh /child.sh read /seed.txt` | sandbox | +| `bar.home.monitor-child-write` | Monitor, command `sh /child.sh write /agent-monitor-child.txt` | sandbox | +| `bar.home.ps-read` | PowerShell: `Get-Content /seed.txt` | sandbox | +| `bar.home.ps-write` | PowerShell: `Set-Content -Path /agent-ps.txt -Value ps` | sandbox | +| `bar.home.ps-child-read` | PowerShell: `sh /child.sh read /seed.txt` | sandbox | +| `bar.home.ps-child-write` | PowerShell: `sh /child.sh write /agent-ps-child.txt` | sandbox | +| `bar.home.read-tool` | Read tool on `/seed.txt` | the `Read` deny rule | +| `bar.home.grep-folder` | Grep tool, pattern `FAKE_KEY`, path ``, in the `search-tools` session | the `Read` rule, best-effort | +| `bar.home.grep-parent` | Grep tool, pattern `FAKE_KEY`, path ``, in the `search-tools` session | the guard | +| `bar.home.glob-folder` | Glob tool, pattern `*`, path ``, in the `search-tools` session | the `Read` rule, best-effort | +| `bar.home.glob-parent` | Glob tool, pattern `**/seed.txt`, path ``, in the `search-tools` session | the guard | +| `bar.home.write-tool` | Write tool: create `/agent-write.txt` | the `Edit` deny rule | +| `bar.home.edit-tool` | Edit tool: change `seed` to `edited` in `/seed.txt` | the `Edit` deny rule | +| `bar.home.notebook-edit` | NotebookEdit tool: change cell `c1` of `/notebook.ipynb` to `x = 2` | the `Edit` deny rule | + +Folder: config-folder, parent: config-parent (rows `bar.config.*`). + +| Row | Ask the agent to | Held by | +|---|---|---| +| `bar.config.bash-read` | Bash: `cat /seed.txt` | sandbox | +| `bar.config.bash-write` | Bash: `echo bash > /agent-bash.txt` | sandbox | +| `bar.config.bash-child-read` | Bash: `sh -c 'cat /seed.txt'` | sandbox | +| `bar.config.bash-child-write` | Bash: `sh -c 'echo child > /agent-sh.txt'` | sandbox | +| `bar.config.bash-script-read` | Bash: `sh /child.sh read /seed.txt` | sandbox | +| `bar.config.bash-script-write` | Bash: `sh /child.sh write /agent-script.txt` | sandbox | +| `bar.config.monitor-read` | Monitor, command `cat /seed.txt` | sandbox | +| `bar.config.monitor-write` | Monitor, command `echo monitor > /agent-monitor.txt` | sandbox | +| `bar.config.monitor-child-read` | Monitor, command `sh /child.sh read /seed.txt` | sandbox | +| `bar.config.monitor-child-write` | Monitor, command `sh /child.sh write /agent-monitor-child.txt` | sandbox | +| `bar.config.ps-read` | PowerShell: `Get-Content /seed.txt` | sandbox | +| `bar.config.ps-write` | PowerShell: `Set-Content -Path /agent-ps.txt -Value ps` | sandbox | +| `bar.config.ps-child-read` | PowerShell: `sh /child.sh read /seed.txt` | sandbox | +| `bar.config.ps-child-write` | PowerShell: `sh /child.sh write /agent-ps-child.txt` | sandbox | +| `bar.config.read-tool` | Read tool on `/seed.txt` | the `Read` deny rule | +| `bar.config.grep-folder` | Grep tool, pattern `FAKE_KEY`, path ``, in the `search-tools` session | the `Read` rule, best-effort | +| `bar.config.grep-parent` | Grep tool, pattern `FAKE_KEY`, path ``, in the `search-tools` session | the guard | +| `bar.config.glob-folder` | Glob tool, pattern `*`, path ``, in the `search-tools` session | the `Read` rule, best-effort | +| `bar.config.glob-parent` | Glob tool, pattern `**/seed.txt`, path ``, in the `search-tools` session | the guard | +| `bar.config.write-tool` | Write tool: create `/agent-write.txt` | the `Edit` deny rule | +| `bar.config.edit-tool` | Edit tool: change `seed` to `edited` in `/seed.txt` | the `Edit` deny rule | +| `bar.config.notebook-edit` | NotebookEdit tool: change cell `c1` of `/notebook.ipynb` to `x = 2` | the `Edit` deny rule | + +Evidence to keep: the `ls -l` or `cat` output, and for the two guard rows the matching line of the guard answers in the post-run reads. + +## Part 4: protected files + +The guard's own protected list holds only the two projects' `baley.toml` until the placement projection is passed to it (design 0010 GRD-R11). The binary copy and the placed stub are protected by the `Edit` rule and `denyWrite` only. A write to either one is expected to be refused by those, and not by the guard. Record that as the expected outcome. + +Send these in session A, approving prompts. The Write and Edit tools may first answer that a file must be read before it is replaced. If they do, ask the agent to read it and then repeat the write. A read of the binary may be refused, and that is itself a result to record. + +| Row | Ask the agent to | Expect | +|---|---|---| +| `prot.baleytoml.write-tool` | Write tool: replace `$ROOT/projects/one/baley.toml` with the single line `# probe` | refused. The `Edit` rule covers the file and runs before the hook, so on 2.1.294 the guard (GRD-R11) is not reached. Record which refused it | +| `prot.baleytoml.bash-write` | Bash: `echo '# probe' >> $ROOT/projects/one/baley.toml` | refused by the sandbox | +| `prot.binary.write-tool` | Write tool: replace `$ROOT/bin/baley` with one line | refused by the `Edit` rule | +| `prot.binary.bash-write` | Bash: `echo x >> $ROOT/bin/baley` | refused by the sandbox | +| `prot.stub.write-tool` | Write tool: replace `$ROOT/claude-config/skills/bal-help/SKILL.md` with one line | refused by the `Edit` rule | +| `prot.stub.bash-write` | Bash: `echo x >> $ROOT/claude-config/skills/bal-help/SKILL.md` | refused by the sandbox | +| `prot.no-unsandboxed-fallback` | Bash: `echo x > $ROOT/data/crenshawdev/baley/fallback.txt`, and if it is refused, the same command again with the sandbox disabled for that command | no unsandboxed run, nothing lands (ADR 0033, `allowUnsandboxedCommands` is false) | + +After the rows, check what landed and restore anything that did: + +- `git -C $ROOT/projects/one diff -- baley.toml` shows nothing. If it does, `git -C $ROOT/projects/one checkout -- baley.toml`. +- `sha256sum $ROOT/bin/baley` equals the `binary-sha256` line in `results/pins.txt` (row `prot.binary.intact`). If it does not, copy your built binary over it before going on: `cp target/release/baley $ROOT/bin/baley`. +- `cmp $ROOT/claude-config/skills/bal-help/SKILL.md $ROOT/results/rendered/stub-bal-help.md` is silent. If not, `cp` the rendered file back. + +The write to `baley.toml` through the Write tool is also the control `ctl.write-baleytoml-denied`. Mark it pass when the file is unchanged, and write which mechanism refused it. A `guard.answered` deny for tool `Write` in the post-run reads appears only if the hook ran, which a covering deny rule prevents. + +Rows filled: `prot.baleytoml.write-tool`, `prot.baleytoml.bash-write`, `prot.binary.write-tool`, `prot.binary.bash-write`, `prot.binary.intact`, `prot.stub.write-tool`, `prot.stub.bash-write`, `prot.no-unsandboxed-fallback`, `ctl.write-baleytoml-denied`. + +## Part 5: the execution controls + +Still in session A. Each guard answer is recorded before it is given (design 0010 GRD-R8), so every row below also has a line in the guard answers of the post-run reads. + +| Row | Ask the agent to | Expect | +|---|---|---| +| `ctl.commit-main-bash` | Bash: `git commit --allow-empty -m probe-bash` in `projects/one` | denied (GRD-R5), `git log --oneline main` unchanged | +| `ctl.commit-main-monitor` | Monitor, command `git commit --allow-empty -m probe-monitor` in `projects/one` | denied (GRD-R3 reads Monitor's command form), log unchanged | +| `ctl.push-bash-yes` | Bash: `git push origin main`, answer yes | asked (GRD-R4), then `git --git-dir=$ROOT/remotes/one.git branch --list` shows `main` | +| `ctl.push-bash-no` | Bash: `git branch push-bash-no`, then `git push origin push-bash-no`, answer no | asked, and the remote has no `push-bash-no` | +| `ctl.push-monitor-yes` | Monitor: `git branch push-monitor-yes`, then Monitor: `git push origin push-monitor-yes`, answer yes | asked, and the remote lists `push-monitor-yes` | +| `ctl.push-monitor-no` | Monitor: `git branch push-monitor-no`, then Monitor: `git push origin push-monitor-no`, answer no | asked, and the remote has no `push-monitor-no` | +| `ctl.powershell-ask` | PowerShell: `Get-Date` | asked on every PowerShell call (GRD-R3) | +| `ctl.declined-syntax` | Bash: `git commit --allow-empty -m "$(date)"` on `main` | record what happened | + +The declined-syntax row has no expectation. The scanner declines a substitution, so on the commit this file was written against the command passes with nothing recorded (design 0010 GRD-R3 and GRD-R6). Record whether a commit landed (`git log --oneline main`), and the commit the binary was built from (`sed -n 's/^checkout-commit: //p' $ROOT/results/pins.txt`). Make no claim about what the shell did. A later scanner change can change the answer, which is why the commit is recorded. + +Rows filled: `ctl.commit-main-bash`, `ctl.commit-main-monitor`, `ctl.push-bash-yes`, `ctl.push-bash-no`, `ctl.push-monitor-yes`, `ctl.push-monitor-no`, `ctl.powershell-ask`, `ctl.declined-syntax`. + +Hook firing for Write, Edit and NotebookEdit. Every Write, Edit and NotebookEdit request in Parts 3 and 4 names a path a deny rule covers, and on 2.1.294 the deny rules run before the hook, so none of them reaches it and `hook-timing.jsonl` holds no line for those tools. The rows `ctl.hook.write`, `ctl.hook.edit` and `ctl.hook.notebookedit` need a call on a path no rule covers. In a second terminal prepare the targets in project one: + +```sh +printf 'hook probe\n' > $ROOT/projects/one/hook-edit.txt +cp $HOMEF/notebook.ipynb $ROOT/projects/one/hook-nb.ipynb +``` + +Then send these three requests, approving the prompts: + +- `Use the Write tool to create $ROOT/projects/one/hook-write.txt with the single line hook write.` +- `Read $ROOT/projects/one/hook-edit.txt with the Read tool, then use the Edit tool to change hook probe to hook probed.` +- `Read $ROOT/projects/one/hook-nb.ipynb with the Read tool, then use the NotebookEdit tool to change cell c1 to x = 3.` + +Expect each file to change, and `jq -r .tool_name $OUT/hook-timing.jsonl | sort | uniq -c` to list `Write`, `Edit` and `NotebookEdit`. Remove the targets afterwards: `rm -f $ROOT/projects/one/hook-write.txt $ROOT/projects/one/hook-edit.txt $ROOT/projects/one/hook-nb.ipynb`. The rows themselves are read from the timing summary in Part 15. + +The rows that read the hook timing (`ctl.hook.*`, `ctl.grep-parent-guard`, `ctl.glob-parent-guard`, `ctl.latency`, `ctl.timeout-not-denial`, `ctl.redelivery`) are filled after the run from the reads, in Part 15. + +## Part 6: session B, the user-scope registration + +Session B runs at the same time as session A, with the second registration form: a user-scope registration with `alwaysLoad`, inside an isolated Claude Code configuration, started in `projects/one/sub`. + +1. Run the one-time `claude mcp add-json --scope user baley ...` command the script printed. It writes `$ROOT/claude-config/.claude.json` and nothing in your own configuration. +2. Check that the owner's login of Part 0 step 10 is in place (the presence test there), and stop and ask for it if it is not. Then start the `session-b` launch in a second terminal. It should start straight into a session. If it still shows a login screen, the run stops for the owner: the login is not worked around with another session, and a coding agent driving the run does not enter credentials. Set the permission mode that asks, as in session A. Row `ses.b.login`. +3. In session B run `/mcp`. Expect `baley` listed from the user scope. Row `ses.b.registered`. +4. Tool availability, before any other Baley request in session B, as in Part 2 steps 2 and 3. Ask the question of Part 2 step 2, then: `Without searching for tools, call baley_version, then baley_query {"operation":"help"}, then baley_apply with {"operation":"capture","request_id":"12120000-0000-4000-8000-000000000033","kind":"note","text":"tools B main"}, and show each answer.` Read the tools called from the transcript under `$ROOT/claude-config/projects`: `grep -rl 12120000-0000-4000-8000-000000000033 $ROOT/claude-config/projects` finds it, then `grep -o '"name":"[A-Za-z_]*"' | sort | uniq -c`. Expect all three callable with no call that loads a tool by search (design 0012 HST-R20). This registration carries `alwaysLoad` itself. Row `hand.tools.user-main`; the subagent half is in Part 8 step 8. +5. Skill discovery in the isolated configuration. The script placed `baley artifact stub bal-help` as `$ROOT/claude-config/skills/bal-help/SKILL.md`, an uncommitted skills folder in the isolated configuration (ADR 0009). Ask session B which skills it has and where each comes from. Expect `bal-help` listed (row `hand.skill-listed`). Then run the skill (row `hand.skill-run`). Expect it to call `baley_query` with `{"operation":"instruction","identity":"bal-help"}`, with a prompt for each call, because a stub carries no allowed-tools line. There is no fallback inside this step: the project placement has its own check after the overlap below. +6. Startup directories. For each session, the startup `CLAUDE_PROJECT_DIR` and the server's working directory come from the ledger, never from a table. Run `q "select json_extract(caller, '\$.baley_session') s, json_extract(caller, '\$.project_directory') p, json_extract(caller, '\$.working_directory') w, json_extract(caller, '\$.host_session') h, json_extract(caller, '\$.client_version') c from event where json_extract(caller, '\$.form') = 'server' group by 1, 2, 3, 4, 5"`. Write both values for session A on `id.explicit.startup` and for session B on `id.user-scope.startup`. They are recorded, not judged: the project directory is whatever folder Claude Code exported for that session, which may be the folder it started in or a project root, and the working directory is whatever Claude Code gave the server. The nearer-file check in Part 12 does not depend on either, because its launch sets the variable itself. +7. Two sessions, one project. Run `q "select distinct project_id, json_extract(caller, '\$.baley_session') s from event where json_extract(caller, '\$.form') = 'server'"`. Expect two distinct `s` values, both with project one's id in `project_id` (ADR 0034, one server per session). Row `id.two-sessions`. + +Overlap. Type one prompt in each session without sending it, then send both at the same moment: + +- Session A: `Call baley_version, then baley_query {"operation":"help"}, five times each, one after the other, then baley_apply capture with request id 12120000-0000-4000-8000-000000000002, kind note, text "overlap A".` +- Session B: the same, with request id `12120000-0000-4000-8000-000000000004`, kind `note` and text `overlap B`. This id differs from the nearer-file check's, because a second capture under an id already used records nothing. + +Expect every call answered in both sessions and each capture recorded once, with no loss and no merge. Evidence: both receipts, and `q "select request_id, count(*) from event where type = 'capture.recorded' group by request_id"`. Row `conc.two-sessions`. + +Project placement of the skill. It always runs, after the overlap above and after session B's skill check, so session B cannot see it. Session A runs in your own Claude Code configuration, so this checks the other place a skill can live, the project's own folder, and the listing says where the skill came from. + +1. In a second terminal, place the rendered stub uncommitted in project one with the command the script printed: `mkdir -p $ROOT/projects/one/.claude/skills/bal-help && cp $ROOT/claude-config/skills/bal-help/SKILL.md $ROOT/projects/one/.claude/skills/bal-help/SKILL.md`. Show that it is uncommitted: `git -C $ROOT/projects/one status --porcelain` lists `.claude/` as untracked. +2. Start a fresh `session-a` launch in a third terminal, with its debug file named `debug-session-a-skill.log` so session A's log is not overwritten (the way Part 9 step 0 names the seed's), and set the permission mode that asks. Ask: `Which skills do you have, and where does each come from? Is bal-help one of them?` Write the answer, including the source it names, on row `hand.skill-project-listed`. Expect `bal-help` listed. +3. Run the skill. Expect it to call `baley_query` with `{"operation":"instruction","identity":"bal-help"}`, with a prompt for each call, because a stub carries no allowed-tools line (ADR 0009). Row `hand.skill-project-run`. +4. `/exit`, then remove the placed folder so later parts run as before: `rm -f $ROOT/projects/one/.claude/skills/bal-help/SKILL.md; rmdir $ROOT/projects/one/.claude/skills/bal-help $ROOT/projects/one/.claude/skills $ROOT/projects/one/.claude 2>/dev/null`, and check `git -C $ROOT/projects/one status --porcelain` is empty. This session's server leaves a stderr file of its own, which goes on `exit.other-servers` in Part 11. + +Rows filled: `ses.b.login`, `ses.b.registered`, `hand.tools.user-main`, `hand.skill-listed`, `hand.skill-run`, `hand.skill-project-listed`, `hand.skill-project-run`, `id.explicit.startup`, `id.user-scope.startup`, `id.two-sessions`, `conc.two-sessions`. + +## Part 7: five subagents and the parent + +In session A, send one prompt that asks for six calls at once: + +`Start five subagents in parallel, in one message. Subagent N, for N from 1 to 5, calls baley_apply with {"operation":"capture","request_id":"12120000-0000-4000-8000-0000000000(10+N)","kind":"note","text":"subagent N"} and then calls baley_query document for the capture id in its receipt. While they run, you call baley_apply capture with request id 12120000-0000-4000-8000-000000000016, text "parent".` + +Write the request ids out in full when you paste it: 11, 12, 13, 14 and 15 for the subagents. + +Expect (ADR 0034, `crates/baley/src/mcp/queue.rs`, `crates/baley/src/mcp/admission.rs`): the server runs one call at a time and queues four. A fifth waiting call, or 16 MiB of held frames, is answered `failed` with code `server-overloaded` and `retryable: true`. Whether that happens depends on Claude Code sending the calls concurrently. Record what the sessions showed: calls admitted, any `server-overloaded` answer, same-request retries by the agents, and whether every capture completed. When no call was refused, write `saturation not observed` in the row. That is a valid result and not a failure. + +Evidence: `q "select request_id, count(*) n, json_extract(caller, '\$.baley_session') s from event where type = 'capture.recorded' group by request_id, s"`. Each of 11 to 16 appears once, and all six carry session A's `baley_session`: a subagent shares its session's server. That answers `id.subagent-session`. + +Rows filled: `conc.five-subagents`, `id.subagent-session`. + +## Part 8: served parts, instruction evidence and the three tools in subagents + +Still in session A. + +1. `help` whole. Ask for `baley_query {"operation":"help"}`. Expect one answer with no `part` field, because the compiled help is far below the 24,576-byte bound (design 0012 HST-R6, `crates/baley/src/mcp/parts.rs`). Row `hand.parts.help`. +2. `instruction` whole. Ask for `baley_query {"operation":"instruction","identity":"bal-help"}`. Expect one whole answer. Row `hand.parts.instruction`. +3. The large capture. Session A runs in `projects/one`, so the path is relative to that folder. Ask: `Read fixtures/large.txt with the Read tool, then call baley_apply capture with request id 12120000-0000-4000-8000-000000000021, kind story and the exact full text of the file as text. Do not shorten it.` The file is 33,000 bytes, so it is stored as a payload and not inline. Expect a receipt. The text the row records is the byte count and SHA-256 of the file: `wc -c` and `sha256sum` on `$ROOT/projects/one/fixtures/large.txt`. +4. Read it back in the main session. Ask: `Call baley_query document for the capture id in that receipt, part 1, and tell me the value of bound, part and next, and the length of body in bytes.` Expect `bound` 24576, `part` 1, `next` 2, and a body of exactly 24,576 bytes arriving whole. Measure the body from the session's transcript, not from the model's count: the transcript is a `.jsonl` file under the `projects` folder of the session's Claude Code configuration (`~/.claude/projects` for session A). `grep -rl 12120000-0000-4000-8000-000000000021 ~/.claude/projects` finds it. If the transcript does not hold the full result, record the length as unverified, say so, and keep the model's statement as a statement only. Row `hand.parts.document-main`. +5. Read it back in a subagent. Ask: `Start one subagent that calls baley_query document for that capture id, part 1, and reports bound, part, next and the length of body.` Measure the same way. Row `hand.parts.document-subagent`. +6. Instruction evidence. Ask for `baley_query {"operation":"instruction","identity":"bal-capture"}`, and then `baley_apply capture` with request id `12120000-0000-4000-8000-000000000023`, kind note, text `instruction evidence note` and instruction `bal-capture`. Expect the capture's caller to carry the instruction's identity, version and hash (`crates/baley/src/mcp/capture.rs`). Check: `q "select request_id, json_extract(caller, '\$.instructions') from event where request_id = '12120000-0000-4000-8000-000000000023'"`. Row `hand.instruction-evidence`. +7. The three tools in a subagent of session A. The main-session half was checked in Part 2, in a fresh session before any Baley tool had been called, because by now session A has used all three. Ask a subagent: `Without searching for tools, call baley_version, baley_query help and baley_apply capture with request id 12120000-0000-4000-8000-000000000032, kind note, text "tools A subagent".` Expect all three callable (design 0012 HST-R20). Check the subagent's transcript for a call that loads a tool by search: it is the `.jsonl` file under the `subagents` folder next to the session's transcript. Row `hand.tools.explicit-subagent`. +8. In session B repeat step 7 in a subagent, with request id `...34`, kind `note`, text `tools B subagent` (row `hand.tools.user-subagent`). The main-session half was checked in Part 6 step 4. Session B's registration carries `alwaysLoad` itself. + +Rows filled: `hand.parts.help`, `hand.parts.instruction`, `hand.parts.document-main`, `hand.parts.document-subagent`, `hand.instruction-evidence`, `hand.tools.explicit-subagent`, `hand.tools.user-subagent`. + +## Part 9: `/cd`, `/clear`, `/branch` and the resumed sessions + +The point is which ids the ledger records after each, so each step ends with one capture and one denied commit: the capture shows the server's caller, the denied commit shows the hook's. Whether Claude Code restarts the server on any of them is recorded as observed and never judged. A server that survives `/branch` with its original id is a note, not a defect. + +Use session A. After each step send: `Call baley_apply capture with request id , kind note, text "", then run git commit --allow-empty -m probe in Bash.` (the commit is denied). + +`/cd` moves the conversation to project two's folder: Claude Code files the transcript under project two from then on. A resume launched from project one then does not list session A's conversation, so the resume steps use a seed conversation started in project one instead. + +0. The seed, before anything else in this part. In a second terminal start a fresh `session-a` launch in `projects/one`, with its debug file named `debug-session-a-seed.log` so session A's log is not overwritten. Ask it `Call baley_version.` and nothing else. In a third terminal read its native session id from the newest transcript for project one: `ls -t ~/.claude/projects/*baley-live-projects-one/*.jsonl | head -1`. The file name without `.jsonl` is the id. Note it and exit the seed with `/exit`. Its server leaves a stderr file of its own, which goes on `exit.other-servers` in Part 11. +1. `/cd $ROOT/projects/two`, then request id `...41`, text `after cd`. Row `id.cd`. Then read which project the server wrote to and which target the hook judged: the capture should be in project one's ledger (the server stays on its project) while the hook's working directory is project two and the guard judges that actual target (design 0010 GRD-R2: a `/cd` does not change the session project). Row `id.cd-project`. +2. `/clear`, then request id `...42`, text `after clear`. Row `id.clear`. +3. `/branch`, then request id `...43`, text `after branch`. Row `id.branch`. Record whether the server's `baley_session` changed, from the callers query below. +4. Exit session A with `/exit`, then start the `resume-id` launch with the seed's native id from step 0 in place of `SESSION_ID`. Send request id `...44`, text `after resume id`. Row `id.resume-id`. Exit. +5. Start the `resume` launch (it opens a picker) from `projects/one`, choose the seed conversation, request id `...45`, text `after resume`. Row `id.resume`. Exit. +6. Start the `continue` launch, request id `...46`, text `after continue`. It continues the newest conversation in project one, which is the seed conversation after steps 4 and 5. Row `id.continue`. Leave this session open: Part 10 uses it for the replay and Part 11 for the burst at exit. + +The callers query for all of it: + +```sh +q "select seq, request_id, json_extract(caller, '\$.form') form, json_extract(caller, '\$.baley_session') server_session, json_extract(caller, '\$.host_session') native_session, json_extract(caller, '\$.working_directory') cwd from event where caller is not null order by recorded_at" +``` + +Expect the server's `native_session` to be the value of `CLAUDE_CODE_SESSION_ID` the server was started with, and the hook's to be the `session_id` on its input. The host session id is recorded and never compared: write what each step showed and do not mark a mismatch as a failure. Unless a row's expected column says otherwise, mark these rows `observed`. + +Rows filled: `id.cd`, `id.cd-project`, `id.clear`, `id.branch`, `id.resume-id`, `id.resume`, `id.continue`. + +## Part 10: replay across a restart + +The `continue` launch is a restarted server, and it is the session Part 9 left open. Do the replay now, before any exit in Part 11, because closing that session ends the restarted server the replay needs. In it: + +1. Send `baley_apply capture` with the same request id and text as the earlier overlap capture: `{"operation":"capture","request_id":"12120000-0000-4000-8000-000000000002","kind":"note","text":"overlap A"}`. Expect the original receipt back and the capture count unchanged: `q "select count(*) from event where type = 'capture.recorded' and request_id = '12120000-0000-4000-8000-000000000002'"` prints 1 (`crates/baley/src/mcp/capture.rs`). Row `rep.same-id`. +2. Send the same request id with changed text: `"text":"overlap A changed"`. Expect the refusal `request-id-reuse` and nothing recorded: the count is still 1. Row `rep.changed-input`. +3. Stale expected observations do not apply: no served operation carries one (design 0012). Row `rep.stale-expected` is prefilled. + +## Part 11: exits + +Every server except the stderr-control server of Part 13 writes its standard error to `results/server-stderr/.log` through the launch file, so there is a file to read. Whether Claude Code keeps or shows that output on its own is a separate question, which the stderr-control session answers. Each file starts with a `start` line, and a server that exits cleanly adds exactly one `baley: exit checkpoint ...` line (`crates/baley/src/mcp/serve.rs`). No idle checkpoint exists in the code, so none is expected. + +1. Exit overlap, three times. Session B is the session that stays. Each round: start a fresh `session-a` launch, then ask session B for: `Call baley_apply capture four times, one after the other, with the four request ids I give you, kind note and the text "overlap round R item N", running git commit --allow-empty -m probe in Bash between each (denied).` The ids end in 51 to 54 in round 1, 61 to 64 in round 2 and 71 to 74 in round 3. While session B is working, type `/exit` in the new session A. Expect session B to keep making progress, and the guard's elapsed time to stay below 10,000 ms, from `hook-timing.jsonl`. Rows `exit.overlap-1`, `exit.overlap-2` and `exit.overlap-3`, and the summary `ctl.contention-exit`. +2. The burst at exit. Use the `continue` session left open in Part 9 and used for the replay in Part 10 (start the `continue` launch again if you closed it). Ask: `Start five subagents in parallel, each calling baley_apply capture with its own request id, 12120000-0000-4000-8000-000000000081 to 85, kind note, text "burst N", and call 86 yourself with kind note and text "burst 6".` As soon as the calls start, type `/exit`. Expect every call the server read to be answered (`server-overloaded` at worst) and the drain line `baley: work still open after 10 seconds was left to SQLite's rollback` in the stderr file only when the 10-second bound passed (ADR 0034). The ledger shows which of 81 to 86 were recorded, and the session transcript may show the answers the agents saw. A call that was never read leaves nothing in either place, so say what you could and could not see. Row `exit.burst`. +3. Close session B last, and every other session you still have open. + +After the last session has exited, read each file: `for f in $OUT/server-stderr/*; do echo "-- $f"; cat "$f"; done`. Fill one row per exiting server with its file name and its single exit line. A file with two exit lines, or none after a normal `/exit`, is a failure. Any extra file (the seed session of Part 9, or a server restarted after `/cd`, `/clear` or `/branch`) goes on `exit.other-servers`. Row `exit.no-idle-checkpoint`: no file holds a checkpoint line before the session's exit. + +| Launch | Row | +|---|---| +| `session-a` | `exit.session-a` | +| `session-b` | `exit.session-b` | +| `resume-id` | `exit.resume-id` | +| `resume` | `exit.resume` | +| `continue` | `exit.continue` | +| `invalid-project` | `exit.invalid-project` | +| `missing-project` | `exit.missing-project` | +| `no-session-id` | `exit.no-session-id` | +| `fork` | `exit.fork` | +| `nearer-file` | `exit.nearer-file` | +| `absent-sandbox` | `exit.absent-sandbox` | +| `fallback` | `exit.fallback` | +| `search-tools` | `exit.search-tools` | + +## Part 12: the variant sessions + +Each starts from the launch the script printed, runs the requests in the table and exits. + +| Launch | Request | Expect | +|---|---|---| +| `invalid-project` | `baley_apply capture` request id `...91`, kind `note`, text `invalid project`, then `baley_query document` for any capture id | each `failed`, code `project-context-invalid`, place `CLAUDE_PROJECT_DIR` (`crates/baley/src/mcp/gate.rs`) | +| `invalid-project` | `baley_version`, `baley_query` `help`, `schema` (tool `apply`, for `capture`) and `instruction` (`bal-help`) | all four answer | +| `missing-project` | the same capture (request id `...92`, kind `note`, text `missing project`) and document | each `failed`, code `project-context-missing`, place `CLAUDE_PROJECT_DIR` | +| `missing-project` | the same four calls | all four answer | +| `fork` | `baley_apply capture` request id `...93`, kind `note`, text `fork` | refused, code `project-id-conflict`: the fork shares project one's id under another remote (`crates/baley-core/src/checkout/judge.rs`) | +| `no-session-id` | `baley_apply capture` request id `...94`, kind `note`, text `no session id` | recorded, with no `host_session` in its caller | +| `nearer-file` | `baley_apply capture` request id `...03`, kind `note`, text `nearer file` | recorded in project one: this launch starts in `projects/one/sub` and sets the server's `CLAUDE_PROJECT_DIR` to that folder, which holds no `baley.toml`, so the server has to walk up to project one's file (design 0003 CFG-R3 and CFG-R4, `crates/baley/src/discovery.rs`) | + +The nearer-file check needs the capture's project and its caller: `q "select project_id, request_id, json_extract(caller, '\$.project_directory') from event where request_id = '12120000-0000-4000-8000-000000000003'"`. Expect project one's id and a `project_directory` ending in `/sub`. The registration's `env` sets that variable for this launch whatever Claude Code exports, so a binary that no longer walks up from a sub folder fails here and cannot pass by luck. Row `hand.nearer-file`. + +An `env` entry on a registration sets `CLAUDE_PROJECT_DIR` but cannot remove it, and an empty value is judged invalid and not missing (`crates/baley/src/mcp/context.rs`). So the invalid and fork variants use the registration's `env`, and the missing-project and no-session-id variants unset the variable in the launch command. Record that the registration's `env` did override the variable Claude Code sets, since the invalid and fork rows depend on it. If it did not, record those rows `unverified` with what you saw. + +Rows filled: `var.invalid.project-calls`, `var.invalid.free-calls`, `var.missing.project-calls`, `var.missing.free-calls`, `var.fork`, `id.absent-native`, `hand.nearer-file`. + +## Part 13: the absent sandbox, the `.git/HEAD` fallback and the stderr control + +1. Start the `absent-sandbox` launch. Its `PATH` holds no `bwrap` and no `socat`. Ask for Bash `cat $ROOT/projects/one/README.md`. Expect Claude Code to refuse to run the command and not to run it unsandboxed, because the settings require the sandbox (`failIfUnavailable`, ADR 0033). Record the message. Row `prot.absent-sandbox`. Exit. +2. Start the `fallback` launch. Its `PATH` holds no `git`. Git runs its own subcommands without `GIT_EXEC_PATH`, so emptying the exec folder would not make it fail; removing it from `PATH` does. Ask for Bash `git commit --allow-empty -m fallback` on `main`. The command itself then fails with `git: command not found`. The question is the guard. Expect a name read from `.git/HEAD` never to decide refuse or ask (design 0010 GRD-R6 and GRD-R14): with git unreadable the guard passes with a loud standard-error line and records a guard failure, because `git.guard_hard_fail` is off. +3. Read what happened: the loud line, if there is one, is in `results/hook-calls/.json.err` (`` is the `call` key of the timing line, the call id and the wrapper's process id), and the guard answers are in the reads. If the `.err` file has no loud line, git still answered inside the hook (the hook did not get the narrowed `PATH`). Then mark `ctl.fallback-head` `unavailable` and cite the unit test that owns the decision: `a_head_file_name_after_git_failed_read_as_the_git_branch_is_caught` in `crates/baley/src/guard_hook/branch.rs`. +4. Where the loud line shows. Look for it in the `.err` file, in the session's own output and in `results/debug-fallback.log`. Claude Code sends a hook's standard error on exit 0 to its debug log only, per the rule's own note. Record which of the three held it. Row `ctl.stderr-line`. +5. The stderr control. Every other server sends its standard error to a file, so none of them can show whether Claude Code displays that output itself. Start the `stderr-control` launch in `projects/one`. Its entry runs a small script that writes the line `baley-live stderr control marker, pid ` to its own standard error and then executes the server with nothing redirected. Ask for `baley_version`, then `/exit`. Then look for the marker, and for a `baley: exit checkpoint` line from the server, in three places: the terminal while the session ran (`/mcp` too), the debug log (`grep -n 'stderr control marker\|exit checkpoint' $OUT/debug-stderr-control.log`) and `results/server-stderr` (expected to hold nothing for this server, since it is not redirected). Write for each place whether the marker was held, and the same for the exit line. Row `ctl.server-stderr-visible`. The exit checkpoint line appears only if the server exits cleanly when the session ends, so record whether it did. + +Rows filled: `prot.absent-sandbox`, `ctl.fallback-head`, `ctl.stderr-line`, `ctl.server-stderr-visible`. + +## Part 14: the hand-offs + +Most of these read what earlier builds left in the tree. + +- `hand.init`: `cat $OUT/init-one.txt $OUT/init-one.status`. Expect exit status 0, `baley.toml` written and the project recorded. The script ran `baley init` at project one's root and kept its output. +- `hand.config-show`: `cd $ROOT/projects/one && env -u BALEY_HOME XDG_DATA_HOME=$ROOT/data XDG_CONFIG_HOME=$ROOT/config $ROOT/bin/baley config show`. Expect every setting with its layer, including `git.on_protected` set to `refuse` by the project file, and the Claude Code host sections. +- `hand.checkout-admission`: `q "select project_id, seq, type from event where type like 'checkout.%' or stream like 'command/checkout%'"`. Expect project one's checkout admitted when `baley init` ran. The fork was never initialised, so it holds no row of its own: its refusal is `var.fork`. +- `hand.restore-doctor`: the `baley doctor` section of `reads.txt` (Part 15), taken before the restore part. No restore was run on that ledger yet, so expect no finding. +- `hand.restore-report`: `sh spikes/host-matrix/live-claude-restore.sh > $OUT/restore.txt`, run after `reads.txt` is saved and after every session has exited (Part 15 step 2). It changes the ledger, which is why the reads come first and are not run again afterwards. Its sections are, in order: project one's `git.remote` set to `origin` and committed on `main`; `baley anchor`; a copy of `baley.db` taken with SQLite's backup API; `baley anchor` again, and once more only if no anchor names a sequence above the copy's head (an anchor names the sequence the chain had before its own events, so the first one after the copy names the copy's head and no more), then the remote's anchor tags and the highest `anchor.pushed` sequence in the ledger; the restore of the copy over `baley.db`; then, from project one, the anchored `baley verify`, `baley acknowledge-restore`, the same again, the anchored `baley verify`, `baley verify --local-only` for project one and `baley doctor`, each followed by its exit status. Expected, from design 0001 (Acknowledging a restore, and the verification paragraphs) and ADR 0035: the first anchored verify reports the chain truncated before the remote anchor and prints no line containing `purges recorded in history`; the acknowledgement succeeds and prints that warning; the verify, the local-only verify and doctor that follow list the accepted restore and print the warning; the warning changes no exit code. Record the repeat of the acknowledgement as printed. Design 0001 says a replayed acknowledgement carries the warning too, and the command line cannot send the same request id twice, so write what the second run printed and its exit status, and do not adjust it to match. If the script stops because no anchor names a sequence above the copy's head, paste its message and the sections above it. +- `hand.keys-detection`: not run. Build 4 (#25) removes the `keys.env` reader, the credential wrapper and the model lister this row would exercise, so the row is prefilled `unavailable` with that reason. No step of this procedure touches your real config folder. + +Rows filled: `hand.init`, `hand.config-show`, `hand.checkout-admission`, `hand.restore-doctor`, `hand.restore-report`, `hand.keys-detection`. + +## Part 15: after the run + +Every session has exited. Do not run `live-claude.sh` again: it clears the tree, results included. + +1. `sh spikes/host-matrix/live-claude-reads.sh > $OUT/reads.txt`. It opens the ledger read-only. Read `reads.txt` and fill the rows below from it. +2. With every session closed and `reads.txt` saved, run `sh spikes/host-matrix/live-claude-restore.sh > $OUT/restore.txt` once. Read `restore.txt` and fill `hand.restore-report` from it. Do not run the reads again after this step: the ledger is now the copy plus the acknowledgement. +3. Keep `$ROOT` as it is until the record is committed. + +| Rows | Read from `reads.txt` | +|---|---| +| `post.verify-one`, `post.views-one`, `post.verify-user`, `post.views-user`, `post.doctor` | the exit status after each `verify --local-only`, `verify --views` and `doctor` section, expected 0 | +| `post.stream-versions` | the stream spans; the section for streams that do not start at 1 or whose count differs from the span is empty | +| `post.captures-once`, `post.no-loss` | the three kinds of id in the Request ids section. One `capture.recorded` per id expected to be recorded, each with its caller and instruction evidence. None for 91, 92 and 93. For 81 to 86 and any request an exit cut off before the server read it, one or none, never two. A missing id of the first kind is a loss; a missing id of the second or third kind is not | +| `post.text-equal` | the stored text equal to the text each row sent. For request id `...21`, the stored body's byte count and SHA-256 after `zstd -d` equal those of `fixtures/large.txt` printed beside it | +| `post.real-folders` | the `real-home against pins.txt` and `real-config against pins.txt` sections say `no difference` | +| `post.real-claude` | the `claude command`, `~/.local/bin/claude link` and `real-claude-versions` sections each say `no difference`, and the line after them says neither the command nor the launcher resolves inside the disposable root. On `DIFFERENT`, repoint your launcher to the target `pins.txt` recorded before anything else (the `claude-launcher` and `claude-launcher-resolved` lines), because `live-claude.sh` refuses to clear the root while the launcher resolves into it. The listing of other entries under the disposable `data` and `config` folders is evidence only: in an earlier run it held `claude/versions`, the updater's copy | +| `ctl.hook.bash`, `ctl.hook.monitor`, `ctl.hook.powershell`, `ctl.hook.read`, `ctl.hook.grep`, `ctl.hook.glob`, `ctl.hook.write`, `ctl.hook.edit`, `ctl.hook.notebookedit` | the hook timing section: a call count for each tool, 9 tools, with Monitor and PowerShell unavailable when the pins say so and Grep and Glob unavailable as a host limit when the `search-tools` session's tool list lacked them (Part 3). Write, Edit and NotebookEdit count only the calls from the hook-firing requests in Part 5 | +| `ctl.grep-parent-guard`, `ctl.glob-parent-guard`, `ctl.write-baleytoml-denied` | the guard answers: a `deny` for tool Grep and for tool Glob, `unavailable` as a host limit where the `search-tools` session's tool list lacked the tool (Part 3). For `ctl.write-baleytoml-denied` a `deny` for tool Write appears only if the hook ran (see Part 4) | +| `ctl.latency` | the highest elapsed milliseconds and the highest wrapper milliseconds in the hook timing section, both below 10,000. The highest overhead figure shows what the wrapper added around the guard, which the host's timer counts too | +| `ctl.timeout-not-denial` | any finished call at or above 10,000 ms, and any call in the section of calls that started and did not finish (a start line with no timing line is what a call the host killed at the timeout leaves, or a call still running when the reads ran), and what Claude Code did for each. `unavailable` when there are none | +| `ctl.redelivery` | any `tool_use_id` seen more than once. `unavailable` when none | +| `id.mcp-revision` | not in the ledger or in `reads.txt`. Look in the debug logs: `grep -n -i 'protocolVersion\|protocol version' $OUT/debug-*.log`. Record the revision and the log it came from. When no log shows it, write `not observed` and name the logs you searched. The server supports `2025-11-25` and `2026-07-28` (`crates/baley/src/mcp/tools.rs`) | + +Return the filled `$OUT/observations.md`, `$OUT/reads.txt`, `$OUT/restore.txt` and the results folder, and say the date of the run. The sheet is checked for a mark on every row before it is recorded. + +## What the run settles about the host + +These are the questions the documentation leaves open. The sheet's rows answer them, and the record should state each plainly: + +- Which MCP revision Claude Code negotiated, and where it was read (`id.mcp-revision`). +- Whether a stdio server's standard error is visible anywhere without the redirect in the launch file. The `stderr-control` session answers it by looking in the terminal, `/mcp` and the debug log (`ctl.server-stderr-visible`). The redirect in every other launch is a fixture, not a launcher. +- Whether the Grep and Glob tools ran on the installed version and through which launch: the `search-tools` session with `--allowedTools Grep,Glob` (`exit.search-tools`, `fld.grep`, `fld.glob`, `ctl.hook.grep`, `ctl.hook.glob`), or a host limit with the bundle read of Part 0 step 9 as the evidence. +- The skills layout: whether a `SKILL.md` in an uncommitted folder is listed and runnable in both places a skill can live, the isolated configuration (`hand.skill-listed`, `hand.skill-run`) and project one's own folder (`hand.skill-project-listed`, `hand.skill-project-run`), and which source the listing names for each. +- What `/cd`, `/clear`, `/branch`, `--resume `, `--resume` and `--continue` do to the server and its native session id (the `id.*` rows). +- Whether an `env` entry on a registration overrides `CLAUDE_PROJECT_DIR` (the invalid-project and fork rows). +- Whether a hook's loud standard-error line reaches the owner (`ctl.stderr-line`). diff --git a/spikes/host-matrix/live-claude.sh b/spikes/host-matrix/live-claude.sh new file mode 100644 index 00000000..60d072dd --- /dev/null +++ b/spikes/host-matrix/live-claude.sh @@ -0,0 +1,746 @@ +#!/bin/sh +# Live Claude Code qualification, preparation side. Given a built baley, it builds a disposable +# tree under ~/.local/share/baley-live, renders the real binary's artifacts into launch files, writes +# the observation sheet and prints the exact launch of every session in live-claude.md. It never +# starts Claude Code: the owner runs every session by hand. +set -u + +if [ $# -ne 1 ]; then + echo "usage: sh live-claude.sh " >&2 + exit 1 +fi +SUPPLIED=$1 +case "$SUPPLIED" in + /*) ;; + *) echo "the baley path must be absolute: $SUPPLIED" >&2; exit 1 ;; +esac +if [ ! -f "$SUPPLIED" ] || [ ! -x "$SUPPLIED" ]; then + echo "not an executable file: $SUPPLIED" >&2; exit 1 +fi + +# BALEY_HOME collapses home and config into one folder, so the two-folder run cannot use it. +if [ -n "${BALEY_HOME+set}" ]; then + echo "BALEY_HOME is set. Unset it: it makes home and config one folder, and this run needs two." >&2 + exit 1 +fi + +# An inherited GIT_ variable can pick another repository, work tree, index or object store, or point the +# config writes at the owner's files, so git would act outside the disposable repositories. None of them +# reaches this script's git or baley commands. Only GIT_EXEC_PATH stays, because it says where git lives. +for NAME in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do + [ "$NAME" = GIT_EXEC_PATH ] || unset "$NAME" +done + +# The paths go into JSON and shell commands unquoted, so only plain path characters are allowed. +case "${HOME:-}" in + ""|/) echo "HOME must be a real directory" >&2; exit 1 ;; + /*) ;; + *) echo "HOME must be absolute" >&2; exit 1 ;; +esac +case "$HOME" in + *[!A-Za-z0-9._/-]*|*crenshawdev*) echo "HOME holds a character or name this script refuses" >&2; exit 1 ;; +esac + +for TOOL in jq git; do + command -v "$TOOL" >/dev/null 2>&1 || { echo "$TOOL is required and is not on PATH" >&2; exit 1; } +done + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +GUIDE="$SCRIPT_DIR/live-claude.md" + +ROOT="$HOME/.local/share/baley-live" +DATA="$ROOT/data" +CONF="$ROOT/config" +HOMEF="$DATA/crenshawdev/baley" +CONFF="$CONF/crenshawdev/baley" +BIN="$ROOT/bin/baley" +CC="$ROOT/claude-config" +PROJ="$ROOT/projects" +REM="$ROOT/remotes" +OUT="$ROOT/results" +REN="$OUT/rendered" + +# Clearing the root deletes everything under it, so a claude command that lives there would be deleted +# with it. Both ways the owner's claude command is found are resolved first, and the script stops before it +# clears or creates anything when either one lies under the root. It never relinks the launcher. +under_root() { + case "$1" in + "$ROOT"|"$ROOT"/*|"$ROOT_RESOLVED"|"$ROOT_RESOLVED"/*) return 0 ;; + esac + return 1 +} +ROOT_RESOLVED="$(readlink -f "$HOME" 2>/dev/null || echo "$HOME")/.local/share/baley-live" +CLAUDE_FOUND=$(command -v claude 2>/dev/null || true) +CLAUDE_RESOLVED="" +case "$CLAUDE_FOUND" in /*) CLAUDE_RESOLVED=$(readlink -f "$CLAUDE_FOUND" 2>/dev/null || true) ;; esac +LAUNCHER_RESOLVED="" +if [ -e "$HOME/.local/bin/claude" ] || [ -L "$HOME/.local/bin/claude" ]; then + LAUNCHER_RESOLVED=$(readlink -f "$HOME/.local/bin/claude" 2>/dev/null || true) +fi +if under_root "$CLAUDE_RESOLVED" || under_root "$LAUNCHER_RESOLVED"; then + echo "your claude command resolves inside the disposable root, so clearing it would delete the binary your claude runs:" >&2 + echo " command -v claude: ${CLAUDE_FOUND:-none}, resolved to ${CLAUDE_RESOLVED:-nothing}" >&2 + echo " $HOME/.local/bin/claude resolved to ${LAUNCHER_RESOLVED:-nothing}" >&2 + echo " root: $ROOT" >&2 + echo "Point your claude command at a file outside the root first. This script repairs nothing and relinks nothing." >&2 + exit 1 +fi + +# The only deletion in this script: the tree has to be exactly the root named above, with no +# symbolic link from HOME down to it, so the delete cannot land somewhere else, and owned by this user. +fresh() { + case "$1" in + "$HOME/.local/share/baley-live") ;; + *) echo "refusing to clear $1" >&2; exit 1 ;; + esac + P="$HOME" + for PART in $(printf '%s\n' "${1#"$HOME"/}" | tr '/' ' '); do + P="$P/$PART" + [ -L "$P" ] && { echo "refusing to clear $1: $P is a symbolic link" >&2; exit 1; } + done + if [ -e "$1" ] && [ -z "$(find "$1" -prune -user "$(id -u)")" ]; then + echo "refusing to clear $1: it is not owned by $(id -un)" >&2; exit 1 + fi + rm -rf "$1" && mkdir -p "$1" +} + +# Refuse a moved help spelling here, before anything is cleared or created. Each block of the +# procedure sits under a heading naming its command. +SCRATCH=$(mktemp -d) || exit 1 +trap 'rm -rf "$SCRATCH"' EXIT +help_block() { + awk -v heading="### \`$1\`" ' + $0 == heading { found = 1; next } + found && /^```/ { if (open) exit; open = 1; next } + found && open { print } + ' "$GUIDE" +} +[ -f "$GUIDE" ] || { echo "missing $GUIDE" >&2; exit 1; } +for SUB in "" manifest stub registration hook settings; do + CMD="baley artifact${SUB:+ $SUB} --help" + help_block "$CMD" > "$SCRATCH/copied" + # shellcheck disable=SC2086 + "$SUPPLIED" artifact $SUB --help > "$SCRATCH/actual" 2>&1 + if [ ! -s "$SCRATCH/copied" ] || ! diff "$SCRATCH/copied" "$SCRATCH/actual" > "$SCRATCH/difference"; then + echo "the help of '$CMD' differs from the copy in live-claude.md (left: copy, right: this binary)" >&2 + cat "$SCRATCH/difference" >&2 + echo "Rebuild baley at the commit the procedure names, or update the procedure first." >&2 + exit 1 + fi +done + +# The owner's real Baley folders as the binary resolves them (folders.rs), listed now and again +# by live-claude-reads.sh, so a hook or server that missed the exported variables shows up as a change. +case "${XDG_DATA_HOME:-}" in /*) REAL_HOME="$XDG_DATA_HOME/crenshawdev/baley" ;; *) REAL_HOME="$HOME/.local/share/crenshawdev/baley" ;; esac +case "${XDG_CONFIG_HOME:-}" in /*) REAL_CONF="$XDG_CONFIG_HOME/crenshawdev/baley" ;; *) REAL_CONF="$HOME/.config/crenshawdev/baley" ;; esac +# Claude Code keeps its installed versions beside its data, and its launcher is a link in ~/.local/bin. Both are +# recorded from the starting environment, so a run that moved the launcher or added a version shows up afterwards. +case "${XDG_DATA_HOME:-}" in /*) REAL_VERSIONS="$XDG_DATA_HOME/claude/versions" ;; *) REAL_VERSIONS="$HOME/.local/share/claude/versions" ;; esac +LAUNCHER="$HOME/.local/bin/claude" +if [ -L "$LAUNCHER" ]; then + LAUNCHER_TEXT="link to $(readlink "$LAUNCHER")" +elif [ -e "$LAUNCHER" ]; then + LAUNCHER_TEXT="not a link" +else + LAUNCHER_TEXT="absent" +fi +list_real() { + if [ -d "$1" ]; then + find "$1" -printf '%p %y %s %TY-%Tm-%Td %TH:%TM:%TS\n' | sort + else + echo absent + fi +} + +umask 077 +# A clear that fails part way can leave a link behind that the writes below would follow out of the +# tree, so a failed clear stops the script before any write. +fresh "$ROOT" || { echo "could not clear $ROOT, so nothing was written" >&2; exit 1; } +mkdir -p "$HOMEF" "$CONFF" "$ROOT/bin" "$CC" "$PROJ" "$REM" "$OUT" "$REN" \ + "$OUT/server-stderr" "$OUT/hook-calls" "$ROOT/bin-nosandbox" "$ROOT/bin-nogit" || exit 1 + +# Harmless fixtures in both folders. The key value is fake on purpose, and no real credential enters +# either folder. +for D in "$HOMEF" "$CONFF"; do + echo seed > "$D/seed.txt" + echo "FAKE_KEY=not-a-real-key-0000" > "$D/keys.env" + printf '%s\n' '{"cells":[{"cell_type":"code","execution_count":null,"id":"c1","metadata":{},"outputs":[],"source":["x = 1"]}],"metadata":{},"nbformat":4,"nbformat_minor":5}' > "$D/notebook.ipynb" + chmod 600 "$D/seed.txt" "$D/keys.env" "$D/notebook.ipynb" +done +# A script for the steps that check a command's child process. +cat > "$ROOT/child.sh" <<'EOF' +#!/bin/sh +case "$1" in + read) cat "$2" ;; + write) echo child > "$2" ;; +esac +EOF + +# Every rendered artifact names this copy, so no step ever writes toward a build folder. +cp "$SUPPLIED" "$BIN" && chmod 700 "$BIN" + +# The two folders of commands for the sessions that run with a narrowed PATH. git runs its own +# subcommands without GIT_EXEC_PATH, so removing the exec folder would not make it fail: the +# fallback session has no git at all instead. +link_cmds() { + DIR=$1; shift + for NAME in "$@"; do + FOUND=$(command -v "$NAME" 2>/dev/null || true) + case "$FOUND" in + /*) ln -s "$FOUND" "$DIR/$NAME" ;; + *) echo "WARNING: $NAME is not on PATH, so $DIR has no $NAME." ;; + esac + done + if [ -n "${SHELL:-}" ] && [ -x "$SHELL" ]; then ln -sf "$SHELL" "$DIR/$(basename "$SHELL")"; fi +} +link_cmds "$ROOT/bin-nosandbox" sh env git cat ls jq claude +link_cmds "$ROOT/bin-nogit" sh env cat ls jq claude bwrap socat date + +cat > "$OUT/pins.txt" </dev/null || echo absent)" >> "$OUT/pins.txt" +done +if [ -n "${DISABLE_TELEMETRY:-}" ] || [ -n "${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-}" ]; then + echo "telemetry-switch: set (Monitor is unavailable)" >> "$OUT/pins.txt" +else + echo "telemetry-switch: unset" >> "$OUT/pins.txt" +fi +{ + echo "checkout-commit: $(git -C "$SCRIPT_DIR" rev-parse HEAD)" + DIRTY=$(git -C "$SCRIPT_DIR" status --porcelain) + if [ -n "$DIRTY" ]; then + echo "checkout-status: DIRTY, the binary may not match the commit" + echo "$DIRTY" + else + echo "checkout-status: clean" + fi + echo "binary: $("$BIN" --version)" + echo "binary-sha256: $(sha256sum "$BIN" | cut -d' ' -f1)" + echo "begin real-home $REAL_HOME" + list_real "$REAL_HOME" + echo "end real-home" + echo "begin real-config $REAL_CONF" + list_real "$REAL_CONF" + echo "end real-config" + echo "claude-command: ${CLAUDE_FOUND:-none}" + echo "claude-command-resolved: ${CLAUDE_RESOLVED:-none}" + echo "claude-launcher: $LAUNCHER_TEXT" + echo "claude-launcher-resolved: ${LAUNCHER_RESOLVED:-none}" + echo "begin real-claude-versions $REAL_VERSIONS" + list_real "$REAL_VERSIONS" + echo "end real-claude-versions" +} >> "$OUT/pins.txt" +grep -q '^checkout-status: DIRTY' "$OUT/pins.txt" \ + && echo "WARNING: the checkout is dirty, so the binary may not match the commit pins.txt names." + +# From here on this script's own baley and git commands see the disposable folders only. +XDG_DATA_HOME="$DATA" +XDG_CONFIG_HOME="$CONF" +export XDG_DATA_HOME XDG_CONFIG_HOME + +# The disposable repositories. Each carries its own identity and no signing, so the commits the agent +# is allowed to make depend on neither a global identity nor the owner's signing agent. Nothing here +# touches the global git config or this repository's. +mkrepo() { + git init -q -b main "$1" || exit 1 + git -C "$1" config --local user.name "Baley live run" + git -C "$1" config --local user.email "live-run@example.invalid" + git -C "$1" config --local commit.gpgsign false +} +project_id() { + sed -n 's/^id = "\(.*\)"$/\1/p' "$1/baley.toml" +} +# Ties a repository to a ledger project, keeps the command's output and exit status for the +# owner-init row, then commits the setting the guard reads: a commit on main is refused. +init_project() { + NAME=$1 + ( cd "$PROJ/$NAME" && "$BIN" init ) > "$OUT/init-$NAME.txt" 2>&1 + STATUS=$? + echo "$STATUS" > "$OUT/init-$NAME.status" + if [ "$STATUS" -ne 0 ]; then + echo "baley init failed in $PROJ/$NAME (exit $STATUS):" >&2 + cat "$OUT/init-$NAME.txt" >&2 + exit 1 + fi + printf '\n[git]\non_protected = "refuse"\n' >> "$PROJ/$NAME/baley.toml" + git -C "$PROJ/$NAME" add baley.toml + git -C "$PROJ/$NAME" commit -q -m "refuse commits on a protected branch" +} + +git init -q --bare -b main "$REM/one.git" || exit 1 +git init -q --bare -b main "$REM/fork.git" || exit 1 + +# Project one: the start folder of the user-scope session is its tracked sub folder, so the project and +# the working directory differ. fixtures/large.txt is plain ASCII of 33,000 bytes, a capture whose +# document read arrives in parts, the first a full 24,576 bytes. +mkrepo "$PROJ/one" +mkdir -p "$PROJ/one/sub" "$PROJ/one/fixtures" +echo "a tracked file in a sub folder" > "$PROJ/one/sub/note.txt" +echo "project one" > "$PROJ/one/README.md" +awk 'BEGIN { for (i = 1; i <= 600; i++) printf "line %04d: the quick brown fox jumps over the lazy dog\n", i }' > "$PROJ/one/fixtures/large.txt" +git -C "$PROJ/one" add -A +git -C "$PROJ/one" commit -q -m "initial" +git -C "$PROJ/one" remote add origin "$REM/one.git" +init_project one + +# Project two is the /cd target: initialised the same way, with no remote. +mkrepo "$PROJ/two" +echo "project two" > "$PROJ/two/README.md" +git -C "$PROJ/two" add -A +git -C "$PROJ/two" commit -q -m "initial" +init_project two + +# The fork is a clone of project one whose origin is another remote and which is not initialised +# again, so it shares project one's id under a different remote. +git clone -q "$PROJ/one" "$PROJ/fork" || exit 1 +git -C "$PROJ/fork" config --local user.name "Baley live run" +git -C "$PROJ/fork" config --local user.email "live-run@example.invalid" +git -C "$PROJ/fork" config --local commit.gpgsign false +git -C "$PROJ/fork" remote set-url origin "$REM/fork.git" + +ID_ONE=$(project_id "$PROJ/one") +ID_TWO=$(project_id "$PROJ/two") +{ + echo "project-one-id: $ID_ONE" + echo "project-two-id: $ID_TWO" + echo "project-fork-id: $(project_id "$PROJ/fork")" +} >> "$OUT/pins.txt" + +# The launch files and the placed stub the settings protect. T15's placement map would yield the same +# list, so the Edit rule and denyWrite cover them while the guard itself protects only the two +# baley.toml files until the placement projection is passed to it. +STUB="$CC/skills/bal-help/SKILL.md" +LAUNCH_FILES="mcp-explicit.json user-scope-entry.json mcp-invalid-project.json mcp-missing-project.json mcp-no-session-id.json mcp-fork.json mcp-nearer-file.json mcp-stderr-control.json" + +# Prints one artifact exactly as the binary renders it into $1. A refusal or a partial settings render +# exits non-zero, and then the script stops with the standard error shown, so partial settings are +# never composed. +render() { + TARGET=$1; shift + if ! "$BIN" artifact "$@" > "$TARGET" 2> "$OUT/artifact-stderr.txt"; then + echo "baley artifact $* failed:" >&2 + cat "$OUT/artifact-stderr.txt" >&2 + exit 1 + fi + rm -f "$OUT/artifact-stderr.txt" +} + +# The homes given are the resolved folders, not the XDG roots, because they are what the server, the +# hook and an installed configuration name. +render "$REN/hook.json" hook --executable "$BIN" +set -- settings --executable "$BIN" --home "$HOMEF" --config "$CONFF" \ + --protect "$PROJ/one/baley.toml" --protect "$PROJ/two/baley.toml" \ + --protect "$STUB" --protect "$OUT/settings.json" +for FILE in $LAUNCH_FILES; do set -- "$@" --protect "$OUT/$FILE"; done +render "$REN/settings.json" "$@" +render "$REN/manifest.json" manifest + +# Times each guard call. It keeps the call as it arrived, writes a start line before the guard runs, +# runs the rendered hook command with that call as standard input, passes the guard's output and exit +# status through unchanged, and appends one line to hook-timing.jsonl. A call the host kills at the +# timeout leaves a start line in hook-starts.jsonl and no timing line, and the post-run reads report it. +# elapsed_ms times the guard alone. The host's timer also covers the wrapper, so the timing line adds +# wrapper_ms (from the wrapper's first instruction to just before the line is written) and overhead_ms, +# the difference. The final decision read and the append, a few milliseconds, are not counted. The +# lines hold no tool_input: the stand-in probe is the per-tool field capture. Tool paths are fixed now, +# so a session with a narrowed PATH still times its calls. +JQ_BIN=$(command -v jq) +DATE_BIN=$(command -v date) +CAT_BIN=$(command -v cat) +MV_BIN=$(command -v mv) +cat > "$ROOT/bin/guard-timed.sh" <> "$ROOT/bin/guard-timed.sh" <<'WRAP' +TW0=$($DATE +%s%N) +PENDING="$OUT/hook-calls/pending.$$" +$CAT > "$PENDING" +TOOL=$($JQ -r '.tool_name // empty' "$PENDING" 2>/dev/null) +ID=$($JQ -r '.tool_use_id // empty' "$PENDING" 2>/dev/null) +SESSION=$($JQ -r '.session_id // empty' "$PENDING" 2>/dev/null) +CWD=$($JQ -r '.cwd // empty' "$PENDING" 2>/dev/null) +# The call id names a file, so only plain characters build the path. Anything else, or a long id, +# uses a fixed name. The timing lines still hold the id as it arrived. +case "$ID" in + ""|*[!A-Za-z0-9_-]*) NAME=none ;; + *) if [ "${#ID}" -gt 100 ]; then NAME=none; else NAME=$ID; fi ;; +esac +KEY="$NAME-$$" +CALL="$OUT/hook-calls/$KEY.json" +$MV "$PENDING" "$CALL" +COMMAND=$($JQ -r '.hooks.PreToolUse[0].hooks[0].command' "$OUT/rendered/hook.json") +$JQ -nc --arg tool "$TOOL" --arg id "$ID" --arg session "$SESSION" --arg cwd "$CWD" --arg key "$KEY" \ + --argjson start "$((TW0 / 1000000))" \ + '{tool_name: $tool, tool_use_id: $id, session_id: $session, cwd: $cwd, call: $key, wrapper_start_ms: $start}' >> "$OUT/hook-starts.jsonl" +T0=$($DATE +%s%N) +/bin/sh -c "$COMMAND" < "$CALL" > "$CALL.out" 2> "$CALL.err" +STATUS=$? +T1=$($DATE +%s%N) +DECISION=$($JQ -r '.hookSpecificOutput.permissionDecision // empty' "$CALL.out" 2>/dev/null) +TW1=$($DATE +%s%N) +$JQ -nc --arg tool "$TOOL" --arg id "$ID" --arg session "$SESSION" --arg cwd "$CWD" --arg key "$KEY" \ + --argjson start "$((T0 / 1000000))" --argjson end "$((T1 / 1000000))" \ + --argjson elapsed "$(((T1 - T0) / 1000000))" --argjson wrapper "$(((TW1 - TW0) / 1000000))" \ + --argjson overhead "$((((TW1 - TW0) - (T1 - T0)) / 1000000))" --argjson status "$STATUS" --arg decision "$DECISION" \ + '{tool_name: $tool, tool_use_id: $id, session_id: $session, cwd: $cwd, call: $key, start_ms: $start, end_ms: $end, elapsed_ms: $elapsed, wrapper_ms: $wrapper, overhead_ms: $overhead, exit: $status} + + (if $decision == "" then {} else {permissionDecision: $decision} end)' >> "$OUT/hook-timing.jsonl" +$CAT "$CALL.out" +$CAT "$CALL.err" >&2 +exit "$STATUS" +WRAP +chmod 700 "$ROOT/bin/guard-timed.sh" + +# The settings the sessions load: the rendered settings and the rendered hook with two kinds of change. +# The hook command is the timed wrapper, with its matcher and timeout as rendered. The fixture keys are +# allowWrite over the disposable tree, so a refused write to the home or config comes from denyWrite and +# not Claude Code's default boundary (an allow entry above a protected folder is not a gap to the +# coverage judge, crates/baley/src/host_artifacts/coverage.rs), and the PowerShell switch. +jq -n --slurpfile settings "$REN/settings.json" --slurpfile hook "$REN/hook.json" \ + --arg wrapper "'$ROOT/bin/guard-timed.sh'" --arg root "$ROOT" \ + '$settings[0] + $hook[0] + | .hooks.PreToolUse[0].hooks[0].command = $wrapper + | .sandbox.filesystem.allowWrite = [$root] + | .env = {"CLAUDE_CODE_USE_POWERSHELL_TOOL": "1"}' > "$OUT/settings.json" || exit 1 + +# The server side, rendered unchanged. +render "$REN/registration.json" registration --executable "$BIN" +render "$REN/registration-always-load.json" registration --executable "$BIN" --always-load +render "$REN/stub-bal-help.md" stub bal-help + +# Each launch file is one mcpServers object holding the single key baley, so tool names match +# production. The entry runs /bin/sh -c with a script that appends a start line (UTC time, process id) +# to results/server-stderr/.log and then executes the rendered command and arguments with +# standard error appended to that file, because a stdio server's standard error is not otherwise kept. +# The rendered command and arguments ride as positional parameters, so they are never retyped. Every +# other key of the rendered entry, alwaysLoad included, is kept. +server_script() { + cat <