can1357/oh-my-pi · error · Error
Extension failed, blocking execution: ${String(err)}
Error message
Extension failed, blocking execution: ${String(err)} What it means
An extension hook (before-tool-call) threw a non-Error value during tool execution. The wrapper only re-throws real Error instances; anything else (strings, thrown objects, rejected non-Error promises) is wrapped in a new Error prefixed with 'Extension failed, blocking execution'. Blocking extensions fail tool execution closed by design.
Source
Thrown at packages/coding-agent/src/extensibility/extensions/wrapper.ts:240
signal,
)) as ToolCallEventResult | undefined;
if (callResult?.block) {
const reason = callResult.reason || "Tool execution was blocked by an extension";
throw new Error(reason);
}
// A non-blocking handler may replace the execution input. The returned object is the raw
// input passed to `execute` (handler-owned; not re-normalized). Skipped for `computer`
// tool calls, whose event input is a synthetic {actions,pendingSafetyChecks} view
// (see toolEventArgs) rather than the real execution params.
if (callResult?.input !== undefined && context?.toolCall?.providerMetadata?.type !== "computer") {
effectiveParams = callResult.input as typeof params;
}
} catch (err) {
if (err instanceof Error) {
throw err;
}
throw new Error(`Extension failed, blocking execution: ${String(err)}`);
}
}
// 2. Full approval gate against the (possibly revised) input that will actually run — resolves
// policy and prompts on `effectiveParams`, so the user approves exactly what executes. A revised
// input that newly resolves to `deny` is caught here even though the original passed the
// short-circuit above.
const resolvedArgs = approvalArgs(effectiveParams, context);
const resolved = resolveApproval(this.tool, resolvedArgs, approvalMode, userPolicies);
context?.xdevTierResolved?.(resolved.tier);
if (resolved.policy === "deny") {
throw denyError(resolved, this.tool.name);
}
const pendingSafetyChecks = computerSafetyChecks(context);
// An xd:// device dispatch already cleared the write tool's outer gate at
// this tool's tier — re-prompting would double-ask for one action. The
// bypass only holds while the input is exactly what that outer gate
// approved: a handler revision here may have raised the tier, so revisedView on GitHub (pinned to 9690622007)
Solutions
- Fix the extension handler to throw/reject with Error instances (new Error(...))
- Inspect the stringified value after the prefix to identify which extension threw and why
- Temporarily disable extensions to confirm which one is blocking execution
- Wrap third-party extension handlers in your own try/catch that converts non-Error throws
Example fix
// before (in extension handler)
if (!params.path) throw 'path required';
// after
if (!params.path) throw new Error('path required'); Defensive patterns
Strategy: try-catch
Validate before calling
function throwsError(fn) { try { fn(); } catch (e) { return e instanceof Error; } return true; } Type guard
function isError(e: unknown): e is Error { return e instanceof Error; } Try / catch
try { await tool.execute(params); } catch (err) {
if (err instanceof Error && err.message.startsWith('Extension failed, blocking execution:')) {
logger.warn('extension blocked tool', { detail: err.message });
} else throw err;
} Prevention
- Always throw Error instances in extension handlers, never strings or objects
- Reject promises with Error only
- Add a CI test asserting your extension throws Errors on failure paths
- Wrap third-party handlers to normalize thrown values
When it happens
Trigger: An extension handler registered for a tool-call event throws a raw string (throw 'bad params'), throws a plain object, or returns a rejected promise with a non-Error reason while the tool is executing via wrapper.execute().
Common situations: Custom or third-party extension authors using throw 'message' instead of new Error('message'); handlers rejecting with response payloads or plain objects; transpilation/bundling changing thrown types.
Related errors
- Hook failed, blocking execution: ${String(err)}
- ${reason}
- Tool call denied by user: ${this.tool.name}
- Tool execution was blocked by a hook
- Permission request cancelled
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/afa35bef43588ab3.
Report an issue: GitHub.