instar 1.3.224 → 1.3.225

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.
@@ -5,4 +5,7 @@
5
5
  * safe default).
6
6
  */
7
7
  export declare function isNonFatalUncaught(err: unknown): boolean;
8
+ export declare function shouldLogStackForUncaught(err: unknown): boolean;
9
+ /** Test-only: reset the dedup memory so each test starts from a clean slate. */
10
+ export declare function __resetUncaughtStackDedupeForTests(): void;
8
11
  //# sourceMappingURL=uncaughtExceptionPolicy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"uncaughtExceptionPolicy.d.ts","sourceRoot":"","sources":["../../src/core/uncaughtExceptionPolicy.ts"],"names":[],"mappings":"AA2CA;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAIxD"}
1
+ {"version":3,"file":"uncaughtExceptionPolicy.d.ts","sourceRoot":"","sources":["../../src/core/uncaughtExceptionPolicy.ts"],"names":[],"mappings":"AA2CA;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAIxD;AAyBD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAQ/D;AAED,gFAAgF;AAChF,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD"}
@@ -52,4 +52,42 @@ export function isNonFatalUncaught(err) {
52
52
  return false;
53
53
  return NON_FATAL_UNCAUGHT_PATTERNS.some((p) => msg.includes(p));
54
54
  }
55
+ /**
56
+ * Decides whether to attach the FULL stack trace when logging a suppressed
57
+ * non-fatal uncaught exception. These isolated races recur (the HTTP
58
+ * double-response one — "Cannot set headers after they are sent" — fires
59
+ * ~10-20x/hour), so logging the stack on EVERY occurrence would flood the log.
60
+ * Yet without ANY stack the offending call site is undiagnosable: the throw
61
+ * originates in node's http internals, so the suppressed message alone carries
62
+ * no location — the route that double-responded is invisible.
63
+ *
64
+ * The fix: log the full stack the FIRST time a given stack is seen, then
65
+ * message-only for repeats. That surfaces each distinct originating call path
66
+ * exactly once (enough to find + fix the real double-send) without the flood.
67
+ *
68
+ * Dedup key is the full `err.stack` (not just the top frames): the distinguishing
69
+ * frame for an HTTP double-response is the application route DEEP in the stack —
70
+ * the top frames are always the same node internals — so only the whole stack
71
+ * separates one origin from another. Process-local + bounded (cleared past
72
+ * MAX_TRACKED_STACKS so a pathological variety of stacks can't grow it without
73
+ * limit). Returns false for non-Error / stackless input.
74
+ */
75
+ const seenUncaughtStacks = new Set();
76
+ const MAX_TRACKED_STACKS = 200;
77
+ export function shouldLogStackForUncaught(err) {
78
+ if (!(err instanceof Error) || !err.stack)
79
+ return false;
80
+ if (seenUncaughtStacks.has(err.stack))
81
+ return false;
82
+ // Bound memory: if a pathological variety of distinct stacks accumulates,
83
+ // reset rather than grow without limit (re-surfacing after the reset is fine).
84
+ if (seenUncaughtStacks.size >= MAX_TRACKED_STACKS)
85
+ seenUncaughtStacks.clear();
86
+ seenUncaughtStacks.add(err.stack);
87
+ return true;
88
+ }
89
+ /** Test-only: reset the dedup memory so each test starts from a clean slate. */
90
+ export function __resetUncaughtStackDedupeForTests() {
91
+ seenUncaughtStacks.clear();
92
+ }
55
93
  //# sourceMappingURL=uncaughtExceptionPolicy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"uncaughtExceptionPolicy.js","sourceRoot":"","sources":["../../src/core/uncaughtExceptionPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,2BAA2B,GAAG;IAClC,0EAA0E;IAC1E,8EAA8E;IAC9E,wCAAwC;IACxC,iBAAiB;IACjB,uBAAuB;IACvB,4BAA4B;IAC5B,2EAA2E;IAC3E,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,uBAAuB;IACvB,6EAA6E;IAC7E,sEAAsE;IACtE,8EAA8E;IAC9E,uEAAuE;IACvE,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,gFAAgF;IAChF,6EAA6E;IAC7E,yDAAyD;IACzD,2BAA2B;CAC5B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAY;IAC7C,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACpF,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,OAAO,2BAA2B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC"}
