Hmbown/CodeWhale · error · ServerError
foreground_consent_required
foreground_consent_required
Error message
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. What it means
Thrown when open_application is called with activate:true and the foreground-consent decision is "undecided" — the user has never allowed or denied shared-desktop control. Because activate:true would take the computer's shared pointer and focus, the server requires an explicit recorded decision first and explains the consent call syntax; background control (activate:false) is explicitly exempt.
Solutions
- Ask the user whether the agent may take the shared pointer/focus.
- Record the answer: consent {action:"allow", scope:"foreground"} or {action:"deny", scope:"foreground"}.
- Alternatively use activate:false, which needs no foreground consent.
- Retry open_application activate:true after the decision is recorded.
Example fix
// before: activate:true with no foreground decision
await callTool({ name: "open_application", arguments: { name: "Firefox", activate: true } }); // throws foreground_consent_required
// after: ask, record, then activate
await callTool({ name: "consent", arguments: { action: "allow", scope: "foreground" } });
await callTool({ name: "open_application", arguments: { name: "Firefox", activate: true } }); Defensive patterns
Strategy: try-catch
Try / catch
try {
return await callTool({ name: "open_application", arguments: { ...args, activate: true } });
} catch (e) {
if (e.code === "foreground_consent_required") {
const ok = await askUser("May I take the mouse/keyboard focus?");
await callTool({ name: "consent", arguments: { action: ok ? "allow" : "deny", scope: "foreground" } });
if (!ok) return callTool({ name: "open_application", arguments: { ...args, activate: false } });
return await callTool({ name: "open_application", arguments: { ...args, activate: true } });
}
throw e;
} Prevention
- Record a foreground decision once at session start if the workflow needs activate:true.
- Prefer activate:false by default; ask only when foreground focus is required.
- Handle the error by asking the user — never by silently retrying activate:true.
When it happens
Trigger: open_application with args.activate === true while consent.foregroundDecision(computer.id).state === "undecided" — i.e. no allow/deny recorded for scope:"foreground" on this computer.
Common situations: First foreground launch of an app in a new session; a script always requests activate:true without ever recording a foreground decision; persisted foreground consent absent on a new machine.
Related errors
- consent_required
- foreground_denied
- app_denied
- click supports left with 1-3 clicks, right x1 or middle x1
- computer_observation_required
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/1c9c7823a5f35dc9.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/mcp/server.mjs:601
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 };
}
// Merged tools (click, pointer, clipboard, recording, computer, key+duration)
// resolve to the wire tool they dispatch to before any gate below, so they
// cannot bypass required args, the kill switch or routing. Wire names stay
// callable as aliases.
let name = requested;View on GitHub (pinned to 73e0f67d83)