Hmbown/CodeWhale · error · Error
${reply.error.message}
Error message
${reply.error.message} What it means
call() is the thin wrapper the background check uses around the app-handler's handle() for every tool invocation (open_application, get_app_state, type, screenshot, ...). When the handler replies with reply.ok === false, the reply's error message is rethrown verbatim. The real cause is whatever the underlying tool reported — permission denials, invalid targets, internal tool failures.
Solutions
- Read the rethrown message — it is the underlying tool's own error — and fix the cause it names (usually a macOS permission or an invalid target).
- Re-run the check after granting Accessibility/Screen Recording permissions; macOS only applies them to already-running processes after approval.
- If the inner message indicates a tool bug, capture the sessionId and app logs and file an issue against the computer-use plugin.
Example fix
// before
await runBackgroundCheck({ bundle }); // opaque propagation
// after
try {
await runBackgroundCheck({ bundle });
} catch (e) {
// e.message is the raw tool error, e.g. a TCC permission denial
console.error("Background check tool failure:", e.message);
throw e;
} Defensive patterns
Strategy: try-catch
Try / catch
try {
await runBackgroundCheck({ bundle });
} catch (e) {
// e.message is the underlying tool's own error — log it with context
console.error(`Background check failed at a tool call: ${e.message}`);
if (/permission|Accessibility|Screen Recording/i.test(e.message)) {
console.error("Grant the named macOS permission, restart the process, retry.");
}
throw e;
} Prevention
- Grant Accessibility and Screen Recording permissions before starting; TCC applies to new processes only.
- Treat the propagated message as the real diagnostic — route it to logs verbatim.
- Keep sessionId/app logs when reporting failures so the failing tool can be identified.
When it happens
Trigger: Any call(tool, args) in runBackgroundCheck where handle({ tool, args }, { sessionId, signal, persistentInputOwner: true }) resolves with { ok: false, error: { message } }; the thrown Error's message equals that inner message.
Common situations: Accessibility or Screen Recording permissions revoked mid-run, a tool receiving stale AX coordinates, or the daemon backend returning an internal error during a step.
Related errors
- app_denied
- click supports left with 1-3 clicks, right x1 or middle x1
- computer_observation_required
- consent_required
- control_stopped
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/6377412ab661fb7b.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/app/background-check.mjs:41
child.stdout.on("data", chunk => {
buffer += chunk;
if (buffer.length > 16_384) { controller.abort(); return; }
let newline;
while ((newline = buffer.indexOf("\n")) >= 0) {
const line = buffer.slice(0, newline); buffer = buffer.slice(newline + 1);
try { const result = JSON.parse(line); latest = result; if (result.event === "ready") ready = true; if (result.event === "applied") applied = result; } catch { /* Cocoa diagnostics are not receipts. */ }
}
});
async function until(predicate) {
while (!predicate()) {
if (spawnError) throw spawnError;
if (controller.signal.aborted || child.exitCode !== null) throw new Error("The check was interrupted. You can run it again when ready.");
await delay(40);
}
}
async function call(tool, args = {}) {
const reply = await handle({ tool, args }, { sessionId, signal: controller.signal, persistentInputOwner: true });
if (!reply.ok) throw new Error(reply.error.message);
return reply.data;
}
try {
await until(() => ready);
await call("open_application", { pid: child.pid, activate: false });
const state = await call("get_app_state");
if (demoDirectory) {
fs.mkdirSync(demoDirectory, { recursive: true });
// AX can register before WindowServer makes a new window capturable.
// Retry only this read, before sending any input, within the check limit.
while (true) {
try { await call("screenshot", { app_ref: { pid: child.pid }, path: path.join(demoDirectory, "01-ready.png") }); break; }
catch (error) {
if (controller.signal.aborted || !error.message.includes("not capturable")) throw error;
await delay(100, undefined, { signal: controller.signal });
}
}
}View on GitHub (pinned to 73e0f67d83)