can1357/oh-my-pi · info · ToolError
Tool call rejected by user (${target.name})
Error message
Tool call rejected by user (${target.name}) What it means
The permission outcome selected a 'reject_once' or 'reject_always' option, so SessionTools refuses to execute the tool and throws ToolError with the tool's name. This is the library's representation of an explicit user denial — the model's tool call is blocked and the rejection (for reject_always) is cached for future calls with the same permission cache key.
Source
Thrown at packages/coding-agent/src/session/session-tools.ts:838
}
if (raced.kind === "aborted" || signal?.aborted) {
throw new ToolAbortError("Permission request cancelled");
}
const outcome = raced.outcome;
if (outcome.outcome === "cancelled") {
throw new ToolAbortError("Permission request cancelled");
}
const selectedOption = PERMISSION_OPTIONS_BY_ID.get(outcome.optionId);
if (!selectedOption) {
throw new ToolError(`Tool permission response used unknown option ID: ${outcome.optionId}`);
}
if (selectedOption.kind === "allow_always") {
this.#acpPermissionDecisions.set(permissionIntent.cacheKey, "allow_always");
} else if (selectedOption.kind === "reject_always") {
this.#acpPermissionDecisions.set(permissionIntent.cacheKey, "reject_always");
}
if (selectedOption.kind === "reject_once" || selectedOption.kind === "reject_always") {
throw new ToolError(`Tool call rejected by user (${target.name})`);
}
return await target.execute(toolCallId, args as never, signal, onUpdate, ctx);
};
},
}) as T;
}
#isExplicitAutoApproveMode(): boolean {
return (
this.#autoApprove ||
(this.#host.settings.isConfigured("tools.approvalMode") &&
this.#host.settings.get("tools.approvalMode") === "yolo")
);
}
/** Applies an enabled tool set and reconciles its `xd://` partition. */
applyActiveToolsByName(toolNames: string[], forcePromptRefresh = false, signal?: AbortSignal): Promise<void> {
return this.runToolRegistryMutation(View on GitHub (pinned to 9690622007)
Solutions
- This is intended behavior — the agent loop should relay the rejection to the model as a tool error, not treat it as a crash.
- To stop seeing rejections, remove the reject-always preference or re-approve the tool in permission settings.
- If building an automated responder, return an allow option for trusted tools instead of reject.
Defensive patterns
Strategy: try-catch
Try / catch
try {
await runTool();
} catch (err) {
if (err instanceof ToolError && err.message.startsWith('Tool call rejected by user')) {
// relay denial to the model as a normal tool result
return { blocked: true, reason: err.message };
}
throw err;
} Prevention
- Treat rejections as expected control flow, not exceptions to log as failures.
- Audit saved reject-always preferences when tools 'mysteriously' stop running.
- Provide clear allow options in custom permission UIs to reduce accidental denials.
When it happens
Trigger: The permission prompt response mapped to an option with kind 'reject_once' or 'reject_always' (session-tools.ts:836-838); or an earlier reject_always decision was cached under `#acpPermissionDecisions` and replayed via the 'Tool call rejected by user (preference)' path.
Common situations: User clicked Deny/Reject on a tool permission dialog; an automated policy handler configured to reject certain tools; a previously saved 'always reject' preference auto-rejecting later calls.
Related errors
- Permission request cancelled
- {}: {error}
- inter-device move failed: {} to {}; unable to remove target:
- Permission denied
- cannot stat {file}: {error}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3873734764717696.
Report an issue: GitHub.