Permission Policies
Control which agent tool calls auto-approve, which ask for permission, and how Pushary reaches you when they do
A policy decides what happens when your agent is about to run a tool — run it silently, ask you on your phone, ask in the terminal, or just notify you. Policies match a tool pattern: a bare tool name (Bash, Write, Edit), an exact argument (Bash(git status)), or a prefix (Bash(npm test:*)), with a * fallback. They only apply to agents that run through the Pushary hook (Claude Code and Codex today).
Policies are evaluated by the PreToolUse hook installed by npx @pushary/agent-hooks setup. Agents connected over MCP only (Cursor, Windsurf, Hermes) send notifications and questions, but don't gate tool calls — there's no hook to enforce a policy.
The four approval modes
Every policy has a mode that decides where the approval happens.
| Mode | What the agent does | You are asked… |
|---|---|---|
push_first | Sends a push, waits a few seconds, then falls back to the terminal if you don't tap | On your phone and the terminal |
push_only | Sends a push and blocks until you answer or the timeout fires | On your phone only |
terminal_only | No push at all | In the terminal only |
notify_only | Sends a one-way "Agent needs approval" notification, never blocks | Nowhere — the terminal decides immediately |
push_first (the default)
Sends the approval as a push question, then polls for up to pushFirstSeconds. If you tap Yes it runs; No denies it. If you don't answer in time, the hook returns control to the terminal but leaves the push live — so you can still approve from your phone, or from the terminal, whichever you reach first. This is the recommended default: you get the phone prompt without ever being blocked at your desk.
push_only
Sends the push and blocks the agent until you answer or timeoutSeconds elapses. On timeout it applies the policy's timeout action (see below). Use this for genuinely destructive operations where you want the agent to wait for an explicit human decision.
terminal_only
No notification is sent — the agent's own terminal prompt handles approval. Use this for tools you always want to approve at the keyboard.
notify_only
Fires a one-way notification (title "Agent needs approval", body = the tool description) and immediately hands back to the terminal. The agent never waits on Pushary. Use this when you want awareness but not a remote gate.
If Pushary is unreachable, the hook fails open to the terminal — it returns ask with the reason "Pushary unavailable, falling back to terminal approval". An offline agent can't reach your phone anyway, so it defers to the safest available approver: you, at the keyboard.
Policy fields
Prop
Type
The auto-approve shortcut
A policy with timeoutSeconds: 0 and timeoutAction: "approve" is allowed silently — no push, no terminal prompt, regardless of mode. This is how read-only tools stay frictionless, and it's exactly what Teach-on-Tap writes when you say "always allow."
The mirror image also holds: timeoutSeconds: 0 and timeoutAction: "deny" denies the call instantly, with no push and no terminal prompt. Combine it with a pattern like Bash(rm -rf:*) to hard-block a command family.
The one thing that overrides the auto-approve shortcut is the kill switch. A halted session is denied before the shortcut is checked, so even silently-approved tools stop.
Pattern syntax
The tool field accepts three pattern forms, modeled on Claude Code's own permission rules:
| Pattern | Matches | Example |
|---|---|---|
Tool | Every call to that tool | Bash |
Tool(exact arg) | Only calls whose argument equals the text exactly | Bash(git status) |
Tool(prefix:*) | Calls whose argument starts with the prefix | Bash(npm test:*) |
The argument under match is tool specific: command for Bash, file_path for Edit and Write. Other tools only match bare patterns, so an argument pattern for them never fires.
When several rules match one call, the most specific wins: an exact argument beats a prefix, a longer prefix beats a shorter one, a prefix beats the bare tool, and the bare tool beats * and the site defaults.
Prefix matching is plain string matching on the command, not shell parsing. Bash(git push:*) will not match cd repo && git push, and it matches any command that merely starts with the text. Claude Code's own permission rules share this property. Keep a safe bare-tool rule underneath your prefix rules.
Argument patterns are evaluated by the hook, so they need @pushary/agent-hooks 0.13.0 or later. On older versions an argument pattern simply never matches and the bare-tool and * rules still apply.
Default policies
Until you customize them, a new site uses these defaults (catch-all * plus a few well-known tools):
| Tool | Timeout | If no response | Mode | Push first |
|---|---|---|---|---|
Bash | 60s | Auto-deny | push_first | 20s |
Write | 60s | Ask in terminal | push_first | 20s |
Edit | 45s | Ask in terminal | push_first | 20s |
Read | 0s | Auto-approve | terminal_only | — |
* (default) | 60s | Ask in terminal | push_first | 20s |
Read ships with the auto-approve shortcut so file reads never interrupt you. Bash defaults to auto-deny on timeout — if you don't approve a shell command, it doesn't run.
Policies are cached locally for 5 minutes (in your temp dir as pushary-policy-<hash>.json), so editing a policy in the dashboard can take up to 5 minutes to take effect on a running agent, or restart the agent to pick it up immediately.
Editing policies in the dashboard
Open the policy editor
Go to Dashboard → Agent → Policies. Each row is one tool pattern.
Set the mode and timeouts
For each tool choose a Mode, a Timeout (0–300s), and an If no response action (Auto-approve / Auto-deny / Ask in terminal). The Push first field is only editable when the mode is push_first.
Add a rule for a specific tool
Type a pattern (e.g. Bash, Bash(git status), or Bash(npm test:*)) and Add rule to override the * default. The * row is the catch-all and can't be deleted.
From the command line
The pushary mode command sets a temporary, site-wide mode override that wins over per-tool modes until it expires:
pushary mode push_only --for 30m # force push_only for 30 minutes
pushary mode status # show the current override
pushary mode clear # back to per-tool policiesDurations are <n>m or <n>h. The override is always TTL'd server-side, so a forgotten override self-heals.
Teach-on-Tap: "always allow this tool"
When a push question is about a specific tool, the decision page shows a button: "Always allow <tool> — stop asking." Tapping it approves the current call and mints a durable policy for that tool so you're never asked again.
Under the hood it writes a policy with timeoutSeconds: 0, timeoutAction: "approve", mode: "push_only", pushFirstSeconds: 0 — which trips the auto-approve shortcut on every future call.
Teach-on-Tap is scoped per site and per exact tool name (e.g. Bash). It is not scoped to one agent or one project — once you always-allow Bash, every agent on that site auto-runs Bash. Reverse it any time by editing or deleting the rule in the policy editor.