Hmbown/CodeWhale · error · ServerError
foreground_denied
foreground_denied
Error message
the user denied shared-desktop (foreground) control on this computer — continue with open_application activate:false (background control) or ask them to reconsider.
What it means
Thrown by consentCheck() when open_application is called with activate:true but the user has recorded a deny decision for shared-desktop (foreground) control. Taking the shared pointer/focus is a second, separate consent from per-app access on every platform. The error offers the supported alternative: background control with activate:false, or asking the user to reconsider.
Solutions
- Re-call open_application with activate:false for background control.
- Ask the user to reconsider; if they agree, record it with consent {action:"allow", scope:"foreground"}.
- If the deny was persisted by mistake, revoke it with consent {action:"revoke", scope:"foreground"}.
Example fix
// before
await callTool({ name: "open_application", arguments: { name: "Firefox", activate: true } }); // throws foreground_denied
// after: background control needs no foreground consent
await callTool({ name: "open_application", arguments: { name: "Firefox", activate: false } }); Defensive patterns
Strategy: fallback
Try / catch
try {
return await callTool({ name: "open_application", arguments: { ...args, activate: true } });
} catch (e) {
if (e.code === "foreground_denied") {
// fall back to background control, which requires no foreground consent
return await callTool({ name: "open_application", arguments: { ...args, activate: false } });
}
throw e;
} Prevention
- Default to activate:false unless foreground focus is genuinely required.
- Remember a foreground deny for the session and never send activate:true again.
- On foreground_denied, offer the user a one-step way to change their decision.
When it happens
Trigger: open_application with args.activate === true while consent.foregroundDecision(computer.id).state === "denied".
Common situations: User denied foreground control earlier in the session or via a persisted decision; an agent habitually passes activate:true and hits the deny on every launch attempt.
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
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/fd893e06e6be24e8.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:596
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.`,
{ scope: "foreground" });
}
}
return grant ? { grant } : null;
}
// ---------- tool dispatch ----------
async function callTool(params) {
const requested = params.name;
if (!TOOL_NAMES.has(requested)) {
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: { code: "unknown_tool", message: `unknown tool "${requested}"` } }) }], isError: true };
}View on GitHub (pinned to 73e0f67d83)