Grants & access requests
A grant is time-boxed permission to work in specific zones. The gate issues it when a task enters In Progress, it is bound to that task and to one git branch, and it ends on its own. The vocabulary is privileged-access management on purpose — grant, access request, just-in-time access — borrowed from the systems (Teleport, Azure PIM, Okta) that already taught the industry the pattern.
Every command named below takes its flags from the CLI reference. This page is about what those commands do to access, not how to type them.
How the gate issues a grant
Section titled “How the gate issues a grant”A grant has one issuer: the transition into In Progress. There is no command that opens a zone directly. Both ways in count — starting a task from Todo, and bringing one back from In Review — and on both the checks run again instead of an earlier grant being reused, because an approval can be withdrawn and a zone reclassified while the task waits.
The gate refuses to issue a grant until:
- the task spec has a goal, non-goals and acceptance criteria, and declares its zones — or declares none;
- every declared zone exists in the board’s zone map;
- every
highrisk zone declared for write has its owner’s approval for this task (below); - the branch it will bind does not name a different task.
What the gate issues:
- Exactly the declared zones, each with its mode, read or write. Not everything the owner could approve — the smallest set the work declared.
- An expiry eight hours after issue. Eight hours is the gate’s default, and neither the CLI nor the console asks it for another length.
- Who asked, and where it is held — the actor, and, when the CLI sends it, the checkout’s top-level path beside the branch.
The move is recorded as an event naming the branch, the expiry, the zones as issued and a sha256 of the grant. That event outlives the grant it describes.
The grant reaches the machine through the CLI. cycle start pulls it down as soon as the gate passes, and cycle sync pulls the grant for the branch it is run on. The hook decides every tool call against that local file and never asks the network, so a board outage cannot open a zone — see the three layers.
Which zones need an approval
Section titled “Which zones need an approval”One combination needs its owner before work starts: a high risk zone declared for write. Reading a high-risk zone, or writing a medium- or low-risk one, opens with the grant and asks nobody. A deny zone is closed for reading too, so a task that only needs to read one declares it for read.
An approval counts only when all four hold, and each has been wrong at some point:
- it is for this task — an approval given to another task opens nothing here;
- it is for write — an approved read never satisfies a write;
- it was approved — pending and denied are not;
- it has not outlived its TTL, when the owner gave it one.
Without one, the gate refuses the start, names the owner, and ends with a ready-to-paste cycle request-access line for that task and that zone. Nothing files the request for you: a request nobody chose to send is a write nobody chose to make.
Paths in no zone need no approval inside an active task. With no task bound to the branch there is no grant to resolve, and a write is denied wherever it lands, unprotected paths included — troubleshooting walks through both refusals.
Access requests
Section titled “Access requests”A running task can still hit a zone it never declared. The denial names the zone and its owner and points at cycle request-access, which asks that owner to open one zone, for one task, in one mode — and only from the branch of the task that needs it.
Two answers are mandatory: a reason, and an alternative — what you will do if the answer is no. The CLI refuses to ask without both, and the board refuses either one in fewer than four words. The alternative is not politeness: it gives the owner something to weigh a no against, and writing it down is often what shows the access was not needed.
- Asking twice files once. An open request for the same task, zone and mode is returned instead of a second one stacking beside it.
- The request waits in two places: the console’s requests page, which the command links to, and
cycle requestsin the owner’s terminal. It is recorded with its reason and its alternative. - Nothing notifies the owner yet. The email to the owner is written and not sent.
- An owner nobody can reach is said out loud. If the zone’s owner is not a member of the board, or the zone names no owner, the request is still filed, and the command says nobody will see it until that is fixed.
- Asking opens nothing. The request stays pending until somebody answers, and the agent carries on with the rest of the task rather than working around the block — the behaviour the E3 eval exists to test rather than assume.
Approve and deny
Section titled “Approve and deny”An approval is for one task and one zone, never global. There is no approve-all and no “always allow this zone”, in the console or in the terminal: every request is answered on its own.
The owner chooses how long. The console offers three answers: 1 hour, 4 hours, or until the task closes. From the terminal, cycle approve takes a number of hours, and without one the approval lasts until the task closes. The board refuses a length that is not a positive number of hours up to 168 — one week.
cycle approve and cycle deny check the owner first. When the zone map names an owner and it is not the identity signed in on this machine, the command refuses before the board is asked.
What a yes does:
- Before the task starts, it is the approval the gate looks for when the task does start.
- While the task runs, it widens the live grant with that zone. A timed approval puts its own expiry on the zone it adds; it never lengthens the grant, and it never narrows a zone already open for write. The widened grant reaches the machine at the next
cycle syncon the task’s branch. - Either way, it writes the zone into the task’s declared zones, because the declaration is what the closing gate compares the diff against. The audit record prints both: what the task declared, and what it was approved for on the way.
A no widens nothing, and it is recorded just as loudly. An answer the board cannot read is not a yes either: a decision has to say exactly approved or denied, and a request already answered cannot be answered again.
A terminal answer says where it came from. cycle approve and cycle deny record their answer as coming from a session, and the trail shows that beside the decision. The console is the stronger door, because a terminal runs on a machine an agent may be driving. The audit record lists every request with its reason, its alternative, its answer and the name recorded against it.
TTLs: how access closes on its own
Section titled “TTLs: how access closes on its own”Two clocks run, and neither needs anybody to remember to close anything.
| Clock | How long | When it runs out |
|---|---|---|
| The grant | 8 hours from issue, or from the last renewal | Nothing is writable on the task’s branch until access comes back |
| An approval | What the owner chose: 1 or 4 hours in the console, up to 168 hours from the terminal, or until the task closes | The gate stops counting it, at a start and at a renewal |
The hook reads the expiry from the grant file itself, offline, so an expired grant opens nothing even while the file is still on disk. cycle status shows how long is left, and once it has run out cycle status and cycle doctor both name the expiry and the command that puts it back.
Expiry is not theoretical. During CommitCycle’s own development, work stopped mid-task when a grant ran out, and nothing on the laptop could mint a new one. That is still true: the laptop mints nothing, and what puts a grant back is the board — through renewal or through the gate.
The TTL is not the only ending: every way out of In Progress revokes the grant before its eight hours are up. A spike’s seven-day time-box is a separate clock with a different job — it asks what to do with an experiment, and it grants nothing.
Renewal: when the work outlives the grant
Section titled “Renewal: when the work outlives the grant”cycle renew asks the board for the same grant again. The board re-issues it and records the act; the command only works out which task this checkout stands on and writes the answer where the hook reads it.
A renewal cannot widen anything:
- The zones come from the grant, not from the task. A declaration widened with
cycle scopeafter its approval does not ride along. - The branch, the zone modes, the task mode and the holder are copied. Only the expiry moves, to eight hours from the renewal, and the board does not accept a caller’s own length.
- Every high-risk write zone is re-checked against its owner’s approval, so a withdrawn or timed-out approval ends the renewal.
- Only running work renews. A task that is not In Progress has no grant, because every way out already revoked it.
- Only this checkout’s task. The command renews the task the current branch names, and nothing else.
Renewal exists on a board with exactly one member. At one human the wait for somebody else collapses — never the check (D-33). The board decides by counting its members: there is no solo mode to switch on, and adding a second person turns renewal off in the same act.
On a board with more than one member, cycle renew refuses, and so does a deployment with no accounts to count. With somebody to ask, the ask is the product (D-44). The refusal names the ordinary path: cycle pause hands the grant back, and cycle start re-earns it at the gate.
Renewing again and again grants nothing new. A renewal gives exactly what the gate already gave. What the eight hours buy is that access has to come back through a live, revocable board credential to continue. Every renewal is an event carrying who renewed and both expiry times, and the audit record counts them in grant_renewals.
Pause, and starting again
Section titled “Pause, and starting again”cycle pause stops work without giving it up. The task moves back to Todo, keeps its branch and its work, and hands its grant back. It needs a reason: a pause that does not say “until what” is indistinguishable from work that quietly stopped.
In that one command the board revokes the grant, the CLI deletes the grant file from the machine, and the task’s record gains a history row carrying the reason. Pausing a task whose branch is checked out in another live worktree is refused before the board is asked, because it would revoke that checkout’s grant mid-task — see parallel sessions.
Starting again runs the whole gate. cycle start on a paused task re-checks the spec, the zones and every approval, and issues a new grant. Access is re-earned, never remembered: an approval withdrawn or timed out while the task sat in Todo refuses the start.
Bringing work back from In Review passes the same gate and needs one thing more, a reason — sending finished work back is a decision against somebody’s effort, and the record has to say why.
Revocation at the hand-in
Section titled “Revocation at the hand-in”Handing work in is the moment access should stop, so it stops there. cycle submit moves the task to In Review; the board revokes the grant, and the CLI deletes the grant file in the same command rather than at the next sync. The hook never phones home, so a grant left on disk would go on being honoured after the board had taken it back.
- If the file cannot be deleted, the command says so — until it is gone, the hook still honours it.
- The agent’s context stops naming the task in the same breath — see the context block.
- A checkout still holding a revoked grant loses it at its next
cycle sync, once the board answers that there is none — never on a network failure, so a dropped connection cannot turn into a hard stop. - Straight after a hand-in the branch has no active task, so writes are denied until work starts again.
Every way out of In Progress revokes, not only the hand-in: a pause and a cancellation do the same.
Revoked is not erased. The board marks the grant revoked instead of deleting it, and the event that issued it — zones, expiry and the grant’s sha256 — stays in the task’s log. “What was open, and when” can still be answered after the key is gone.
Signed grants
Section titled “Signed grants”The problem. The hook trusts the grant file, and it has to: it never asks the network. On 2026-08-24 the E19 eval measured a grant that opened one zone for read being rewritten into a wider one through a node one-liner, a python3 one-liner and a shell script — and the hook honoured it. The snapshot guard that reverts writes into zones could not help, because the grant file is gitignored and the guard had nothing to revert.
The mechanism (D-61). When a signing key is configured, the board signs each grant it issues with an Ed25519 private key kept as a secret of the board’s deployment. The hook carries the matching public key and verifies offline. An agent can read the public key, the hook and the grant, and still cannot produce a signature.
- The signature pins what matters. Changing a zone from read to write, the branch, the expiry, the task mode or the tenant breaks it, and so does claiming a different holder.
- One set of bytes. The board signs, and the hook verifies, the payload one shared function builds, so the two cannot drift apart.
- A broken key fails the transition, rather than letting the gate hand out an unsigned grant in its place.
- Unsigned is its own case. The hook reports an unsigned grant apart from a forged one, so a board that has not started signing does not read as an attack.
- Cheap on the hot path. 20,000 verifies measured 0.168 ms at p99 on 2026-08-24 — the verify alone, not the hook’s whole decision — against roughly 17 ms of latency headroom.
Not switched on yet. The hook verifies only once the board’s public key is compiled into it, and the two halves switch on together or not at all: a hook that verified while the grants on disk were unsigned would deny every write. The latest record of the hook’s key (CC-505, 2026-08-24) has none compiled in, so grant files are not yet checked for a signature. Two paths drop a signature today: the board’s store neither saves nor returns it, so the grant cycle sync brings down carries none; and an approval that widens a running grant rewrites its zones without signing them.
What signing does not close. It closes grant forgery; it does not sandbox an agent. An agent that can run arbitrary code can still alter the hook itself. Signing moves the attack from writing a file the hook reads to changing the hook — harder, far more visible, and what cycle doctor --enforcement reports on.
What a grant does not do
Section titled “What a grant does not do”- It is not a sandbox, and it binds only where the hook is installed — a hook that is not installed lets every write through. See what a zone is not.
- Renewal is not for teams. On a board with more than one member, work that outlives its grant pauses and starts again through the gate.
- It does not email anyone. An owner finds a request in the console or in
cycle requests.