@sayknow-cli/utils 0.2.4 → 0.2.5
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.
- package/package.json +2 -2
- package/src/postmortem.ts +40 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/utils",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.5",
|
|
5
5
|
"description": "Shared utilities for pi packages",
|
|
6
6
|
"homepage": "https://github.com/jaybeyond/Sayknow_CLI",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"fmt": "biome format --write ."
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@sayknow-cli/natives": "0.2.
|
|
34
|
+
"@sayknow-cli/natives": "0.2.5",
|
|
35
35
|
"beautiful-mermaid": "^1.1.3",
|
|
36
36
|
"handlebars": "^4.7.9",
|
|
37
37
|
"winston": "^3.19.0",
|
package/src/postmortem.ts
CHANGED
|
@@ -76,9 +76,34 @@ function formatFatalError(label: string, err: Error): string {
|
|
|
76
76
|
return `\n[${label}] ${name}: ${message}${formattedStack}\n`;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* True for terminal/pipe write failures (EIO, EPIPE, EBADF, …) on the write side.
|
|
81
|
+
*
|
|
82
|
+
* Bun flushes TTY writes asynchronously, so when the controlling terminal hangs up
|
|
83
|
+
* (terminal closed, external volume stalled, SSH dropped) a restore write issued during
|
|
84
|
+
* shutdown can surface as an uncaughtException on a later tick — after cleanup has already
|
|
85
|
+
* begun. Treating that as fatal logs a scary stack and exits nonzero, which a hosting
|
|
86
|
+
* terminal renders as a crash/restart loop. When it happens mid-shutdown it is a benign
|
|
87
|
+
* artifact, not a real fault. We only match write-side syscalls so genuine bugs (a read
|
|
88
|
+
* EIO, a logic error that happens to carry one of these codes) are never masked.
|
|
89
|
+
*/
|
|
90
|
+
function isBenignIoWriteError(err: unknown): boolean {
|
|
91
|
+
if (!err || typeof err !== "object") return false;
|
|
92
|
+
const { code, syscall } = err as { code?: unknown; syscall?: unknown };
|
|
93
|
+
const isIoCode =
|
|
94
|
+
code === "EIO" ||
|
|
95
|
+
code === "EPIPE" ||
|
|
96
|
+
code === "EBADF" ||
|
|
97
|
+
code === "ENXIO" ||
|
|
98
|
+
code === "ENOTTY" ||
|
|
99
|
+
code === "ECONNRESET";
|
|
100
|
+
return isIoCode && (syscall === undefined || syscall === "write" || syscall === "writev");
|
|
101
|
+
}
|
|
102
|
+
|
|
79
103
|
if (isMainThread) {
|
|
80
104
|
process
|
|
81
105
|
.on("SIGINT", async () => {
|
|
106
|
+
logger.debug("Shutting down on SIGINT");
|
|
82
107
|
await runCleanup(Reason.SIGINT);
|
|
83
108
|
process.exit(130); // 128 + SIGINT (2)
|
|
84
109
|
})
|
|
@@ -90,6 +115,19 @@ if (isMainThread) {
|
|
|
90
115
|
safeStderrWrite(`Inspector opened: ${url}\n`);
|
|
91
116
|
})
|
|
92
117
|
.on("uncaughtException", async err => {
|
|
118
|
+
// A terminal/stdout write can fail asynchronously (EIO/EPIPE) while we are
|
|
119
|
+
// already tearing down — e.g. the controlling terminal hung up, or the volume
|
|
120
|
+
// the runtime lives on stalled. Bun surfaces that on a later tick, after cleanup
|
|
121
|
+
// has begun. Treat it as a benign shutdown artifact: log at debug and let the
|
|
122
|
+
// in-progress shutdown own the exit, instead of logging a fatal error and exiting
|
|
123
|
+
// nonzero (which a hosting terminal renders as a crash/restart loop).
|
|
124
|
+
if (cleanupStage !== "idle" && isBenignIoWriteError(err)) {
|
|
125
|
+
logger.debug("Ignoring benign terminal I/O write error during shutdown", {
|
|
126
|
+
code: (err as { code?: unknown }).code,
|
|
127
|
+
syscall: (err as { syscall?: unknown }).syscall,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
93
131
|
safeStderrWrite(formatFatalError("Uncaught Exception", err));
|
|
94
132
|
logger.error("Uncaught exception", { err, stack: err.stack });
|
|
95
133
|
await runCleanup(Reason.UNCAUGHT_EXCEPTION);
|
|
@@ -106,10 +144,12 @@ if (isMainThread) {
|
|
|
106
144
|
void runCleanup(Reason.EXIT); // fire and forget (exit imminent)
|
|
107
145
|
})
|
|
108
146
|
.on("SIGTERM", async () => {
|
|
147
|
+
logger.warn("Shutting down on SIGTERM (terminated by another process)");
|
|
109
148
|
await runCleanup(Reason.SIGTERM);
|
|
110
149
|
process.exit(143); // 128 + SIGTERM (15)
|
|
111
150
|
})
|
|
112
151
|
.on("SIGHUP", async () => {
|
|
152
|
+
logger.warn("Shutting down on SIGHUP (controlling terminal hung up / disconnected)");
|
|
113
153
|
await runCleanup(Reason.SIGHUP);
|
|
114
154
|
process.exit(129); // 128 + SIGHUP (1)
|
|
115
155
|
});
|