fix(runtime): surface harness stderr on non-zero exit so failures aren't blank
Problem
When a harness run fails, the persona sees a blank error. A real production failure looked like:
{ "error": "grok harness failed (exit 1): ", "durationMs": 8 }
The persona builds that message from run.output:
if (run.exitCode !== 0) {
throw new Error(`grok harness failed (exit ${run.exitCode}): ${truncate(run.output, …)}`);
}
…but run.output was empty, so the actual cause was invisible.
Root cause
createProcessHarnessRunner returns only stdout in HarnessRunResult.output. On failure the reason almost always lands on stderr:
- CLI auth/usage errors print to stderr.
- Spawn failures (
spawn grok ENOENTwhen the binary is missing) surface viachild.on('error'), whichspawnAndCapturereports asexitCode: 1with the message onstderrand an empty stdout.
spawnAndCapture captured stderr, but the runner dropped it — so the diagnostic never reached the caller. (This is exactly what hid the cloud#2195 grok-binary-missing failure: an 8ms ENOENT with a blank message.)
Fix
- Add an optional
HarnessRunResult.stderr(trimmed, when non-empty) so callers can read it explicitly. - On a non-zero exit, fold trimmed stderr into
outputso existing callers that only readrun.outputsee the cause. On success, output is left untouched (harnesses routinely log progress/warnings to stderr — that must not pollute output). - Extracted the fold into an exported pure helper
foldHarnessFailureOutputand unit-tested it. The integration seam can't be used directly because the spawned binary is derived from the harness type, not injectable.
With this, the same failure would have read:
grok harness failed (exit 1): spawn grok ENOENT
— immediately pointing at the missing binary.
Tests
4 new unit tests (ENOENT/empty-stdout, partial-stdout+stderr, success-with-noisy-stderr stays clean, failure-with-empty-stderr). Full runtime suite green:
# tests 110
# pass 110
Related
- AgentWorkforce/cloud#2195 — the actual grok failure this blank error was hiding (grok CLI missing from the Daytona snapshot builder).
🤖 Generated with Claude Code