pi-mono/packages/coding-agent/test/exec-runtime-limit.test.ts
Christian Klotz c466a35957
fix(coding-agent): cap pi.exec output at 256 MiB (#11546)
Closes #11360.

## Summary
- Cap combined stdout/stderr with `ExecOptions.maxBuffer`, defaulting to
the smaller of 256 MiB and the runtime string limit. Invalid explicit
limits reject before spawning.
- Terminate oversized commands and return `truncated: true`, `killed:
true`, and a nonzero exit code. Close output pipes so writing
descendants cannot keep the call pending, and discard incomplete UTF-8
suffixes rather than adding replacement characters.
- Fix SIGKILL escalation for children that ignore SIGTERM, and simplify
completion to `await` with one cleanup block and result.
- Document the API and add regressions for byte limits, shell
descendants, UTF-8 chunk boundaries, and 32-bit runtime limits.

`ExecResult.truncated` is required. Existing extensions reading
execution results continue working; typed mocks or wrappers constructing
results must add the field.

## Verification
- `npm run build` and the post-rebase `npm run build:offline`
- `npm run check`
- 39 focused execution and extension tests
- `./test.sh` passes after building the env daemon. Earlier runs hit
remote-cleanup and reftable-watcher timeouts; both passed in focused
reruns and the final full run.
2026-10-29 00:13:41 +02:00

25 lines
1,021 B
TypeScript

import * as buffer from "node:buffer";
import { expect, it, vi } from "vitest";
import { execCommand } from "../src/core/exec.ts";
vi.mock("node:buffer", async (importOriginal) => {
const actual = await importOriginal<typeof buffer>();
return {
...actual,
// 32-bit Node's maximum is 16 bytes below 256 MiB.
constants: { ...actual.constants, MAX_STRING_LENGTH: 268_435_440 },
};
});
// Regression tests for https://github.com/earendil-works/pi/issues/11360
it("executes with the default cap when the runtime string limit is below 256 MiB", async () => {
const result = await execCommand(process.execPath, ["-e", 'process.stdout.write("ok")'], process.cwd());
expect(result).toEqual({ stdout: "ok", stderr: "", code: 0, killed: false, truncated: false });
});
it("still rejects an explicit cap above the runtime string limit", async () => {
await expect(
execCommand(process.execPath, ["-e", ""], process.cwd(), { maxBuffer: buffer.constants.MAX_STRING_LENGTH + 1 }),
).rejects.toThrow(RangeError);
});