Files
oh-my-pi/packages/coding-agent/test/rpc-stdin-lock.test.ts
T
can1357 89d6a8f6d1 fix(coding-agent): gate print-mode plan arming on settings before session context
- runPrintMode dereferenced session.sessionManager before checking whether
  plan.defaultOnStartup was even enabled, crashing partial-session harnesses;
  the settings check now short-circuits first.
- Extended the print-mode session fakes with the settings/sessionManager
  members every real session provides.
- Scoped the rpc-stdin-lock stderr assertion to non-load-notice lines: the
  adversarial fixture's load failure is now legitimately surfaced (#4954)
  and mentions the locked stream by design.
2026-07-21 00:16:45 +02:00

66 lines
2.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import * as path from "node:path";
import { isRecord, readJsonl } from "@oh-my-pi/pi-utils";
async function expectRpcModeOwnsStdin(mode: "rpc" | "rpc-ui"): Promise<void> {
const cliPath = path.join(import.meta.dir, "..", "src", "cli.ts");
const extensionPath = path.join(import.meta.dir, "fixtures", "locked-stdin-reader.ts");
const child = Bun.spawn(
[
"bun",
cliPath,
"--extension",
extensionPath,
"--mode",
mode,
"--provider",
"anthropic",
"--model",
"claude-sonnet-4-5",
],
{
cwd: path.join(import.meta.dir, ".."),
env: { ...Bun.env, PI_NO_TITLE: "1" },
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
},
);
const stderrPromise = new Response(child.stderr).text();
child.stdin.write(`${JSON.stringify({ type: "get_state", id: "probe" })}\n`);
await child.stdin.flush();
let stateResponse: Record<string, unknown> | undefined;
try {
for await (const frame of readJsonl<unknown>(child.stdout as ReadableStream<Uint8Array>)) {
if (isRecord(frame) && frame.type === "response" && frame.id === "probe") {
stateResponse = frame;
break;
}
}
} finally {
child.stdin.end();
child.kill();
await child.exited.catch(() => {});
}
const stderr = await stderrPromise;
// The adversarial fixture is EXPECTED to fail loading — RPC claimed stdin
// first — and its surfaced load notice (#4954) mentions the locked stream.
// Any OTHER "ReadableStream is locked" line means RPC lost stdin ownership.
for (const line of stderr.split("\n").filter(l => l.includes("ReadableStream is locked"))) {
expect(line).toContain("Failed to load extension");
}
expect(stateResponse?.success).toBe(true);
}
describe("RPC mode stdin ownership", () => {
test("rpc claims stdin before extensions can lock its singleton stream", () => expectRpcModeOwnsStdin("rpc"), 30000);
test(
"rpc-ui claims stdin before extensions can lock its singleton stream",
() => expectRpcModeOwnsStdin("rpc-ui"),
30000,
);
});