Two Registers, One Wall: In-Flight State That Never Gates a Push
Project state that git can witness and coordination state that only this machine knows are two different things, and collapsing them is how a push gate stops being provable. Here is where the wall goes, why the filesystem holds it, and why a guard you believe is armed is worse than one you know is not.
Put two coding agents on one repository and the first thing that breaks is not the merge. It is the twenty minutes before it.
Session A is halfway through src/billing/. Nothing it has done is committed, because it is halfway. Session B, opened in another terminal, reads the repository, sees a clean tree at the same HEAD, and starts editing the same directory. Both are working from an accurate picture. The picture is simply not current, and nothing in git can make it current, because the work that would distinguish them has not been written to git yet. This is not a Claude Code problem or a Cursor problem — it is a property of any two processes editing one tree between commits.
The obvious fix is to record it: have each session announce what it holds. The non-obvious part is where that record is allowed to be read.
Two registers, and why they are not one
A project that verifies itself already keeps a durable register — the recorded state of the work, synced at session boundaries, with git as its witness. That register is what a push gate validates: every claim in it resolves against the commit graph, or the gate fails and the push is blocked. A claim that cannot be checked against git has no business being in it.
In-flight coordination is the opposite kind of fact. Session B holds src/billing/ right now is true for the next forty minutes, on this machine, for these process ids. It is not a property of the repository. Committing it would be committing a fact about a laptop, and the next machine to read it would be reading something that was never true there.
So there are two registers with two lifetimes: the durable one that syncs at boundaries and is witnessed by git, and the in-flight one that lives for the length of a session and is witnessed by nothing but the processes involved.
The wall is a filesystem, not a rule
Keeping the second register out of the first is the whole design, and a written rule is the weakest possible way to keep it. Rules get relaxed by the next person with a good reason.
$ cat casp/live/.gitignore # casp live: machine-local runtime state, never committed, never gated. * $ casp check --plain | grep -c live 0
The directory ignores itself, from the inside. No outer configuration has to be kept in step with it, and a fresh clone has no way to inherit another machine's in-flight state because that state was never in the objects. The second line is the other half: across every rule the gate evaluates, the string does not appear once. There is no code path from the gate to the ephemeral register — not a disabled one, not a flag, not a conditional. The wall is enforced by there being nothing on the other side of it.
Why the gate must never learn to read it
The tempting improvement writes itself. The in-flight register knows more than git does. It knows about work in progress that a commit graph cannot see. Why not let the gate use it — refuse a push when another session is mid-edit, say?
Because the gate's value is that its verdict is reproducible. Every rule it applies resolves against the commit graph, so the same repository yields the same verdict on any machine, at any hour, run by anyone. Teach it to read machine-local runtime state and one exit code starts depending on which processes happened to be alive when it ran. The verdict becomes unreproducible in exactly the cases where it matters — CI, a colleague's clone, a rerun ten minutes later.
That is the trade being declined. The in-flight register is allowed to be useful and is not allowed to be evidence.
A guard believed armed is worse than a guard known to be off
The second register does refuse things. Wired as a pre-tool hook, it can block a file write against a path another living session holds. That makes it the one component that can interrupt an action inside a different process, which sets a hard requirement: every degraded state — an expired claim, a dead holder, an unparseable timestamp, a corrupt file, any internal error — has to mean no coordination, never no editing. Fail open, absolutely, plus a kill switch that works without editing a file inside the session that is stuck.
Failing open is the correct contract. Failing open silently is where it goes wrong, and it took a release of its own to fix. A session that believes its lanes are guarded and edits accordingly, while the guard is standing down for a reason nobody printed, is in worse shape than a session that knows there is no guard at all — the second one is careful.
So the status output states the category and the reason, in every state, including the ones where nothing is being enforced:
$ casp live claims reserved paths not enforced no controller declared no active claims
$ casp live controller --label cto-casp-website controller declared cto-casp-website until 2026-08-18T09:41:12.503Z reserved paths are enforced only while another session holds a lane $ casp live claim src/billing --label session-b --ttl 60 claimed src/billing until 2026-08-18T02:41:12.698Z $ casp live claims controller cto-casp-website (eb94698b-dbf5-483b-99de-b5497804bf13) reserved paths not enforced no other lane is held — a solo session is never blocked src/billing session-b (eb94698b-dbf5-483b-99de-b5497804bf13) until 2026-08-18T02:41:12.698Z
A claim exists, a controller exists, and enforcement is still off — because both rows belong to one session and a session alone has nobody to collide with. Read the first line and you know it. Read only the third and you would have guessed wrong. The difference between those two readings is the entire point of printing the reason.
What it does not guard, stated plainly
Path claims guard path writes. They do not guard the side effects of shared state, and the gap is not small.
- A commit without an explicit pathspec publishes the whole git index, which every process in the repository shares.
- An install regenerates a common lockfile.
- A build writes into one output directory.
- A dev server takes one port.
Every one of those is an in-lane action with an out-of-lane effect. None of them is a file-writing tool call, so no path claim will ever see them. That is why wiring claims into a session launcher was ruled out rather than left pending: across the recorded incidents from three measured trials, a claim would have caught one and would have refused two entirely legitimate writes. The tally, and what it says about running several sessions at once, is on the fleet page.
The shape worth copying
Two registers, two lifetimes, one wall — and the wall held by the filesystem rather than by anyone's care:
- The durable register syncs at session boundaries, git witnesses it, and the push gate validates nothing else.
- The in-flight register is machine-local, self-ignored, and readable by the gate through no code path at all.
- Every degraded state in the in-flight register means less coordination, never less editing, and it says which state it is in and why.
The register that gets committed is the one whose claims a commit graph can settle. Everything else is coordination, and coordination is allowed to be wrong without anything being blocked.
$ npm i -g @justethales/casp $ casp init && casp check
Local, deterministic, zero telemetry. No account, no outbound call, no model in the gate.
The model holds the context. CASP proves the state is true — against git.