Why a permission gate
A Kraft worker runs with nobody watching, so a permission prompt has nobody to answer it. This page explains why Kraft answers those prompts itself, and why it does that differently for each harness. For what the gate does and how to configure it, see Permission gate.
The problem with an unattended prompt
Left to a harness's own judgment, an unattended agent does one of two things. It stalls on a tool it is unsure about, or it runs the tool anyway because nobody is watching. Neither is acceptable when you want to know what a worker was allowed to do.
The third option
Kraft's permission gate is a third option. The worker asks Kraft, Kraft
answers from the task's own policy, and the answer, allow or deny and why,
lands on the work item's timeline. You do not have to watch a session to know
what it was let do. The policy comes from allowed_tools, deny_tools and
grants, which you set at any layer down from the repository to the task. See
Caps and budgets for how those
layers combine.
Why it works differently per harness
The gate has to meet each CLI where that CLI offers a hook. The harnesses differ, so Kraft uses the strongest mechanism each one has.
Claude Code settles most tool calls itself with its own classifier, such as
a read-only shell command or a routine edit. What it will not settle, it hands
to a permission prompt tool. Kraft points that at its own tool, so the CLI
calls into Kraft instead of prompting a human who is not there. Whether the
gate is reachable, and how much reaches it, follows from the permission mode.
With no allowed_tools, Claude runs in auto, and the gate sees only what the
classifier declines to settle. With an allowed_tools list, Claude switches to
manual, and every call the built-in allowlist does not cover reaches the
gate. That is what makes the gate worth configuring.
Cursor and Codex have no prompt tool, but each runs a hook before every tool call. Kraft installs a hook that asks the same gate. Kraft scopes the hook to the launch: Cursor's is written into the worktree and kept out of your commits, and Codex's is passed per launch and trusted by hash, never by bypassing Codex's hook trust, since that would trust every hook a repository ships.
Gemini runs with --approval-mode yolo, so its calls never reach the gate.
OpenCode and Amp offer no reliable per-call hook. Kraft writes the task's
deny_tools and allowed_tools into the CLI's own permission configuration
for that one launch, and the CLI enforces them itself. The trade-off is that
these calls never reach the gate, so they leave no permission_decision on
the timeline.
Why a deny is final but an allow may not be
Kraft can only decide; the CLI still runs the call. A deny is a deny on every
harness. An allow is the gate's decision, and on Cursor it does not override
the CLI's own --auto-review classifier, so a call the gate allows can still
be refused. This is why grants are described
as the gate's decision and not a guarantee that a command will run.
Why grants are names, not command patterns
A grant such as git-push is a named operation, not a pattern like
git push *. A pattern also matches git push x; rm -rf y. Kraft's grant
matcher accepts only a call that is exactly one plain git invocation of the
granted subcommand, and refuses anything that could run something else. That
is also why grants are not written into OpenCode's or Amp's rules, where only a
pattern is available.
Why an escalation gets git grants by default
An escalation turn holds git-commit, git-rebase and git-push unless you
narrow it, because an escalation that rebased the branch has to push it. A
gate's reviewer gets no such default.
Related
- Permission gate covers what the gate does per harness and how to configure it.
- Agent harnesses shows how each harness runs unattended.