@ouro.bot/cli 0.1.0-alpha.258 → 0.1.0-alpha.259
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/changelog.json +6 -0
- package/dist/heart/daemon/daemon-entry.js +18 -4
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.259",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix(daemon): SIGINT and SIGTERM now ALWAYS write a tombstone before exiting, instead of silently skipping when `_gracefulShutdown` was set. The previous behavior made signal-driven shutdowns invisible in `~/.ouro-cli/daemon-death.json` \u2014 launchd policy decisions, the OOM killer, manual `kill`, and `killOrphanProcesses` from a sibling daemon all looked identical to a clean exit. After a real outage where the user's daemon kept dying with a tombstone from a week earlier, this restores observability: every signal-driven exit now records `reason: \"sigint\"` or `reason: \"sigterm\"` with timestamp + recentCrashes accumulator. The catch-all `process.on('exit')` handler also no longer short-circuits on graceful shutdown."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.258",
|
|
6
12
|
"changes": [
|
|
@@ -195,7 +195,15 @@ void daemon.start().then(() => {
|
|
|
195
195
|
process.exit(1);
|
|
196
196
|
});
|
|
197
197
|
process.on("SIGINT", () => {
|
|
198
|
-
|
|
198
|
+
// ALWAYS write a tombstone, even on signal-driven shutdown. The previous
|
|
199
|
+
// behavior was to set _gracefulShutdown=true and skip the tombstone, which
|
|
200
|
+
// meant ANY external SIGINT/SIGTERM (launchd policy, OOM killer, manual
|
|
201
|
+
// kill, killOrphanProcesses from a sibling daemon) silently disappeared
|
|
202
|
+
// from the death log. The user lost weeks of visibility into why their
|
|
203
|
+
// daemon kept dying. Tombstones are informational — having a "sigint"
|
|
204
|
+
// tombstone is strictly better than silence.
|
|
205
|
+
_tombstoneWritten = true;
|
|
206
|
+
(0, daemon_tombstone_1.writeDaemonTombstone)("sigint", new Error("daemon received SIGINT"));
|
|
199
207
|
for (const s of habitSchedulers) {
|
|
200
208
|
s.stopWatching();
|
|
201
209
|
s.stop();
|
|
@@ -204,7 +212,8 @@ process.on("SIGINT", () => {
|
|
|
204
212
|
void daemon.stop().then(() => process.exit(0));
|
|
205
213
|
});
|
|
206
214
|
process.on("SIGTERM", () => {
|
|
207
|
-
|
|
215
|
+
_tombstoneWritten = true;
|
|
216
|
+
(0, daemon_tombstone_1.writeDaemonTombstone)("sigterm", new Error("daemon received SIGTERM"));
|
|
208
217
|
for (const s of habitSchedulers) {
|
|
209
218
|
s.stopWatching();
|
|
210
219
|
s.stop();
|
|
@@ -221,7 +230,6 @@ process.stderr?.on?.("error", () => { });
|
|
|
221
230
|
/* v8 ignore start -- global exception handlers: genuinely untestable in vitest; exercised by real daemon crashes @preserve */
|
|
222
231
|
let _uncaughtCount = 0;
|
|
223
232
|
let _tombstoneWritten = false;
|
|
224
|
-
let _gracefulShutdown = false;
|
|
225
233
|
let _lastKnownCause = null;
|
|
226
234
|
const CIRCUIT_BREAKER_WINDOW_MS = 60_000;
|
|
227
235
|
const CIRCUIT_BREAKER_MAX = 10;
|
|
@@ -270,8 +278,14 @@ process.on("unhandledRejection", (reason) => {
|
|
|
270
278
|
});
|
|
271
279
|
// Catch-all: write tombstone on any exit where we didn't already record the cause.
|
|
272
280
|
// process.on('exit') is synchronous-only — writeDaemonTombstone uses writeFileSync, so it works.
|
|
281
|
+
//
|
|
282
|
+
// Previously this skipped writing if `_gracefulShutdown` was true, which made
|
|
283
|
+
// SIGINT/SIGTERM-driven exits invisible in the death log. The signal handlers
|
|
284
|
+
// above now always write their own tombstone before exiting, so this catch-all
|
|
285
|
+
// only runs for exits the signal handlers didn't reach (e.g. process.exit
|
|
286
|
+
// called from somewhere unexpected).
|
|
273
287
|
process.on("exit", (code) => {
|
|
274
|
-
if (_tombstoneWritten
|
|
288
|
+
if (_tombstoneWritten)
|
|
275
289
|
return;
|
|
276
290
|
const reason = code === 0 ? "unexpectedCleanExit" : "unexpectedExit";
|
|
277
291
|
const error = _lastKnownCause ?? new Error(`daemon exited with code ${code} (no specific cause captured)`);
|