Hmbown/CodeWhale · error · ServerError
consent_required
consent_required
Error message
Codewhale needs the user's permission to use ${desc} on this computer — ask them, then record their answer with consent {action:"allow"|"deny", app:"${arg}"}. What it means
Thrown when a tool call targets an application whose consent verdict is "undecided" — the user has never allowed or denied it. The computer-use server requires an explicit recorded user decision before an agent may act on a local application, and this error tells the agent exactly how to record one: ask the user, then call consent with {action:"allow"|"deny", app:"<arg>"}.
Solutions
- Ask the user for permission to use the named application.
- Record the answer with consent {action:"allow" or "deny", app:"<bundle_id or name>"}.
- Retry the original tool call — it will now pass the consent gate.
Example fix
// before: calling the tool with no recorded decision
await callTool({ name: "screenshot", arguments: { app: "com.apple.Safari" } }); // throws consent_required
// after: record the user's answer first, then call
await callTool({ name: "consent", arguments: { action: "allow", app: "com.apple.Safari" } });
await callTool({ name: "screenshot", arguments: { app: "com.apple.Safari" } }); Defensive patterns
Strategy: try-catch
Try / catch
try {
return await callTool({ name, arguments });
} catch (e) {
if (e.code === "consent_required") {
const arg = e.data?.app?.bundle_id ?? e.data?.app?.name;
const answer = await askUser(`May I use ${arg}?`);
await callTool({ name: "consent", arguments: { action: answer ? "allow" : "deny", app: arg } });
return await callTool({ name, arguments }); // single retry
}
throw e;
} Prevention
- Record consent for every app before the first tool call against it.
- Reuse the app identifier from the error's data.app field verbatim in the consent call.
- Batch consent setup at the start of a session for all apps the workflow will touch.
When it happens
Trigger: First tool call that resolves an app identity via consentForRef() where no consent record exists (consent.appKeys(ref) was empty, so no recorded decision could match) and verdict.state === "undecided".
Common situations: First-ever automation of a new app on the machine; a fresh session with only session-scoped decisions; a container/registry reset clearing persisted consent records.
Related errors
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/c4e8d73bca5f83a1.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:584
const bound = boundApps.get(computer.id);
if (!refs.length && bound && BOUND_TARGET_TOOLS.has(name)) refs.push(bound);
}
let grant = null;
for (const ref of refs) {
// A state or element whose backend reported no identity at all has no app
// to consent to — observation never named one either, so there is nothing
// a recorded decision could match.
if (!ref || !consent.appKeys(ref).length) continue;
const { verdict, ref: known } = await consentForRef(computer, ref);
const desc = known.name ?? known.bundle_id ?? (known.pid ? `pid ${known.pid}` : "the application");
const arg = known.bundle_id ?? known.name ?? (known.pid ? `pid:${known.pid}` : "the app");
if (verdict.state === "denied") {
throw new ServerError("app_denied",
`the user denied access to ${desc} on this computer — do not work around it; only they can change it (consent {action:"revoke"}).`,
{ app: known });
}
if (verdict.state === "undecided") {
throw new ServerError("consent_required",
`Codewhale needs the user's permission to use ${desc} on this computer — ask them, then record their answer with consent {action:"allow"|"deny", app:"${arg}"}.`,
{ app: known });
}
grant = { ref: known, persisted: verdict.persisted === true };
}
// Taking the shared pointer/focus is a second, separate consent: the first
// activate:true is the moment the agent stops being background — on every
// platform, not just macOS.
if (name === "open_application" && args.activate === true) {
const fg = consent.foregroundDecision(computer.id);
if (fg.state === "denied") {
throw new ServerError("foreground_denied",
`the user denied shared-desktop (foreground) control on this computer — continue with open_application activate:false (background control) or ask them to reconsider.`,
{ scope: "foreground" });
}
if (fg.state === "undecided") {
throw new ServerError("foreground_consent_required",
`open_application activate:true would take this computer's shared pointer and focus — ask the user, then record their answer with consent {action:"allow"|"deny", scope:"foreground"}. Background control (activate:false) needs no such consent.`,View on GitHub (pinned to 73e0f67d83)