fix(runtime): throw typed ExitError instead of generic Error for simulated exit (#97796) (#97803)

createNonExitingRuntime.exit() threw a generic Error, making it
impossible for upstream try-catch to distinguish a simulated process
exit from a real runtime crash. Added an ExitError class (internal-only,
not exported from SDK) that callers can check via instanceof.

ExitError is kept internal to avoid expanding the public plugin SDK
API surface without maintainer approval. Tests import directly from
./runtime.js.

Fixes #97796.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
maweibin
2026-07-07 00:07:32 +08:00
committed by GitHub
parent dbb70549be
commit 586107002a
2 changed files with 27 additions and 5 deletions

View File

@@ -10,7 +10,7 @@ vi.mock("../packages/terminal-core/src/restore.js", () => ({
restoreTerminalState: vi.fn(),
}));
import { createNonExitingRuntime, writeRuntimeJson } from "./runtime.js";
import { createNonExitingRuntime, ExitError, writeRuntimeJson } from "./runtime.js";
describe("createNonExitingRuntime", () => {
it("returns runtime with exit function", () => {
@@ -18,14 +18,25 @@ describe("createNonExitingRuntime", () => {
expect(typeof runtime.exit).toBe("function");
});
it("exit function throws error", () => {
it("exit function throws ExitError", () => {
const runtime = createNonExitingRuntime();
expect(() => runtime.exit(1)).toThrow("exit 1");
expect(() => runtime.exit(1)).toThrow(ExitError);
});
it("exit function includes code in error message", () => {
it("ExitError includes exit code", () => {
const runtime = createNonExitingRuntime();
expect(() => runtime.exit(42)).toThrow("exit 42");
expect(() => runtime.exit(42)).toThrow(ExitError);
});
it("ExitError is distinguishable from generic Error", () => {
const runtime = createNonExitingRuntime();
try {
runtime.exit(1);
} catch (err) {
expect(err instanceof ExitError).toBe(true);
expect(err instanceof Error).toBe(true);
}
});
});

View File

@@ -95,11 +95,22 @@ export const defaultRuntime: OutputRuntimeEnv = {
},
};
/** Error thrown by createNonExitingRuntime.exit() to signal simulated process exit. */
export class ExitError extends Error {
constructor(
public readonly code: number,
message?: string,
) {
super(message ?? `exit ${code}`);
this.name = "ExitError";
}
}
export function createNonExitingRuntime(): OutputRuntimeEnv {
return {
...createRuntimeIo(),
exit: (code: number) => {
throw new Error(`exit ${code}`);
throw new ExitError(code);
},
};
}