Three layers Shipped · proven on Claude Code only

Each layer is there because the one above it can fail

A file can be ignored. A hook can be absent. A pull request cannot be either, but it arrives too late to stop anything. None of the three is sufficient, which is the entire reason there are three.

acme-webafter cycle start CC-58

$ cycle start CC-58

<!-- cc:begin -->

### You are working on CC-58 — Export times out on large boards

Branch `task/CC-58-export-times-out-on-large-boards`. Access expires in about 8 hours.

What it must NOT do. No change to the export format.

No protected zone is open to this task.

<!-- cc:end -->

cycle start writes the task into a gitignored CLAUDE.local.md beside the zone map in AGENTS.md, and cycle submit takes it out again.

Claude Code — ~/acme-webplugin · commitcycle

> add an index on tasks.updated_at — the board query is slow

● Write(apps/api/migrations/0042_tasks_updated_at.sql)

⎿ PreToolUse:Write — ✗ Denied by CC

zone schema · risk high

owner ana@acme.dev

task CC-42 declared no protected zones

reason path is in a zone the task never asked for

● The schema zone is closed to CC-42. Asking its owner rather than looking for another way in.

● Bash(cycle request-access --zone schema --mode write --reason "the board query needs an index" --alternative "hand the DDL to the owner")

⎿ REQ-7 is with the zone owner

approve or deny: https://dash.commitcycle.com/requests

The hook refuses the write before it lands, names the zone and its owner, and the agent asks for access instead of looking for another way in. Claude Code is where this block has been seen working.

Pull request · commitcycleCommitCycle's own CI

CC-42: add an index on tasks.updated_at

task/CC-42-add-an-index-on-tasks-updated-at → main

  • ✓ static, test, evals, apps — typecheck, unit tests, evals, builds
  • ◌✓scope — Is this pull request inside what its task declared?

Advisory here: this repository’s plan cannot make a check required.

Layer 3 re-checks the diff against what the task declared. It runs in CommitCycle’s own CI today, advisory there; nothing installs it in yours.

Layer 1 — context, which advises

The AGENTS.md block carries the zone map, the board, the hand-in note it expects and the deploy rule; the task itself — what is open and what is shut — is written at the gate to a gitignored CLAUDE.local.md, which Claude Code loads — whether other editors do has never been checked — and removed at hand-in. It is honest in its own text about being advice: nothing is stopping you, so this one is on you.

Layer 2 — the hook, which denies

A pre-tool-call hook answers before the write happens. Ours only narrows: it can deny, and since CC-1142 it never answers allow, so the harness's own permission prompts still apply. What matters more than the mechanism:

Worth saying plainly: this mechanism is commodity. Claude Code, Codex, Cursor, Augment, Factory and Devin CLI all expose a hook that can return deny, and in the one you are probably using it is free. What is not commodity is the model it consults — owners, expiry, and permission scoped to a declared task.

The admission that makes the rest credible

We measured the harness against real sessions on 2026-08-03, and it is fail-open in all four failure modes: crash, timeout, malformed output, not installed. That is not a footnote — it is the reason the other two layers exist in the shape they do. A wrapper can close three of those modes by refusing on its own behalf when the hook cannot answer — but only where the repository's failure policy is set to closed. The default, since CC-1143, is open: calls continue and the session is told. The fourth — never installed at all — is invisible from inside the machine, so only CI can see it, and only where the check runs.

Layer 3 — CI, which recomputes

The CI check reads the zone map from the pull request's own checkout and recomputes which zones the diff actually touched, against what the task declared — the declaration, not the grant, so approvals and expiry go unchecked. It is the only layer that could see work produced where CommitCycle was never installed.

Its own limits, stated: it runs only in CommitCycle's own repository, and nothing installs it into yours. There, on a private personal free plan, branch protection answers 403, so it runs advisory, says so, and cannot stop a merge. A board that does not answer turns it into a skip that still passes, and on a same-repository pull request the branch can rewrite the workflow that checks it. An enforcing mode and a board-side check run are built and not switched on.

Where the arrangement stops

How the layers divide the work is in the docs, and the map all three read is the zone map.