Hmbown/CodeWhale · error · ExecError

timeout after ms

Error message

timeout after ${opts.timeoutMs ?? 20_000}ms: ${cmd}

What it means

runOk wraps run() and converts timeouts into this typed ExecError, embedding the timeout duration and the command. The child process did not exit within opts.timeoutMs (default 20000ms), so run() killed it and flagged timedOut. This is a wall-clock deadline failure, not a non-zero exit code.

Solutions

  1. Increase opts.timeoutMs for the specific slow command (e.g. pass { timeoutMs: 60_000 } for UIA-heavy actions).
  2. Check the target machine/app health — an unresponsive or modal-blocked app will hang any automation call.
  3. Reduce work per call: split large scripts, or avoid operations known to block (modal dialogs, network paths).
  4. Add retry with a longer budget if the slowness is transient (system under load).

Example fix

// before
await runOk('powershell', ['-NoProfile', '-Command', bigScript]);
// after
await runOk('powershell', ['-NoProfile', '-Command', bigScript], { timeoutMs: 60_000 });
Defensive patterns

Strategy: retry

Type guard

const isTimeoutError = (e) =>
  e instanceof Error && /^timeout after \d+ms:/.test(e.message);

Try / catch

try {
  return await runOk(cmd, args, { timeoutMs: budgetMs });
} catch (e) {
  if (isTimeoutError(e) && attempt < 2) return runWithRetry(cmd, args, budgetMs * 2, attempt + 1);
  throw e;
}

Prevention

When it happens

Trigger: Any runOk call whose command exceeds opts.timeoutMs: PowerShell UIA scripts on slow/loaded machines, hung target applications blocking an automation query, a stuck screenshot or input injection, or too small an explicit timeoutMs for a legitimately slow command.

Common situations: Under-provisioned CI machines where 20s PowerShell startup plus automation exceeds the budget; an unresponsive app whose UIA provider blocks; network-mapped paths stalling file operations; callers passing timeoutMs tuned for fast commands to slow ones.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/b444283029feec1e. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/exec.mjs:156

          signal?.addEventListener("abort", abort, { once: true });
        });
        child.stdin.write(JSON.stringify(message) + "\n");
        return await Promise.race([next, cancelled, new Promise((_, reject) => { commandTimer = setTimeout(() => reject(new ExecError("Native input owner did not acknowledge pointer motion")), opts.timeoutMs ?? 20_000); })]);
      }
      catch (error) { await release().catch(() => {}); throw error; }
      finally { clearTimeout(commandTimer); signal?.removeEventListener("abort", abort); }
    } };
  } catch (error) {
    await release().catch(() => {});
    throw error;
  } finally { clearTimeout(timer); }
}

/** run() and throw a typed error on non-zero exit / timeout. */
export async function runOk(cmd, args = [], opts = {}) {
  const r = await run(cmd, args, opts);
  if (r.aborted) throw Object.assign(new ExecError("computer request cancelled", r), { code: "cancelled" });
  if (r.timedOut) throw new ExecError(`timeout after ${opts.timeoutMs ?? 20_000}ms: ${cmd}`, r);
  if (r.code !== 0) throw new ExecError(`${cmd} exited ${r.code}: ${trim(r.stderr || r.stdout)}`, r);
  return r;
}

export class ExecError extends Error {
  constructor(message, result) {
    super(message);
    this.name = "ExecError";
    this.result = result;
  }
}

/** True when the executable exists on PATH (or opts.fullPath exists). */
export async function have(cmd) {
  const probe = process.platform === "win32" ? "where" : "which";
  const r = await run(probe, [cmd], { timeoutMs: 5000 });
  return r.code === 0 && r.stdout.trim().length > 0;
}

View on GitHub (pinned to 73e0f67d83)