Hmbown/CodeWhale · error · ExecError

powershell timed out after

Error message

powershell timed out after ${o.timeoutMs ?? 25_000}ms

What it means

psOk wraps every PowerShell invocation and is 'truthful': unlike ps(), it throws instead of returning results on timeout, nonzero exit, or spawn failure. When the spawned powershell.exe exceeds the caller's timeoutMs (default 25s), it throws this ExecError with the effective timeout value.

Solutions

  1. Increase timeoutMs in the options object for slow operations (e.g. { timeoutMs: 60000 })
  2. Profile the script manually in powershell.exe to find the slow or blocking statement
  3. Trim UIA queries to smaller scopes (single window/element) instead of whole-desktop tree walks
  4. Retry once on timeout; also check for warm-up effects (first call after boot is much slower)

Example fix

// before
await psOk(uiaScript);
// after
await psOk(uiaScript, { timeoutMs: 60_000 });
Defensive patterns

Strategy: retry

Try / catch

let last;
for (const t of [25_000, 60_000]) {
  try { return await psOk(script, { timeoutMs: t }); }
  catch (e) { if (!/timed out/.test(e.message)) throw e; last = e; }
}
throw last;

Prevention

When it happens

Trigger: Any computer-use operation backed by a PowerShell script (UIA queries, screenshots, window enumeration) exceeding its timeoutMs — e.g. slow UIA tree walks, hung COM automation, first-run .NET/PowerShell startup delays on cold machines, or an explicit small timeoutMs passed in options.

Common situations: 25s default too short for large UIA trees or AV-scanned PowerShell startup; a custom timeoutMs set optimistically low; a hung PowerShell child waiting on a modal dialog.

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/c9bf7792fe9bc43a. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/win32.mjs:182

    if (opts.exec?.persistentInputOwner !== true) throw Object.assign(new ExecError(
      "This held-input gesture requires a connected Codewhale Computer Use desktop helper so a disconnected client cannot leave keys or buttons pressed. Start the helper and reconnect before retrying."
    ), { code: "input_owner_required" });
  }

  async function ps(script, o = {}) {
    throwIfAborted();
    const encoded = Buffer.from(`$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';\n[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false);\n${script}`, "utf16le").toString("base64");
    return runner("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", encoded], {
      timeoutMs: o.timeoutMs ?? 25_000,
      maxBuffer: 32 * 1024 * 1024,
    });
  }

  /** ps() but truthful: timeout, nonzero exit, and spawn failure all throw. */
  async function psOk(script, o = {}) {
    const r = await ps(script, o);
    if (r.aborted) throw Object.assign(new ExecError("computer request cancelled", r), { code: "cancelled" });
    if (r.timedOut) throw new ExecError(`powershell timed out after ${o.timeoutMs ?? 25_000}ms`, r);
    if (r.code !== 0) {
      const raw = (r.stderr || r.stdout).trim();
      // EncodedCommand serializes errors as CLIXML; surface the error strings,
      // not a truncated XML/progress header that conceals the actual failure.
      const messages = [...raw.matchAll(/<S S="Error">([\s\S]*?)<\/S>/g)].map(m => m[1]
        .replace(/_x([0-9A-Fa-f]{4})_/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
        .replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&"));
      throw new ExecError(`powershell.exe exited ${r.code}: ${(messages.join("") || raw).slice(0, 1600)}`, r);
    }
    return r;
  }

  async function psJson(script, o = {}) {
    const r = await psOk(script, o);
    const out = r.stdout.trim();
    const j = tryJson(out, null);
    if (!j) throw new ExecError(`powershell did not return JSON: ${(r.stderr || out).trim().slice(0, 300)}`, r);
    return j;

View on GitHub (pinned to 73e0f67d83)