Skip to main content

Verdicts: Allow / Ask / Deny

Every tool call Belay evaluates resolves to exactly one of three verdicts: Allow, Ask, or Deny. This page explains what each means, how precedence is resolved when signals disagree, and the guarantees that make the outcome safe.

When to read this

Read this alongside How it works. Once you understand the tool-call boundary, this is the rulebook for what happens at it.

The three verdicts

Allow

The tool call proceeds normally. Either nothing in the rule catalog matched, or what matched was benign and the call is safe to run. Belay still records the decision in the audit log.

Ask

The call is ambiguous but risky — plausibly legitimate, plausibly dangerous. Instead of guessing, Belay pauses the agent and escalates to a human for an explicit Allow / Deny. The agent stays blocked until the decision comes back. Approvals surface in the terminal, in the desktop app's approval queue, and — if you configure them — in a chat channel (see below).

warning

Timeout equals deny. An ask that is never answered resolves to a deny, not an allow. Belay fails closed — an unattended escalation cannot become an accidental approval. Prompts auto-expire; a stale prompt is auto-denied.

Deny

The call is dangerous and is blocked outright — no human is asked. This covers things like reading secret-shaped files (.env, API keys, SSH keys), destructive commands (rm -rf /), curl | sh, reverse shells, and exfiltration to known sinks. The agent is prevented from taking the action, and the block is recorded.

Every verdict — allow, ask, or deny — carries a curated plain-English explanation (summary / what / why_risky / normal_use / suggested_action), so you always know why a call was gated and what to do next.

Precedence: Deny > Ask > Allow

A single tool call can trip more than one signal at once — a rule match, a session-correlation pattern, an allowlist entry. Belay resolves them with a strict, unambiguous priority:

Deny > Ask > Allow

If any signal says deny, the verdict is deny. If none say deny but at least one says ask, the verdict is ask. Only when nothing raises a concern is the verdict allow. There is no averaging, scoring-to-threshold, or "mostly fine" — the most restrictive applicable signal wins.

The allowlist can never downgrade a Deny

Belay ships an allowlist so that ordinary developer-toolchain activity isn't constantly flagged. That allowlist can only ever downgrade non-deny decisions — it can quiet an ask down to an allow for genuinely benign work.

It can never touch a deny. A DENY is final: the allowlist cannot upgrade a blocked action into an allowed one. This is a deliberate one-way rule so that a crafted "looks-like-dev-tooling" wrapper around a dangerous command can't launder its way past the boundary.

note

This is why precedence and the allowlist are described together: the allowlist sits below deny in priority by construction. Deny always wins.

Session correlation feeds the verdict

Beyond matching a single call in isolation, the engine correlates activity across a session. It watches for an arm → sink shape (a step that stages something dangerous, followed by a step that acts on it) and for the "lethal-trifecta" combination that, taken together, indicates a genuine exfiltration risk even when each individual step looked harmless. When correlation fires, it raises the verdict through the same Deny > Ask > Allow precedence.

Approving an Ask from a chat channel

When a verdict is ask, Belay can send the approval prompt to a chat channel and take your Allow / Deny reply back. Two-way channels: Telegram, Discord, WhatsApp, Matrix, Mattermost, and Slack (Block Kit buttons). Notify-only channels: ntfy, Microsoft Teams, WeCom, a generic webhook, and the terminal.

Approval is default-deny and owner-gated: only enrolled principals can approve. Enrollment is interactive — the app shows a one-time code, and the approver DMs pair <code> to the bot to become an authorized approver. Prompts auto-expire, so an unanswered prompt in a chat channel denies just like any other timeout. Configure channels in the setup wizard (belay setup) or the desktop Messaging tab.

--observe: dry-run before you enforce

Before you enforce, run in observe mode:

belay protect claude-code --observe

In observe mode the engine computes the exact same verdicts and logs them, but nothing is ever blocked — every decision is log-only. It's the recommended way to see what would be denied or asked, tune out false positives, and build confidence, before you re-run protect without the flag to enforce for real.

tip

Observe mode changes nothing about how verdicts are computed — only whether they're enforced. What you see logged in observe mode is exactly what will be enforced when you drop the flag.

Fail-closed, everywhere

The verdict model is safe by default at every failure point:

  • An internal engine error resolves to deny.
  • A timeout resolves to deny.
  • An unanswered ask (including in the headless MCP proxy) resolves to deny.

There is no path where a failure quietly becomes an allow.