Skip to content

claustrum — shipped ledger

This document records the completed hardening work on claustrum: the CI, test, release, and safety items that changed the daemon from a bare reimplementation into a maintained drop-in. Every item here is done. This document is history, not a backlog.

Most of this work is wire-neutral. It changes tooling, tests, or OS-level behaviour, but it does not change a JSON-RPC frame. The exceptions are the items that knowingly make a frame or a behaviour different from the reference daemon. Those items are the numbered deliberate divergences (the D series) and the client-driven extensions (CT-1..CT-5). They no longer live here.

Deliberate divergences and the decision rules → docs/DIVERGENCES.md

The divergence catalog and THE RULE now live in their own canonical home, DIVERGENCES.md. The catalog holds the D series and CT-1..CT-5. Each entry gives its default, how to activate it, why it exists, and its reopen trigger. THE RULE is the four-rule, clause-(a)/(b)/(c) standard, and every divergence is judged against it. A contributor needs two things most: the byte-identical contract, and the list of places where claustrum departs from that contract on purpose. The split makes both first-class. Before the split, they were at the bottom of a done backlog. PROTOCOL.md gives the per-method wire facts. ARCHITECTURE.md gives the driver-claim provenance.

The shipped items

The impact/cost ratings are H/M/L/S, as the original triage set them. The "where" part of each row points at the artifact that carries the item. The split condensed the full measurement and correction notes out of this table.

