@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 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
- _gracefulShutdown = true;
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
- _gracefulShutdown = true;
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 || _gracefulShutdown)
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)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.258",
3
+ "version": "0.1.0-alpha.259",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",