{"record":{"id":"afa35bef43588ab3","repo":"can1357/oh-my-pi","slug":"extension-failed-blocking-execution-string-err","errorCode":null,"errorMessage":"Extension failed, blocking execution: ${String(err)}","messagePattern":"Extension failed, blocking execution: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/extensions/wrapper.ts","lineNumber":240,"sourceCode":"\t\t\t\t\tsignal,\n\t\t\t\t)) as ToolCallEventResult | undefined;\n\n\t\t\t\tif (callResult?.block) {\n\t\t\t\t\tconst reason = callResult.reason || \"Tool execution was blocked by an extension\";\n\t\t\t\t\tthrow new Error(reason);\n\t\t\t\t}\n\t\t\t\t// A non-blocking handler may replace the execution input. The returned object is the raw\n\t\t\t\t// input passed to `execute` (handler-owned; not re-normalized). Skipped for `computer`\n\t\t\t\t// tool calls, whose event input is a synthetic {actions,pendingSafetyChecks} view\n\t\t\t\t// (see toolEventArgs) rather than the real execution params.\n\t\t\t\tif (callResult?.input !== undefined && context?.toolCall?.providerMetadata?.type !== \"computer\") {\n\t\t\t\t\teffectiveParams = callResult.input as typeof params;\n\t\t\t\t}\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error) {\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\tthrow new Error(`Extension failed, blocking execution: ${String(err)}`);\n\t\t\t}\n\t\t}\n\n\t\t// 2. Full approval gate against the (possibly revised) input that will actually run — resolves\n\t\t// policy and prompts on `effectiveParams`, so the user approves exactly what executes. A revised\n\t\t// input that newly resolves to `deny` is caught here even though the original passed the\n\t\t// short-circuit above.\n\t\tconst resolvedArgs = approvalArgs(effectiveParams, context);\n\t\tconst resolved = resolveApproval(this.tool, resolvedArgs, approvalMode, userPolicies);\n\t\tcontext?.xdevTierResolved?.(resolved.tier);\n\t\tif (resolved.policy === \"deny\") {\n\t\t\tthrow denyError(resolved, this.tool.name);\n\t\t}\n\t\tconst pendingSafetyChecks = computerSafetyChecks(context);\n\t\t// An xd:// device dispatch already cleared the write tool's outer gate at\n\t\t// this tool's tier — re-prompting would double-ask for one action. The\n\t\t// bypass only holds while the input is exactly what that outer gate\n\t\t// approved: a handler revision here may have raised the tier, so revised","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/extensions/wrapper.ts#L222-L258","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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"],"exampleFix":"// before (in extension handler)\nif (!params.path) throw 'path required';\n// after\nif (!params.path) throw new Error('path required');","handlingStrategy":"try-catch","validationCode":"function throwsError(fn) { try { fn(); } catch (e) { return e instanceof Error; } return true; }","typeGuard":"function isError(e: unknown): e is Error { return e instanceof Error; }","tryCatchPattern":"try { await tool.execute(params); } catch (err) {\n  if (err instanceof Error && err.message.startsWith('Extension failed, blocking execution:')) {\n    logger.warn('extension blocked tool', { detail: err.message });\n  } else throw err;\n}","preventionTips":["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"],"tags":["extension","tool-execution","fail-closed"],"backgroundTag":"extension-hook-threw-non-error","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}