The gates and the log

What the owner boundary enforces, when approval blocks, and where a real CLI still has reach.

There are two rules and one record. Being precise about which is enforced and which is instructed matters more than the pitch does, so they are in that order.

The approval command — enforced when used

A workflow can run a command that blocks its own process until you decide. You answer in the app or from a bridge you configured. AgentCron's own mail-send path and shipped prompts route named consequential actions through it.

The shipped instruction names these actions:

  • git push or force-push
  • opening, merging or closing a pull request
  • deploying or restarting a production service
  • deleting a branch, worktree, database, or more than a handful of files
  • spending money
  • sending an email or a message as you
  • changing another agent's schedule
  • installing anything globally

It fails closed, three ways

What happenedWhat the agent gets
You denied itNon-zero exit. Do not do it.
It expired because nobody decidedNon-zero exit. Do not do it.
The app was unreachableNon-zero exit. Do not do it.

The agent is told all three mean no, that it must not look for another way to do the thing, and that it must report having been refused. That fail-closed result applies to the approval command. It does not prove an arbitrary CLI web tool or project script called the command in the first place.

It is also told not to grade its own request as low risk to avoid the wait, and not to split one action into several small "safe" pieces.

The table above describes the defaults, and the defaults are owner-policy — disclosed here the same way the review-gate switch is disclosed below. agentcron policy ships with on_approval_timeout = deny and auto_approve_low_risk off; the owner can flip an expired approval to approve instead, or let requests marked low-risk through without waiting. Changing either is owner-only: agentcron policy --set is refused when it runs inside an agent's own run, so an agent cannot loosen the gate it is standing behind.

The repository owner boundary — normally enforced

Every run is told, in a paragraph injected into its prompt:

Leave all your work uncommitted in the worktree for a human to review and commit. Never run git commit, git push, or open a PR — even when the work is finished and tests pass; handing over the uncommitted diff is the point.

The review screen ends in a commit that you make: you read the diff, tick the files, and write the message. In the default policy, the prompt is backed by a macOS Seatbelt profile that denies repository-history changes, common remote-credential paths, the main checkout from a linked worktree, and AgentCron's control files.

One setting changes it, per agent. An agent's When it finishes choice can be switched from "leave work for me to review" to "allowed to commit, push and open a PR" — and then that agent does exactly that, unattended, with nothing held back. The paragraph above is not injected into its prompt; a different one is, telling it to commit its own work.

The switch is owner-only, and only reachable in the app. No agent can set it for itself or for another agent; the control socket refuses every write to it by name. An instruction to commit arriving inside a task prompt, a queue item, a ticket body or a message does not lift it either — the injected paragraph says so in as many words, and a test scans the shipped source for the loophole phrasings to keep it that way.

This is a targeted owner boundary, not a general-purpose sandbox. The owner can switch it off, and a Mac without the boundary records the run as advisory. Credentials deliberately placed in the project can still widen the process's reach. The run records the enforced/advisory state and attempted protected operations.

The command log — the record

Run output is retained. Where an integration observes an individual command, the record includes its working directory, exit code, duration and output. Protected-operation attempts are also classified by the owner boundary.

Coverage is not uniform, and the app says so

CLIHow commands are captured
Claude CodeAutomatically, through the CLI's own hook API. No cooperation from the agent required, including for commands that failed.
GitHub Copilot CLI, agy, plain shell, anything in a nested processOnly what the agent routes through the wrapper command, because none of those exposes a hook API.

So an empty log means we could not see it, not that nothing ran — and the app states that rather than implying full coverage. There is a per-CLI coverage view for exactly this reason.

This matters more than it sounds. Agent CLIs change, and a hook contract that changes in a point release can quietly thin the log without throwing a single error. Measuring coverage is how a degradation becomes visible instead of silent.

Questions and plans

Two smaller mechanisms that do a lot of work:

Blocking questions. An agent that hits an ambiguity can ask with options and stop. The process genuinely blocks. You answer eight hours later and it resumes from where it stopped. This is the difference between an overnight run that produces something and one that produces a guess.

Declared plans. Three to seven steps declared before starting, stepped through as it goes. A long run shows where it is rather than merely that it is alive.

Why not just leave permission prompts on?

Because nobody does. The prompt asks about ls with the same urgency as git push --force, so people turn it off and rely on watching — and watching scales to one agent.

Every unattended run here launches the CLI with prompts disabled, deliberately, because an unattended run cannot answer one. The honest trade is a targeted repository boundary plus explicit approval calls—not a claim that AgentCron can intercept every capability a CLI or project exposes.