Hmbown/CodeWhale · error · ExecError
powershell did not return JSON
Error message
powershell did not return JSON: ${(r.stderr || out).trim().slice(0, 300)} What it means
psJson runs a script through psOk (which guarantees a zero exit code) and then parses stdout as JSON via tryJson. If the trimmed stdout is not valid JSON, it throws this ExecError including up to 300 chars of stderr or stdout to show what actually came back. This catches scripts that succeeded but printed unexpected output.
Solutions
- Inspect the 300-char excerpt in the error to see what polluted stdout
- In your script, redirect all non-JSON output to stderr (Write-Host/Write-Warning to stderr) or suppress profile loading (-NoProfile)
- Ensure a single ConvertTo-Json -Compress -Depth N call is the only stdout output and use Out-String where needed
- Force UTF-8 output encoding in the script: [Console]::OutputEncoding = [Text.Encoding]::UTF8
Example fix
// before # powershell "loading..." ConvertTo-Json $result // after # powershell Write-Warning "loading..." # goes to stderr ConvertTo-Json $result -Compress -Depth 6
Defensive patterns
Strategy: validation
Type guard
function isJsonObject(s) { try { const v = JSON.parse(s); return v && typeof v === 'object'; } catch { return false; } } Try / catch
try { return await psJson(script); }
catch (e) {
if (/did not return JSON/.test(e.message)) log.error('stdout polluted:', e.message);
throw e;
} Prevention
- Make ConvertTo-Json -Compress the only stdout output in the script
- Route diagnostics through Write-Warning/Write-Host (stderr) and run with -NoProfile
- Force UTF-8 output encoding to avoid BOM/encoding corruption
- Test scripts across Windows PowerShell 5.1 and PowerShell 7 before shipping
When it happens
Trigger: A psJson-backed script prints progress/warning lines before the JSON, emits nothing on stdout, uses ConvertTo-Json without -Compress in a way that mangles output, or writes diagnostics to stdout while stderr is empty.
Common situations: Profile scripts ($PROFILE) or modules echoing banners on load; ConvertTo-Json emitting multiple documents for arrays; BOM or console encoding (e.g. UTF-16) corrupting the first bytes; Windows PowerShell 5.1 vs PowerShell 7 differences in JSON formatting.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
Related errors
- Failed to parse MCP config
- invalid
- measurement emitted invalid JSON
- powershell.exe exited
- powershell.exe exited
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/0fb22515f209cf28.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/win32.mjs:199
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(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/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;
}
let lastRaster = null;
let activeDisplay = null;
const heldButtons = new Set();
const heldKeys = new Set();
async function releaseInput({ buttons = [...heldButtons], keys = [...heldKeys] } = {}) {
buttons = buttons.filter((button) => heldButtons.has(button));
keys = keys.filter((key) => heldKeys.has(key));
if (!buttons.length && !keys.length) return;
const releases = [
...buttons.map((button) => `[User32]::mouse_event([User32]::${button}UP, 0, 0, 0, [UIntPtr]::Zero);`),
...keys.reverse().map((vk) => `[User32]::SendKey(${vk}, 2);`),
];
await withSignal(null, () => withUser32(releases.join("\n"), { timeoutMs: 2_000 }));
for (const button of buttons) heldButtons.delete(button);View on GitHub (pinned to 73e0f67d83)