Bound Subprocess Output
Keep memory proportional to configured diagnostics, never to total child output.
Workflow
- Identify what the caller actually needs: exit code, a tail, structured output, or a durable export.
- Define separate byte limits for stdout, stderr, structured result files, and combined output before editing code.
- Retain data online with a ring/tail buffer. Do not collect all chunks and trim at process exit.
- Track total, retained, and dropped bytes without logging content.
- Define explicit behavior for timeout, output ceiling, spawn error, non-zero exit, and malformed structured output.
- Preserve shell:false and argv-array execution.
- Add volume tests that exceed every limit.
Required invariants
- Never let an array of chunks scale with total output.
- Never call Buffer.concat over the full historical stream.
- Measure limits in UTF-8 bytes, not JavaScript string length.
- Bound stdout and stderr independently.
- Apply hard ceilings while the process is running.
- Kill and reap the child when a hard ceiling or timeout fires.
- Expose truncated, droppedBytes, and termination reason.
- Persist only bounded tails in traces and plan tracking.
Structured test output
Write large machine-readable results to a temporary file when a parser needs more than a tail. Check file size before reading, parse only required fields, and remove the file in finally. Keep console streams bounded even when using a result file.
Dotcontext routing
- Put pure bounding rules in src/harness/domain.
- Keep subprocess orchestration in the existing harness execution or adapter boundary.
- Apply the same collector to sensors and plan acceptance.
- Follow F-01.
Review gate
Reject the change if any answer is no:
- Is there a documented default and absolute maximum?
- Does memory stay bounded before process exit?
- Does every failure path reap the child?
- Are truncation counters visible?
- Does a 100 MiB-output regression test pass without retaining 100 MiB?
- Do npm run build and npm test -- --runInBand pass?