Plugin permission requests
先看人话
这页用于补齐 OpenClaw 官方最新文档里的新增内容。先按命令和字段原样理解;如果你只是普通用户,优先看本页的标题、小节和示例命令,不需要一口气读完所有维护者细节。
Plugin permission requests let plugin code pause a tool call or plugin-owned operation until a user approves or denies it. They use the Gateway plugin.approval.* flow and the same approval UI surfaces that handle chat approval buttons and /approve commands.
Use plugin permission requests for plugin/app permissions. They do not replace host exec approvals, optional tool allowlists, or Codex's native permission review.
Choose the right gate
Pick the gate that matches the decision point you need:
| Gate | Use it when | What it controls |
|---|---|---|
| Optional tools | A tool should not be visible to the model until the user opts in. | Tool exposure through tools.allow. |
| Plugin permission requests | A plugin hook or plugin-owned operation must ask before one action runs. | Runtime approval through plugin.approval.*. |
| Exec approvals | A host command or shell-like tool needs operator approval. | Host exec policy and durable exec allowlists. |
| Codex native permission requests | Codex asks before native shell, file, MCP, or app-server actions. | Codex app-server or native hook approval handling, routed through plugin approvals when OpenClaw owns the prompt. |
| MCP approval elicitations | A Codex MCP server requests approval for a tool call. | MCP approval responses bridged through OpenClaw plugin approvals. |
Optional tools are a discovery-time gate. Plugin permission requests are a per-call gate. Use both when a sensitive tool should require explicit opt-in before the model can see it and approval before the action runs.
Request approval before a tool call
Most plugin-authored prompts should start in a before_tool_call hook. The hook runs after the model selects a tool and before OpenClaw executes it:
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
export default definePluginEntry({
id: "deploy-policy",
name: "Deploy Policy",
register(api) {
api.on("before_tool_call", async (event) => {
if (event.toolName !== "deploy_service") {
return;
}
const environment =
typeof event.params.environment === "string" ? event.params.environment : "unknown";
return {
requireApproval: {
title: "Deploy service",
description: `Deploy service to ${environment}.`,
severity: environment === "production" ? "critical" : "warning",
allowedDecisions:
environment === "production"
? ["allow-once", "deny"]
: ["allow-once", "allow-always", "deny"],
timeoutMs: 120_000,
timeoutBehavior: "deny",
onResolution(decision) {
console.log(`deploy approval resolved: ${decision}`);
},
},
};
});
},
});Write prompt text for the person who will approve the action:
- Keep
titleshort and action-focused. The Gateway accepts up to 80 characters. - Keep
descriptionspecific and bounded. The Gateway accepts up to 256 characters. - Include the action, target, and risk. Do not include secrets, tokens, or private payloads that should not appear in chat approval surfaces.
- Use
severity: "critical"only for actions where the wrong decision could cause production damage or data loss. - Use
allowedDecisions: ["allow-once", "deny"]when persistent trust is unsafe for that action.
Decision behavior
OpenClaw creates a pending approval with a plugin: ID, delivers it to the available approval surfaces, and waits for a decision.
| Decision | Result |
|---|---|
allow-once | The current call continues. |
allow-always | The current call continues and the decision is passed to the plugin. |
deny | The call is blocked with a denied tool result. |
| Timeout | The call is blocked unless timeoutBehavior is "allow". |
| Cancellation | The call is blocked when the run is aborted. |
| No approval route | The call is blocked because no connected approval surface can resolve it. |
allow-always is only durable when the requesting plugin or runtime implements that persistence. For ordinary before_tool_call.requireApproval hooks, OpenClaw treats allow-once and allow-always as approval decisions for the current call and passes the resolved value to onResolution. If your plugin offers allow-always, document and implement exactly what future calls it trusts.
If the hook also returns params, OpenClaw applies those parameter changes only after the approval succeeds. A lower-priority hook can still block after a higher-priority hook requested approval.
allowedDecisions limits the buttons and commands shown to the user. The Gateway rejects a resolve attempt for any decision the request did not offer.
Route approval prompts
Approval prompts can resolve in local UI surfaces or in chat channels that support approval handling. To forward plugin approval prompts to explicit chat targets, configure approvals.plugin:
{
approvals: {
plugin: {
enabled: true,
mode: "targets",
agentFilter: ["main"],
targets: [{ channel: "slack", to: "U12345678" }],
},
},
}approvals.plugin is independent from approvals.exec. Enabling exec approval forwarding does not route plugin approval prompts, and enabling plugin approval forwarding does not change host exec policy.
When a prompt includes manual approval text, resolve it with one of the offered decisions:
/approve <id> allow-once
/approve <id> allow-always
/approve <id> denySee Advanced exec approvals for the full forwarding model, same-chat approval behavior, native channel delivery, and channel-specific approver rules.
Codex native permissions
Codex native permission prompts can also travel through plugin approvals, but they have different ownership than plugin-authored hooks.
- Codex app-server approval requests route through OpenClaw after Codex review.
- The native hook
permission_requestrelay can ask throughplugin.approval.requestwhen that relay is enabled. - MCP tool approval elicitations route through plugin approvals when Codex marks
_meta.codex_approval_kindas"mcp_tool_call".
See Codex harness runtime for the Codex-specific behavior and fallback rules.
Troubleshooting
The tool says plugin approvals are unavailable. No approval UI or configured approval route accepted the request. Connect an approval-capable client, use a channel that supports same-chat /approve, or configure approvals.plugin.
allow-always appears but the next call prompts again. The generic plugin approval flow does not automatically persist trust for arbitrary hooks. Persist plugin-owned trust in your plugin after onResolution("allow-always"), or offer only allow-once and deny.
/approve rejects the decision. The request restricted allowedDecisions. Use one of the decisions printed in the prompt.
A Slack, Discord, Telegram, or Matrix prompt routes differently from exec approvals. Plugin approvals and exec approvals use separate config and may use different authorization checks. Verify approvals.plugin and the channel's plugin approval support instead of only checking approvals.exec.
