@stigmer/runner 3.0.8-dev.20260613071809 → 3.0.8-dev.20260613074252

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"hash":"6d487ab26cf2b9d2","builtAt":"2026-06-13T07:19:36.708Z","fileCount":193}
1
+ {"hash":"b6dffd4185a32e0f","builtAt":"2026-06-13T07:44:18.724Z","fileCount":194}
package/dist/main.js CHANGED
@@ -26,22 +26,27 @@ import { createStigmerRunner } from "./runner.js";
26
26
  import { createStigmerRunnerManager } from "./runner-manager.js";
27
27
  import { buildReadyMessage } from "./ipc-protocol.js";
28
28
  import { handleUnhandledRejection } from "./activities/execute-cursor/rejection-capture.js";
29
+ import { installProcessPipeGuards, reportFatal } from "./pipe-safety.js";
30
+ // Guard the host pipes before anything writes to them. A dropped stderr/stdout
31
+ // reader otherwise turns the next write into an uncaught EPIPE loop that starves
32
+ // Temporal heartbeats and kills the in-flight execution (issue #177).
33
+ const { writeStdout, writeStderr } = installProcessPipeGuards();
29
34
  process.on("unhandledRejection", (reason) => {
30
35
  handleUnhandledRejection(reason);
31
36
  });
32
37
  process.on("uncaughtException", (err) => {
33
- console.error("Uncaught exception in runner:", err);
38
+ reportFatal(writeStderr, "Uncaught exception in runner:", err);
34
39
  });
35
40
  // ─── IPC Helpers ─────────────────────────────────────────────────────────────
36
41
  function sendIpc(msg) {
37
- process.stdout.write(JSON.stringify(msg) + "\n");
42
+ writeStdout(JSON.stringify(msg) + "\n");
38
43
  }
39
44
  // ─── Manager Mode ────────────────────────────────────────────────────────────