1
+ {"version":3,"file":"uncaughtExceptionPolicy.js","sourceRoot":"","sources":["../../src/core/uncaughtExceptionPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,2BAA2B,GAAG;IAClC,0EAA0E;IAC1E,8EAA8E;IAC9E,wCAAwC;IACxC,iBAAiB;IACjB,uBAAuB;IACvB,4BAA4B;IAC5B,2EAA2E;IAC3E,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,uBAAuB;IACvB,6EAA6E;IAC7E,sEAAsE;IACtE,8EAA8E;IAC9E,uEAAuE;IACvE,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,gFAAgF;IAChF,6EAA6E;IAC7E,yDAAyD;IACzD,2BAA2B;CAC5B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAY;IAC7C,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACpF,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,OAAO,2BAA2B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAU,CAAC;AAC7C,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,MAAM,UAAU,yBAAyB,CAAC,GAAY;IACpD,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACxD,IAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACpD,0EAA0E;IAC1E,+EAA+E;IAC/E,IAAI,kBAAkB,CAAC,IAAI,IAAI,kBAAkB;QAAE,kBAAkB,CAAC,KAAK,EAAE,CAAC;IAC9E,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,kCAAkC;IAChD,kBAAkB,CAAC,KAAK,EAAE,CAAC;AAC7B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.224",
3
+ "version": "1.3.225",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-03T14:11:31.808Z",
5
- "instarVersion": "1.3.224",
4
+ "generatedAt": "2026-06-03T14:47:08.272Z",
5
+ "instarVersion": "1.3.225",
6
6
  "entryCount": 196,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,46 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Makes suppressed non-fatal uncaught exceptions **diagnosable** by logging their stack
9
+ trace once per origin.
10
+
11
+ **The problem.** The server's `uncaughtException` handler logs a small allowlist of
12
+ isolated/recoverable errors and continues (instead of crashing) — chiefly the HTTP
13
+ double-response race `Cannot set headers after they are sent`. But it logged only
14
+ `err.message`, no stack. Observed live: echo (~34×/2h) and codey (~26×/2h) emit
15
+ `[WARN] Non-fatal uncaught exception (suppressed): Cannot set headers after they are sent`
16
+ with **no location** — and since that error is thrown deep in node's http internals, the
17
+ message alone can't tell you which route double-responded. The real double-send bug is
18
+ therefore un-findable from the logs.
19
+
20
+ **The fix.** A new `shouldLogStackForUncaught(err)` in `uncaughtExceptionPolicy.ts`: the
21
+ handler now attaches the **full stack the first time a given stack is seen**, then logs
22
+ message-only for repeats. That surfaces each distinct offending call path exactly once —
23
+ enough to find and fix the real double-send — without flooding the log (these races recur
24
+ ~10–20×/hour). The dedup key is the whole `err.stack` (the distinguishing route frame is
25
+ deep, not at the top); the tracking set is bounded (cleared past 200 distinct stacks).
26
+
27
+ This is a **diagnostic** change — it does not alter which exceptions are suppressed vs.
28
+ fatal (`isNonFatalUncaught` is unchanged), only how the suppressed ones are logged.
29
+
30
+ ## What to Tell Your User
31
+
32
+ Nothing required — internal observability (agent-only). The next time the harmless-but-noisy
33
+ "Cannot set headers" race fires, the log will include the originating stack once, so the
34
+ underlying double-respond can be located and fixed.
35
+
36
+ ## Summary of New Capabilities
37
+
38
+ None — observability/diagnostic only. No new routes, config, or surfaces.
39
+
40
+ ## Evidence
41
+
42
+ - Live: echo/codey `logs/server.log` — `[WARN] Non-fatal uncaught exception (suppressed):
43
+ Cannot set headers after they are sent` recurring with no stack.
44
+ - `src/commands/server.ts` uncaughtException handler now calls `shouldLogStackForUncaught`.
45
+ - Tests: `tests/unit/uncaughtExceptionPolicy.test.ts` — first-logs-then-suppresses,
46
+ distinct-origin-surfaced-once, non-Error/stackless → false, reset re-surfaces, cap bound.
@@ -0,0 +1,56 @@
1
+ # Side-Effects Review — Suppressed-uncaught stack capture
2
+
3
+ **Version / slug:** `uncaught-suppressed-stack-capture`
4
+ **Date:** `2026-06-03`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required (Tier-1 — additive diagnostic helper + one log-line change; no control-flow/decision change)`
7
+
8
+ ## Summary of the change
9
+
10
+ The server's `uncaughtException` handler suppresses an allowlist of isolated errors
11
+ (HTTP double-response, Slack reconnect, standby read-only write) and logs `err.message`.
12
+ For the recurring `Cannot set headers after they are sent` race the message has no location
13
+ (thrown in node internals), so the offending route is un-findable. Added
14
+ `shouldLogStackForUncaught(err)` to `uncaughtExceptionPolicy.ts` — logs the full stack the
15
+ first time a given stack is seen, message-only thereafter. The handler appends that stack
16
+ when the helper returns true. `isNonFatalUncaught` (the suppress-vs-crash decision) is
17
+ unchanged.
18
+
19
+ ## Decision-point inventory
20
+
21
+ 1. **Dedup key = full `err.stack` (not top-N frames).** For an HTTP double-response the
22
+ top frames are always node's http internals; only the deep application frame separates
23
+ one route from another, so the whole stack is the right key. Errs toward surfacing more
24
+ distinct origins (good for diagnosis), not fewer.
25
+ 2. **First-occurrence-only logging (vs. always / rate-limited count).** These races fire
26
+ ~10–20×/hour; logging the stack every time would flood. Once-per-origin gives the route
27
+ exactly once (sufficient to fix the real bug). Chosen over a time-window rate-limit
28
+ because the goal is "reveal each origin once," not "sample volume."
29
+ 3. **Bounded tracking (clear past 200).** A pathological variety of distinct stacks can't
30
+ grow the Set without limit; clearing re-surfaces stacks after the cap, which is benign
31
+ (a little extra logging) and far better than unbounded memory.
32
+
33
+ ## 1. Behavior / safety
34
+
35
+ No change to which exceptions crash vs. continue — `isNonFatalUncaught` and the FATAL
36
+ branch are untouched. The only effect is additional (bounded) log output on the
37
+ already-suppressed path. Cannot introduce a crash (the helper is pure + guarded for
38
+ non-Error/stackless input → returns false).
39
+
40
+ ## 2. Log volume
41
+
42
+ Net REDUCES noise risk vs. a naive "always log stack": one stack per origin, then
43
+ message-only. The first-occurrence stacks are a small, finite set (the handful of
44
+ double-respond origins + the other allowlisted errors).
45
+
46
+ ## 3. Blast radius / reversibility
47
+
48
+ One new pure function + one log-line edit in the handler. Fully reversible (revert both).
49
+ No schema/config/route/external surface. Memory: one process-local bounded `Set<string>`.
50
+
51
+ ## 4. Tests
52
+
53
+ `tests/unit/uncaughtExceptionPolicy.test.ts` (existing isNonFatalUncaught suite unchanged) +
54
+ a new `shouldLogStackForUncaught` block covering: first-logs-then-suppresses, same-stack
55
+ fresh-Error still deduped, distinct-origin each surfaced once, non-Error/stackless → false,
56
+ reset re-surfaces, and the cap-bound clear path. 10 tests pass.
@@ -0,0 +1,22 @@
1
+ # Suppressed-crash stack capture (#72) — ELI16
2
+
3
+ Viewable private artifact:
4
+ https://echo.dawn-tunnel.dev/view/311d1657-692e-48ca-a112-04c82fa74dec?sig=6e66a9515e16c2e28156a94169f7737cb86dd0c661440267eed93dc730ebd632
5
+
6
+ The agent server has a short list of harmless-but-annoying errors it deliberately swallows
7
+ instead of crashing — the main one is an HTTP race called "Cannot set headers after they are
8
+ sent" (two replies sent for one web request). Swallowing it is correct: the request is already
9
+ handled, and crashing the whole agent would be far worse.
10
+
11
+ The snag: it only logged the error MESSAGE, not WHERE it came from. And that error is thrown
12
+ deep inside Node's plumbing, so the message alone never says which piece of code double-replied.
13
+ It fires 10–20 times an hour on each agent, completely un-findable.
14
+
15
+ This change logs the full stack trace the FIRST time each distinct origin appears, then goes
16
+ quiet (message-only) for the repeats. So the offending code reveals itself once — enough to
17
+ actually fix the real double-reply — without flooding the log. Nothing about what gets swallowed
18
+ vs. crashed changes; only how the swallowed ones are logged.
19
+
20
+ _Patch · branch `echo/double-send-stack-capture` · found by scanning live fleet logs (the
21
+ see-what-breaks loop). Step 1 (make it findable); the real double-send fix follows once the next
22
+ occurrence reveals the route._