Hmbown/CodeWhale · error · ServerError
app_denied
app_denied
Error message
the user denied access to ${desc} on this computer — do not work around it; only they can change it (consent {action:"revoke"}). What it means
Thrown by the computer-use MCP server when a tool call targets an application the user has explicitly recorded as denied. The server treats a user deny decision as authoritative: the agent must not retry, rename the target, or attempt a workaround — only the user can change the decision via a consent {action:"revoke"} call. The error carries the known app identity (name, bundle_id, or pid) in its data.
Solutions
- Do not retry the call; inform the user the app is blocked and that only they can lift it.
- Ask the user to explicitly revoke the denial, then call the consent tool with {action:"revoke", app:"<bundle_id or name>"}.
- Target a different application the user has allowed instead of the denied one.
Example fix
// before: retrying after app_denied
await callTool({ name: "open_application", arguments: { name: "Keychain Access", activate: true } });
// after: stop and ask the user to revoke, then retry
// user: "ok, you may use it"
await callTool({ name: "consent", arguments: { action: "revoke", app: "com.apple.keychainaccess" } });
await callTool({ name: "open_application", arguments: { name: "Keychain Access" } }); Defensive patterns
Strategy: try-catch
Validate before calling
// no pre-call check available; the decision lives server-side // defensively: track apps the user has denied in this session and skip them const deniedApps = new Set(); if (deniedApps.has(targetApp)) return skip;
Try / catch
try {
await callTool({ name, arguments });
} catch (e) {
if (e.code === "app_denied") {
// stop; surface e.data.app to the user and offer consent {action:"revoke"}
return { blocked: true, app: e.data?.app };
}
throw e;
} Prevention
- Treat a deny as final; never retry or re-spell the app identity to dodge it.
- Cache denied app identities per session and skip them proactively.
- When app_denied fires, ask the user explicitly before any further attempts on that app.
When it happens
Trigger: Any MCP tool call (e.g. screenshot, get_app_state, open_application, run_actions) that resolves to an application whose consent verdict is state === "denied" in consentForRef() during consentCheck().
Common situations: The user previously answered 'deny' to a permission prompt for this app; an automation retries a task that was denied in an earlier session with a persisted decision; the agent resolves the same app via a different spelling (pid vs bundle_id) but the deny was recorded under all app keys.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- consent_required
- foreground_denied
- computer_observation_required
- control_stopped
- foreground_consent_required
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/abb295de75dd49e0.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:579
}
if (args.state_id != null) {
const st = appStates.get(args.state_id);
if (st?.computerId === computer.id) refs.push(st.app_ref);
}
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.`,View on GitHub (pinned to 73e0f67d83)