can1357/oh-my-pi · warning · Error

Tool call denied by user: ${this.tool.name}

Error message

Tool call denied by user: ${this.tool.name}

What it means

The user was shown the approval prompt for this tool call and explicitly chose not to approve it. The wrapper emits an approval-resolved event with reason 'denied by user' and throws so the tool call is recorded as an error rather than executed.

Source

Thrown at packages/coding-agent/src/extensibility/extensions/wrapper.ts:340

			}

			const uiContext = this.runner.getUIContext();
			const basePrompt = formatApprovalPrompt(this.tool, resolvedArgs, approvalCheck.reason);
			const safetyPrompt =
				pendingSafetyChecks.length > 0
					? `${basePrompt}\nProvider safety checks:\n${safetyCheckLines(pendingSafetyChecks).join("\n")}`
					: basePrompt;
			let choice: string | undefined;
			try {
				choice = await uiContext.select(safetyPrompt, ["Approve", "Deny"]);
			} catch (err) {
				await emitApprovalResolved(false, err instanceof Error ? err.message : "approval aborted");
				throw err;
			}
			const approved = choice === "Approve";
			await emitApprovalResolved(approved, approved ? undefined : "denied by user");
			if (!approved) {
				throw new Error(`Tool call denied by user: ${this.tool.name}`);
			}
			if (pendingSafetyChecks.length > 0) {
				if (!context) throw new Error("Provider safety approval context is unavailable");
				context.providerSafetyApproved = true;
			}
		}

		// Execute the actual tool
		let result: AgentToolResult<TDetails, TParameters>;
		let executionError: Error | undefined;

		try {
			// A denied file write or delete inside this tool can be brokered to an
			// extension handler, and that registry is PROCESS-WIDE — so the session is
			// named here, the one place where every tool's execution and the runner
			// that owns the handlers are both in scope (`sdk.ts` wraps the whole tool
			// registry with this class whenever a runner exists). Inert with no
			// fallback registered: no scope is entered.

View on GitHub (pinned to 9690622007)

Solutions

  1. If denial was accidental, re-issue the tool call and approve it
  2. Pre-approve the tool via tools.approval.<toolName>: allow if prompts are too frequent
  3. Adjust approvalMode to reduce prompting in trusted environments
  4. Catch/handle the error upstream and instruct the agent to try a different approach
Defensive patterns

Strategy: try-catch

Type guard

function isUserDenial(e: unknown): boolean { return e instanceof Error && e.message.startsWith('Tool call denied by user:'); }

Try / catch

try { await tool.execute(params); } catch (err) {
  if (isUserDenial(err)) {
    return { status: 'denied', tool: err.message.split(': ')[1] };
  }
  throw err;
}

Prevention

When it happens

Trigger: A user presses 'Deny' (or aborts via Esc producing a rejection that resolves to non-Approve) on the tool approval dialog during execution in an interactive UI.

Common situations: Agent attempts a destructive or unexpected command and the user declines; automated flows where a human in the loop denies an action; scripts driving the TUI that answer 'Deny'.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/2340d4d7b21b7fe8. Report an issue: GitHub.