40
45
  async function runManagerMode(config) {
41
46
  // In manager mode, redirect console.log to stderr so stdout is reserved for IPC
42
47
  const originalLog = console.log;
43
48
  console.log = (...args) => {
44
- process.stderr.write(args.map(String).join(" ") + "\n");
49
+ writeStderr(args.map(String).join(" ") + "\n");
45
50
  };
46
51
  let manager;
47
52
  try {
@@ -286,7 +291,7 @@ async function main() {
286
291
  }
287
292
  }
288
293
  main().catch((err) => {
289
- console.error("Fatal error in runner:", err);
294
+ reportFatal(writeStderr, "Fatal error in runner:", err);
290
295
  process.exit(1);
291
296
  });
292
297
  //# sourceMappingURL=main.js.map
package/dist/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Makes the runner's stdout/stderr writes fail-safe so a dead host pipe degrades
3
+ * logging instead of killing execution.
4
+ *
5
+ * Background (issue #177): the runner is embedded as a subprocess whose stdout is
6
+ * the JSON IPC channel and whose stderr is the host's log channel. When the host
7
+ * drops a pipe's read end, the runner's next write raises an async `EPIPE` error
8
+ * event. With no `error` listener that event escalates to `uncaughtException` —
9
+ * and the uncaught handler logs to the very stream that just failed, raising EPIPE
10
+ * again. That re-entrant loop pegs the event loop at ~125% CPU, starves Temporal
11
+ * heartbeats, and the in-flight execution dies as "Activity task timed out" while
12
+ * the runner process still looks alive.
13
+ *
14
+ * The fix is one Node idiom: attach an `error` listener to each stream. Once a
15
+ * listener exists, EPIPE is delivered to it and can never escalate, which both
16
+ * breaks the loop and makes every raw `console.*` call in the codebase safe. The
17
+ * guarded writers below add the intent-revealing layer on top: once a pipe breaks
18
+ * we detach (writes become silent no-ops) rather than keep trying.
19
+ *
20
+ * See <https://github.com/stigmer/stigmer/issues/177>.
21
+ */
22
+ import type { Writable } from "node:stream";
23
+ export declare function isBrokenPipeError(err: unknown): boolean;
24
+ /**
25
+ * Guards a single writable stream and returns a writer that can never crash the
26
+ * process. The returned writer no-ops once the stream's reader is gone; callers
27
+ * include their own trailing newline so the byte stream is unchanged on the happy
28
+ * path. `onUnexpectedError` reports a non-broken-pipe failure through a channel
29
+ * the caller knows is still safe (see {@link installProcessPipeGuards}).
30
+ *
31
+ * Pure with respect to the injected stream (no `process` coupling) so the
32
+ * detach-on-EPIPE behavior is unit-testable with a fake writable.
33
+ */
34
+ export declare function guardStream(stream: Writable, onUnexpectedError?: (err: Error) => void): (chunk: string) => boolean;
35
+ /**
36
+ * Installs broken-pipe guards on `process.stdout` and `process.stderr` and returns
37
+ * the safe writers for each. Idempotent — the first call wins so repeated imports
38
+ * never stack duplicate `error` listeners.
39
+ *
40
+ * Channel asymmetry is deliberate: stdout is the JSON IPC channel, so an
41
+ * unexpected stdout error is reported on stderr (the log channel); an unexpected
42
+ * stderr error has no safe place to go, so it detaches silently rather than
43
+ * corrupting the IPC stream with diagnostics.
44
+ */
45
+ export declare function installProcessPipeGuards(): {
46
+ writeStdout: (chunk: string) => boolean;
47
+ writeStderr: (chunk: string) => boolean;
48
+ };
49
+ /**
50
+ * Reports a fatal/uncaught error through a guarded writer, guaranteeing the report
51
+ * itself can never throw. A fatal handler that throws while reporting is the exact
52
+ * re-entrancy trap behind issue #177, so the write is wrapped defensively even
53
+ * though the writer is already broken-pipe safe.
54
+ */
55
+ export declare function reportFatal(write: (chunk: string) => boolean, label: string, err: unknown): void;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Makes the runner's stdout/stderr writes fail-safe so a dead host pipe degrades
3
+ * logging instead of killing execution.
4
+ *
5
+ * Background (issue #177): the runner is embedded as a subprocess whose stdout is
6
+ * the JSON IPC channel and whose stderr is the host's log channel. When the host
7
+ * drops a pipe's read end, the runner's next write raises an async `EPIPE` error
8
+ * event. With no `error` listener that event escalates to `uncaughtException` —
9
+ * and the uncaught handler logs to the very stream that just failed, raising EPIPE
10
+ * again. That re-entrant loop pegs the event loop at ~125% CPU, starves Temporal
11
+ * heartbeats, and the in-flight execution dies as "Activity task timed out" while
12
+ * the runner process still looks alive.
13
+ *
14
+ * The fix is one Node idiom: attach an `error` listener to each stream. Once a
15
+ * listener exists, EPIPE is delivered to it and can never escalate, which both
16
+ * breaks the loop and makes every raw `console.*` call in the codebase safe. The
17
+ * guarded writers below add the intent-revealing layer on top: once a pipe breaks
18
+ * we detach (writes become silent no-ops) rather than keep trying.
19
+ *
20
+ * See <https://github.com/stigmer/stigmer/issues/177>.
21
+ */
22
+ /** Error codes that mean the downstream reader is gone — logging must degrade, not crash. */
23
+ const BROKEN_PIPE_CODES = new Set(["EPIPE", "ERR_STREAM_DESTROYED", "ERR_STREAM_WRITE_AFTER_END"]);
24
+ export function isBrokenPipeError(err) {
25
+ const code = err?.code;
26
+ return typeof code === "string" && BROKEN_PIPE_CODES.has(code);
27
+ }
28
+ /**
29
+ * Guards a single writable stream and returns a writer that can never crash the
30
+ * process. The returned writer no-ops once the stream's reader is gone; callers
31
+ * include their own trailing newline so the byte stream is unchanged on the happy
32
+ * path. `onUnexpectedError` reports a non-broken-pipe failure through a channel
33
+ * the caller knows is still safe (see {@link installProcessPipeGuards}).
34
+ *
35
+ * Pure with respect to the injected stream (no `process` coupling) so the
36
+ * detach-on-EPIPE behavior is unit-testable with a fake writable.
37
+ */
38
+ export function guardStream(stream, onUnexpectedError) {
39
+ let detached = false;
40
+ stream.on("error", (err) => {
41
+ // Any error means the stream is no longer usable; stop writing to it. The
42
+ // listener's existence is what prevents EPIPE from escalating to
43
+ // uncaughtException — that is the actual fix for issue #177.
44
+ const wasAttached = !detached;
45
+ detached = true;
46
+ // Report an unexpected (non-broken-pipe) failure at most once, on the first
47
+ // error — later errors are just noise from the same dead stream.
48
+ if (wasAttached && !isBrokenPipeError(err)) {
49
+ onUnexpectedError?.(err);
50
+ }
51
+ });
52
+ return (chunk) => {
53
+ if (detached)
54
+ return false;
55
+ try {
56
+ return stream.write(chunk);
57
+ }
58
+ catch {
59
+ // A synchronous throw (e.g. write-after-end) means the stream is unusable;
60
+ // detach so we never retry into the same failure.
61
+ detached = true;
62
+ return false;
63
+ }
64
+ };
65
+ }
66
+ let installed = null;
67
+ /**
68
+ * Installs broken-pipe guards on `process.stdout` and `process.stderr` and returns
69
+ * the safe writers for each. Idempotent — the first call wins so repeated imports
70
+ * never stack duplicate `error` listeners.
71
+ *
72
+ * Channel asymmetry is deliberate: stdout is the JSON IPC channel, so an
73
+ * unexpected stdout error is reported on stderr (the log channel); an unexpected
74
+ * stderr error has no safe place to go, so it detaches silently rather than
75
+ * corrupting the IPC stream with diagnostics.
76
+ */
77
+ export function installProcessPipeGuards() {
78
+ if (installed)
79
+ return installed;
80
+ const writeStderr = guardStream(process.stderr);
81
+ const writeStdout = guardStream(process.stdout, (err) => {
82
+ writeStderr(`[pipe-safety] stdout (IPC) channel error, detaching: ${err.stack ?? err}\n`);
83
+ });
84
+ installed = { writeStdout, writeStderr };
85
+ return installed;
86
+ }
87
+ /**
88
+ * Reports a fatal/uncaught error through a guarded writer, guaranteeing the report
89
+ * itself can never throw. A fatal handler that throws while reporting is the exact
90
+ * re-entrancy trap behind issue #177, so the write is wrapped defensively even
91
+ * though the writer is already broken-pipe safe.
92
+ */
93
+ export function reportFatal(write, label, err) {
94
+ try {
95
+ const detail = err instanceof Error ? (err.stack ?? err.message) : String(err);
96
+ write(`${label} ${detail}\n`);
97
+ }
98
+ catch {
99
+ // Intentionally empty: the reporter is the last line of defense and must
100
+ // never propagate.
101
+ }
102
+ }
103
+ //# sourceMappingURL=pipe-safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipe-safety.js","sourceRoot":"","sources":["../src/pipe-safety.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,6FAA6F;AAC7F,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,sBAAsB,EAAE,4BAA4B,CAAC,CAAC,CAAC;AAEnG,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC5C,MAAM,IAAI,GAAI,GAA6C,EAAE,IAAI,CAAC;IAClE,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CACzB,MAAgB,EAChB,iBAAwC;IAExC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;QAChC,0EAA0E;QAC1E,iEAAiE;QACjE,6DAA6D;QAC7D,MAAM,WAAW,GAAG,CAAC,QAAQ,CAAC;QAC9B,QAAQ,GAAG,IAAI,CAAC;QAChB,4EAA4E;QAC5E,iEAAiE;QACjE,IAAI,WAAW,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3C,iBAAiB,EAAE,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,KAAa,EAAW,EAAE;QAChC,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC3B,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,2EAA2E;YAC3E,kDAAkD;YAClD,QAAQ,GAAG,IAAI,CAAC;YAChB,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,IAAI,SAAS,GACX,IAAI,CAAC;AAEP;;;;;;;;;GASG;AACH,MAAM,UAAU,wBAAwB;IAItC,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAEhC,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,WAAW,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE;QACtD,WAAW,CAAC,wDAAwD,GAAG,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;IAEH,SAAS,GAAG,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC;IACzC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAiC,EAAE,KAAa,EAAE,GAAY;IACxF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC/E,KAAK,CAAC,GAAG,KAAK,IAAI,MAAM,IAAI,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,mBAAmB;IACrB,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/runner",
3
- "version": "3.0.8-dev.20260613071809",
3
+ "version": "3.0.8-dev.20260613074252",
4
4
  "description": "Embeddable Temporal worker for the Stigmer AI agent platform — handles agent execution, workflow orchestration, and MCP server management",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -86,7 +86,7 @@
86
86
  "@opentelemetry/resources": "^2.0.0",
87
87
  "@opentelemetry/sdk-trace-base": "^2.0.0",
88
88
  "@opentelemetry/sdk-trace-node": "^2.0.0",
89
- "@stigmer/protos": "3.0.8-dev.20260613071809",
89
+ "@stigmer/protos": "3.0.8-dev.20260613074252",
90
90
  "@temporalio/activity": "^1.11.0",
91
91
  "@temporalio/client": "^1.11.0",
92
92
  "@temporalio/common": "^1.11.0",
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { EventEmitter } from "node:events";
3
+ import type { Writable } from "node:stream";
4
+ import { isBrokenPipeError, guardStream, reportFatal } from "../pipe-safety.js";
5
+
6
+ // Minimal stand-in for a writable host pipe: records writes and lets a test drive
7
+ // `error` events deterministically, which is exactly the EPIPE condition from #177.
8
+ class FakeWritable extends EventEmitter {
9
+ public writes: string[] = [];
10
+ public throwOnWrite: Error | null = null;
11
+ write(chunk: string): boolean {
12
+ if (this.throwOnWrite) throw this.throwOnWrite;
13
+ this.writes.push(chunk);
14
+ return true;
15
+ }
16
+ }
17
+
18
+ function asWritable(fake: FakeWritable): Writable {
19
+ return fake as unknown as Writable;
20
+ }
21
+
22
+ function pipeError(code: string): Error {
23
+ return Object.assign(new Error(code), { code });
24
+ }
25
+
26
+ describe("isBrokenPipeError", () => {
27
+ it.each([
28
+ ["EPIPE", true],
29
+ ["ERR_STREAM_DESTROYED", true],
30
+ ["ERR_STREAM_WRITE_AFTER_END", true],
31
+ ["ECONNRESET", false],
32
+ ["ENOENT", false],
33
+ ])("classifies code %s as %s", (code, expected) => {
34
+ expect(isBrokenPipeError(pipeError(code))).toBe(expected);
35
+ });
36
+
37
+ it("is false for non-error / code-less values", () => {
38
+ expect(isBrokenPipeError(undefined)).toBe(false);
39
+ expect(isBrokenPipeError(null)).toBe(false);
40
+ expect(isBrokenPipeError(new Error("no code"))).toBe(false);
41
+ expect(isBrokenPipeError("EPIPE")).toBe(false);
42
+ });
43
+ });
44
+
45
+ describe("guardStream", () => {
46
+ it("writes through on the happy path", () => {
47
+ const fake = new FakeWritable();
48
+ const write = guardStream(asWritable(fake));
49
+
50
+ expect(write("hello\n")).toBe(true);
51
+ expect(fake.writes).toEqual(["hello\n"]);
52
+ });
53
+
54
+ it("detaches to a silent no-op after an EPIPE error (the #177 regression)", () => {
55
+ const fake = new FakeWritable();
56
+ const onUnexpected = vi.fn();
57
+ const write = guardStream(asWritable(fake), onUnexpected);
58
+
59
+ // Emitting `error` must not throw — a listener is what stops EPIPE from
60
+ // escalating to uncaughtException and forming the busy loop.
61
+ expect(() => fake.emit("error", pipeError("EPIPE"))).not.toThrow();
62
+
63
+ expect(write("after\n")).toBe(false);
64
+ expect(fake.writes).toEqual([]);
65
+ expect(onUnexpected).not.toHaveBeenCalled();
66
+ });
67
+
68
+ it("reports an unexpected (non-broken-pipe) error exactly once", () => {
69
+ const fake = new FakeWritable();
70
+ const onUnexpected = vi.fn();
71
+ const write = guardStream(asWritable(fake), onUnexpected);
72
+
73
+ fake.emit("error", pipeError("ECONNRESET"));
74
+ fake.emit("error", pipeError("ECONNRESET"));
75
+
76
+ expect(onUnexpected).toHaveBeenCalledTimes(1);
77
+ expect(write("after\n")).toBe(false);
78
+ });
79
+
80
+ it("detaches when stream.write throws synchronously instead of propagating", () => {
81
+ const fake = new FakeWritable();
82
+ fake.throwOnWrite = pipeError("ERR_STREAM_WRITE_AFTER_END");
83
+ const write = guardStream(asWritable(fake));
84
+
85
+ expect(() => write("boom\n")).not.toThrow();
86
+ expect(write("boom\n")).toBe(false);
87
+
88
+ // Once detached, even a now-healthy stream is left alone (we never retry).
89
+ fake.throwOnWrite = null;
90
+ expect(write("recovered\n")).toBe(false);
91
+ expect(fake.writes).toEqual([]);
92
+ });
93
+ });
94
+
95
+ describe("reportFatal", () => {
96
+ it("writes the label and error detail", () => {
97
+ const writes: string[] = [];
98
+ reportFatal((chunk) => (writes.push(chunk), true), "Uncaught:", new Error("kaboom"));
99
+
100
+ expect(writes).toHaveLength(1);
101
+ expect(writes[0]).toContain("Uncaught:");
102
+ expect(writes[0]).toContain("kaboom");
103
+ });
104
+
105
+ it("stringifies non-Error values", () => {
106
+ const writes: string[] = [];
107
+ reportFatal((chunk) => (writes.push(chunk), true), "Uncaught:", "plain string");
108
+
109
+ expect(writes[0]).toContain("plain string");
110
+ });
111
+
112
+ it("never throws even when the writer itself throws (the re-entrancy trap)", () => {
113
+ const throwingWriter = (): boolean => {
114
+ throw pipeError("EPIPE");
115
+ };
116
+
117
+ expect(() => reportFatal(throwingWriter, "Uncaught:", new Error("x"))).not.toThrow();
118
+ });
119
+ });
package/src/main.ts CHANGED
@@ -31,19 +31,25 @@ import { buildReadyMessage } from "./ipc-protocol.js";
31
31
  import type { IpcCommand, IpcResponse } from "./ipc-protocol.js";
32
32
 
33
33
  import { handleUnhandledRejection, setExecutionContextRef } from "./activities/execute-cursor/rejection-capture.js";
34
+ import { installProcessPipeGuards, reportFatal } from "./pipe-safety.js";
35
+
36
+ // Guard the host pipes before anything writes to them. A dropped stderr/stdout
37
+ // reader otherwise turns the next write into an uncaught EPIPE loop that starves
38
+ // Temporal heartbeats and kills the in-flight execution (issue #177).
39
+ const { writeStdout, writeStderr } = installProcessPipeGuards();
34
40
 
35
41
  process.on("unhandledRejection", (reason) => {
36
42
  handleUnhandledRejection(reason);
37
43
  });
38
44
 
39
45
  process.on("uncaughtException", (err) => {
40
- console.error("Uncaught exception in runner:", err);
46
+ reportFatal(writeStderr, "Uncaught exception in runner:", err);
41
47
  });
42
48
 
43
49
  // ─── IPC Helpers ─────────────────────────────────────────────────────────────
44
50
 
45
51
  function sendIpc(msg: IpcResponse): void {
46
- process.stdout.write(JSON.stringify(msg) + "\n");
52
+ writeStdout(JSON.stringify(msg) + "\n");
47
53
  }
48
54
 
49
55
  // ─── Manager Mode ────────────────────────────────────────────────────────────
@@ -52,7 +58,7 @@ async function runManagerMode(config: import("./config.js").Config): Promise<voi
52
58
  // In manager mode, redirect console.log to stderr so stdout is reserved for IPC
53
59
  const originalLog = console.log;
54
60
  console.log = (...args: unknown[]) => {
55
- process.stderr.write(args.map(String).join(" ") + "\n");
61
+ writeStderr(args.map(String).join(" ") + "\n");
56
62
  };
57
63
 
58
64
  let manager: StigmerRunnerManager;
@@ -319,6 +325,6 @@ async function main(): Promise<void> {
319
325
  }
320
326
 
321
327
  main().catch((err) => {
322
- console.error("Fatal error in runner:", err);
328
+ reportFatal(writeStderr, "Fatal error in runner:", err);
323
329
  process.exit(1);
324
330
  });
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Makes the runner's stdout/stderr writes fail-safe so a dead host pipe degrades
3
+ * logging instead of killing execution.
4
+ *
5
+ * Background (issue #177): the runner is embedded as a subprocess whose stdout is
6
+ * the JSON IPC channel and whose stderr is the host's log channel. When the host
7
+ * drops a pipe's read end, the runner's next write raises an async `EPIPE` error
8
+ * event. With no `error` listener that event escalates to `uncaughtException` —
9
+ * and the uncaught handler logs to the very stream that just failed, raising EPIPE
10
+ * again. That re-entrant loop pegs the event loop at ~125% CPU, starves Temporal
11
+ * heartbeats, and the in-flight execution dies as "Activity task timed out" while
12
+ * the runner process still looks alive.
13
+ *
14
+ * The fix is one Node idiom: attach an `error` listener to each stream. Once a
15
+ * listener exists, EPIPE is delivered to it and can never escalate, which both
16
+ * breaks the loop and makes every raw `console.*` call in the codebase safe. The
17
+ * guarded writers below add the intent-revealing layer on top: once a pipe breaks
18
+ * we detach (writes become silent no-ops) rather than keep trying.
19
+ *
20
+ * See <https://github.com/stigmer/stigmer/issues/177>.
21
+ */
22
+
23
+ import type { Writable } from "node:stream";
24
+
25
+ /** Error codes that mean the downstream reader is gone — logging must degrade, not crash. */
26
+ const BROKEN_PIPE_CODES = new Set(["EPIPE", "ERR_STREAM_DESTROYED", "ERR_STREAM_WRITE_AFTER_END"]);
27
+
28
+ export function isBrokenPipeError(err: unknown): boolean {
29
+ const code = (err as { code?: unknown } | null | undefined)?.code;
30
+ return typeof code === "string" && BROKEN_PIPE_CODES.has(code);
31
+ }
32
+
33
+ /**
34
+ * Guards a single writable stream and returns a writer that can never crash the
35
+ * process. The returned writer no-ops once the stream's reader is gone; callers
36
+ * include their own trailing newline so the byte stream is unchanged on the happy
37
+ * path. `onUnexpectedError` reports a non-broken-pipe failure through a channel
38
+ * the caller knows is still safe (see {@link installProcessPipeGuards}).
39
+ *
40
+ * Pure with respect to the injected stream (no `process` coupling) so the
41
+ * detach-on-EPIPE behavior is unit-testable with a fake writable.
42
+ */
43
+ export function guardStream(
44
+ stream: Writable,
45
+ onUnexpectedError?: (err: Error) => void,
46
+ ): (chunk: string) => boolean {
47
+ let detached = false;
48
+
49
+ stream.on("error", (err: Error) => {
50
+ // Any error means the stream is no longer usable; stop writing to it. The
51
+ // listener's existence is what prevents EPIPE from escalating to
52
+ // uncaughtException — that is the actual fix for issue #177.
53
+ const wasAttached = !detached;
54
+ detached = true;
55
+ // Report an unexpected (non-broken-pipe) failure at most once, on the first
56
+ // error — later errors are just noise from the same dead stream.
57
+ if (wasAttached && !isBrokenPipeError(err)) {
58
+ onUnexpectedError?.(err);
59
+ }
60
+ });
61
+
62
+ return (chunk: string): boolean => {
63
+ if (detached) return false;
64
+ try {
65
+ return stream.write(chunk);
66
+ } catch {
67
+ // A synchronous throw (e.g. write-after-end) means the stream is unusable;
68
+ // detach so we never retry into the same failure.
69
+ detached = true;
70
+ return false;
71
+ }
72
+ };
73
+ }
74
+
75
+ let installed: { writeStdout: (chunk: string) => boolean; writeStderr: (chunk: string) => boolean } | null =
76
+ null;
77
+
78
+ /**
79
+ * Installs broken-pipe guards on `process.stdout` and `process.stderr` and returns
80
+ * the safe writers for each. Idempotent — the first call wins so repeated imports
81
+ * never stack duplicate `error` listeners.
82
+ *
83
+ * Channel asymmetry is deliberate: stdout is the JSON IPC channel, so an
84
+ * unexpected stdout error is reported on stderr (the log channel); an unexpected
85
+ * stderr error has no safe place to go, so it detaches silently rather than
86
+ * corrupting the IPC stream with diagnostics.
87
+ */
88
+ export function installProcessPipeGuards(): {
89
+ writeStdout: (chunk: string) => boolean;
90
+ writeStderr: (chunk: string) => boolean;
91
+ } {
92
+ if (installed) return installed;
93
+
94
+ const writeStderr = guardStream(process.stderr);
95
+ const writeStdout = guardStream(process.stdout, (err) => {
96
+ writeStderr(`[pipe-safety] stdout (IPC) channel error, detaching: ${err.stack ?? err}\n`);
97
+ });
98
+
99
+ installed = { writeStdout, writeStderr };
100
+ return installed;
101
+ }
102
+
103
+ /**
104
+ * Reports a fatal/uncaught error through a guarded writer, guaranteeing the report
105
+ * itself can never throw. A fatal handler that throws while reporting is the exact
106
+ * re-entrancy trap behind issue #177, so the write is wrapped defensively even
107
+ * though the writer is already broken-pipe safe.
108
+ */
109
+ export function reportFatal(write: (chunk: string) => boolean, label: string, err: unknown): void {
110
+ try {
111
+ const detail = err instanceof Error ? (err.stack ?? err.message) : String(err);
112
+ write(`${label} ${detail}\n`);
113
+ } catch {
114
+ // Intentionally empty: the reporter is the last line of defense and must
115
+ // never propagate.
116
+ }
117
+ }