# Item I/C What shipped · where
1 CI workflow H/L go vet, gofmt -l, make all, go test gate every PR (.github/workflows/ci.yml).
2 In-repo Go test suite H/M harness_test.go and the integration suites boot the daemon on a temp socket. They assert each method's frames over the real wire. Thus CI gates compatibility without the reference binary.
3 Golden-frame fixtures H/M The tests assert testdata/socket_*.golden.json byte-equal. To regenerate them, run go test -run Socket -update.
4 Atomic -install extract M/L ensureCLI stages the file at .fetch-<random>, not at <cliPath>.tmp, so the orphan sweep can reclaim it. ensureCLI then renames the file into place with os.Rename. An interrupted install never leaves a half-written cliPath. The end state and the __INSTALL_RESULT__ facts stay byte-compatible.
5 Timeouts on git/exec calls M/L Wrapped git and the linux-only ldd libc probe in exec.CommandContext. Both halves became numbered divergences: D5 (gitTimeout) and D14 (ldd probe). D5 is opt-in and off by default. D14 is retired, because the reference now bounds the ldd probe at 5 s itself. See DIVERGENCES.md.
6 pre-commit + gofmt/vet hooks M/L A zero-dependency hook lives in .githooks/. make hooks installs it. It uses the same lint order as CI. --no-verify bypasses it.
7 go vet-clean + staticcheck in CI M/L The CI lint job runs golangci-lint (.golangci.yml: staticcheck + govet + errcheck + ineffassign + unused + misspell/unconvert).
8 Bounded replay buffer (ring) M-H/M A cap of 16 MiB of serialized frame bytes applies to each per-process buffer. The count includes each frame's JSON line and its trailing newline. The oldest frames drop, and firstSeq advances. The cap is parity, not tuning. The reference's 16 MiB was measured, and firstSeq is wire-visible. Measure again before you change it.
9 stdin backpressure M/M A per-proc stdinWriter goroutine drains a bounded FIFO (stdinQueueCap, 16 MiB). process.stdin puts the data in the queue and returns immediately, which matches the reference's async behaviour. A full queue is rejected with -32002 stdin backpressure: queue full, not blocked. That is parity with 4534d86, measured with the probe scratch/probe/stdincap. The cap and the error are wire-visible.
10 Fuzz the JSON-RPC parser M/L-M fuzz_test.go adds FuzzDispatch and FuzzBindParams. FuzzDispatch skips the side-effectful methods. CI runs the seeds.
11 Release automation H/M .goreleaser.yaml + release.yml: 6-target builds, checksums, syft SBOM, cosign signing, SLSA provenance. knope opens the version PRs (changesets-only).
12 Pin the Go toolchain M/L go.mod carries an explicit toolchain directive. CI and release use go-version-file: go.mod, so builds are reproducible against a known patch.
13 Structured/leveled logging M/L-M logging.go holds a tiny leveled logger. The logger puts the level tag before the [Component] prefixes, so existing greps keep matching. CLAUSTRUM_LOG_LEVEL sets the threshold, which defaults to debug. The logger therefore emits everything, and the variable only raises the floor. The logger writes to stderr only and does not touch the wire surface.
14 Windows process-tree kill via Job Objects M/M-H A Job Object held the spawned children (sysproc_windows.go), and kill and killAll ended the whole tree. The 89cb6289 reconciliation removed the job: 89cb6289 has none, and a kill there ends the direct child only (PROTOCOL.md → Windows child trees). This item added golang.org/x/sys (Windows-only).
15 Docs site (mkdocs) M/M mkdocs-material publishes docs/ to GitHub Pages. A docs workflow runs mkdocs build --strict on every PR and deploys on a push to main.
16 /metrics counters L-M/M metrics.go holds process-wide atomic counters, which are always on. The daemon serves the /metrics endpoint only with -metrics-addr set, and that flag is off by default. The endpoint gives counts only and has no auth, so bind it to loopback. No wire change.
17 Duplicate-id spawn policy L/L A spawn that reuses a still-live id succeeds and replaces the registry entry (reference parity). The previous process gets no signal, as on 89cb6289 (Linux and macOS rows A1 to A4, Windows row EV). An earlier build ended the previous tree there. That kill is removed (TestSpawnDuplicateIDReplacesAndKeepsOld). The stream frames and the exit frame of the previous process still reach its own connection, as on 89cb6289 (Linux row P2, TestSameIDKeepsTheFramesOfTheOldProcess).
18 Token from fd/stdin L/L With -token-fd <n>, -serve reads the auth token from an open descriptor, so the operator writes no token file. The parent sends the token to the detached child over an inherited pipe (CLAUSTRUM_TOKEN_PIPE). Additive/off-wire.
19 Docs-site visibility/formatting pass L/L-M Changed the prose-heavy tables into per-item and per-method sections, so every method gets its own table-of-contents entry on the published site. Site-only.
20 Windows CI test runner M/M Added a windows-latest leg to the test matrix. The stdlib helper-process pattern (helperproc_test.go, CLAUSTRUM_TEST_HELPER) replaced the Unix fixtures, so the streamed bytes stay byte-identical on every operating system. TestSocketFilesBattery skips on Windows, because its golden pins a Unix mode string.
21 Exited-child group-kill guard + LIVED-mutant triage S/S kill and killAll skip the children that already exited, so the daemon cannot SIGKILL a recycled pgid (Unix). The duplicate-id replace did the same, until its kill was removed (item 17). PROTOCOL.md records this at process.kill. A mutant triage after the run closed 5 real assertion gaps.
22 Spawn/exec syscall hardening S/S A strace -f differential found two syscall-level divergences. Claustrum keeps both on purpose, because claustrum is the safer side. First, the plain git calls (gitContext, gitStdoutErr) run git -C <repo>. The hardened calls now run with the repository directory as their working directory. Either way the daemon itself never calls chdir, so concurrent requests do not race. Second, process.kill signals the whole process group. The frames are byte-identical.
23 Recover from handler panics M/S The per-request goroutine wraps dispatch in defer recover(), so a handler panic no longer stops the whole daemon. The reply (-32603 "recovered panic: <v>") is claustrum's own, and it is NOT a parity claim. The path is unreachable, so no client can observe it. The recover applies to dispatch only. Under a panic it emits no frame for server.shutdown. That method's normal {"ok":true} reply comes from the dispatch path, not from the recover.
24 -install follows 89cb6289 M/M The direct --version run has the 30 s and 120 s bounds of the reference, and D11 is retired. The install order, the default CLI folder, the Windows .exe name, the progress lines and the download texts follow the reference. A home guard runs before the tree delete at the final CLI name (D2). See PROTOCOL.md → -install.
25 peerCheck in server.capabilities M/S The result has the peerCheck member and the server.peer_check feature of 5fd08069. Only CLAUDE_SSH_PEER_CHECK=1 changes the value, to "unavailable". claustrum has no peer check and serves every caller. On Linux 5fd08069 answers "on" there (D25). No child of the daemon inherits the variable. See PROTOCOL.md → server.*.
26 files.read follows 5fd08069 M/S files.read opens the path and then refuses a file that is not regular with -32602 files.read: not a regular file. The null device itself still reads on Linux and macOS. D4 is retired, and -files-read-regular-only is a deprecated flag that sets nothing. See files.read.
27 files.extract_tar keeps the execute bit of the owner M/S On Linux and macOS a file whose archive mode has the execute bit of the owner arrives with mode 0700, as on 5fd08069. The error texts of the entry steps follow that build too, and a name that leaves destDir and comes back is refused. The marker replaces a .synced entry of the archive. server.capabilities lists files.extract_tar.execBit. See files.extract_tar.
28 files.extract_tar prepares destDir through a handle of its parent folder M/S The daemon opens the parent folder of destDir, removes the last name of destDir through that handle, and makes the new folder there. The failure texts of that step follow 5fd08069, and a parent folder that cannot be opened leaves destDir as it is. The entry at the last name gets the home guard by identity before the wipe. See files.extract_tar.
29 git.worktree_create refuses a filter that only the repository defines M/S A checkout that needs a git filter that only the git config of the repository defines answers errorCode repository_only_filter and rolls back, as on 5fd08069 (Linux and Windows VMs). A filter with a definition outside the repository runs with the outside values. "shadowDirOff":true turns the rule off for one request. See git.worktree_create.
30 The git methods refuse a repository config that the daemon does not accept M/M The daemon reads the config of the repository itself, with one git call for each file, and follows include.path. A config or a file of the git directory that the daemon does not accept gets a refusal, as on 5fd08069. The cells ran on Linux, macOS and Windows VMs, and not every cell on each system. A verdict of a read with no open error is kept until a file of the read changes. "shadowDirOff":true turns the read off for one request. See PROTOCOL.md.
31 The git calls carry the option lists of 5fd08069 M/S The heavy profile has 28 -c items and six variables, and the light profile has 20 items, as on 5fd08069 (Linux, macOS and Windows VMs). git.info and git.list_branches run on the heavy profile. The variables of git.worktree_create are those of 5fd08069 with "shadowDirOff":true. Four results follow 5fd08069 and differ from 89cb6289 (round val-s3p2b1). git.status does not read replace objects (Linux, macOS and Windows VMs). The failure texts of git.worktree_create have no graft-file hint (Linux, macOS and Windows VMs). On Windows a create with a tree path of 270 characters succeeds (Windows VM). With worktree.useRelativePaths in the user config, a create writes absolute paths (macOS and Windows VMs). See Hardened git calls.
32 The git methods run git on the daemon's own copy of the settings H/L The daemon keeps a copy of the git settings of each repository in a folder git-shadow beside its socket. Its git calls read that copy, as on 5fd08069. git.info reports the state in a last member settingsControl: shadow, fallback-git or off-switch. The start test, the call that asks git about the repository and the folder of the copy have failure texts of their own. A refused config is read again in each request, and the daemon asks git no question about the owner. On Windows a create gives its worktree add a folder of its own, and it removes old .claude-new-worktree-* entries of the git directory. See The settings copy.
33 The measurement records leave the docs, and each doc file has a word limit M/S The folder docs/record/ is removed, with each link to it. TestDocsWordLimits (docs_consistency_test.go) fails for a file of docs/ or for AGENTS.md that passes its limit.
34 On Linux and macOS a stopping daemon keeps a new daemon's socket H/S keepSocketFileAtClose (livepred_unix.go), TestCloseAllKeepsSuccessorSocket.
35 Four places where claustrum differed from 5fd08069 in a signal, a remove or a write: the worktree file copy, the session key of a spawn, the host cleaner on Linux and the start reap H/M Each has Linux VM cells. worktreecopy_test.go, sessionsupersede_test.go, hostclean_unexamined_linux_test.go, childreap_inspect_linux_test.go.