instar 1.3.664 → 1.3.666

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.
Files changed (43) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +52 -0
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/IdleErrorClassifier.d.ts +44 -0
  5. package/dist/core/IdleErrorClassifier.d.ts.map +1 -0
  6. package/dist/core/IdleErrorClassifier.js +87 -0
  7. package/dist/core/IdleErrorClassifier.js.map +1 -0
  8. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  9. package/dist/core/PostUpdateMigrator.js +9 -0
  10. package/dist/core/PostUpdateMigrator.js.map +1 -1
  11. package/dist/core/SessionManager.d.ts +19 -0
  12. package/dist/core/SessionManager.d.ts.map +1 -1
  13. package/dist/core/SessionManager.js +71 -3
  14. package/dist/core/SessionManager.js.map +1 -1
  15. package/dist/core/paneTail.d.ts +38 -0
  16. package/dist/core/paneTail.d.ts.map +1 -0
  17. package/dist/core/paneTail.js +64 -0
  18. package/dist/core/paneTail.js.map +1 -0
  19. package/dist/core/types.d.ts +11 -0
  20. package/dist/core/types.d.ts.map +1 -1
  21. package/dist/core/types.js.map +1 -1
  22. package/dist/monitoring/PermissionPromptAutoResolver.d.ts +215 -0
  23. package/dist/monitoring/PermissionPromptAutoResolver.d.ts.map +1 -0
  24. package/dist/monitoring/PermissionPromptAutoResolver.js +475 -0
  25. package/dist/monitoring/PermissionPromptAutoResolver.js.map +1 -0
  26. package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
  27. package/dist/monitoring/PresenceProxy.js +18 -0
  28. package/dist/monitoring/PresenceProxy.js.map +1 -1
  29. package/dist/monitoring/StuckSignatureClassifier.d.ts +1 -1
  30. package/dist/monitoring/StuckSignatureClassifier.d.ts.map +1 -1
  31. package/dist/monitoring/StuckSignatureClassifier.js +29 -3
  32. package/dist/monitoring/StuckSignatureClassifier.js.map +1 -1
  33. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  34. package/dist/monitoring/guardManifest.js +15 -0
  35. package/dist/monitoring/guardManifest.js.map +1 -1
  36. package/dist/monitoring/guardPosture.d.ts.map +1 -1
  37. package/dist/monitoring/guardPosture.js +19 -0
  38. package/dist/monitoring/guardPosture.js.map +1 -1
  39. package/package.json +1 -1
  40. package/scripts/lint-guard-manifest.js +1 -0
  41. package/src/data/builtin-manifest.json +20 -20
  42. package/upgrades/1.3.666.md +131 -0
  43. package/upgrades/side-effects/idle-error-tailgate-corroboration.md +65 -0
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-25T10:17:36.090Z",
5
- "instarVersion": "1.3.664",
4
+ "generatedAt": "2026-06-25T23:12:52.494Z",
5
+ "instarVersion": "1.3.666",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -11,7 +11,7 @@
11
11
  "domain": "identity",
12
12
  "sourcePath": "src/core/PostUpdateMigrator.ts",
13
13
  "installedPath": ".instar/hooks/instar/session-start.sh",
14
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
14
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
15
15
  "since": "2025-01-01"
16
16
  },
17
17
  "hook:dangerous-command-guard": {
@@ -20,7 +20,7 @@
20
20
  "domain": "safety",
21
21
  "sourcePath": "src/core/PostUpdateMigrator.ts",
22
22
  "installedPath": ".instar/hooks/instar/dangerous-command-guard.sh",
23
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
23
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
24
24
  "since": "2025-01-01"
25
25
  },
26
26
  "hook:grounding-before-messaging": {
@@ -29,7 +29,7 @@
29
29
  "domain": "safety",
30
30
  "sourcePath": "src/core/PostUpdateMigrator.ts",
31
31
  "installedPath": ".instar/hooks/instar/grounding-before-messaging.sh",
32
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
32
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
33
33
  "since": "2025-01-01"
34
34
  },
35
35
  "hook:compaction-recovery": {
@@ -38,7 +38,7 @@
38
38
  "domain": "identity",
39
39
  "sourcePath": "src/core/PostUpdateMigrator.ts",
40
40
  "installedPath": ".instar/hooks/instar/compaction-recovery.sh",
41
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
41
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
42
42
  "since": "2025-01-01"
43
43
  },
44
44
  "hook:external-operation-gate": {
@@ -47,7 +47,7 @@
47
47
  "domain": "safety",
48
48
  "sourcePath": "src/core/PostUpdateMigrator.ts",
49
49
  "installedPath": ".instar/hooks/instar/external-operation-gate.js",
50
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
50
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
51
51
  "since": "2025-01-01"
52
52
  },
53
53
  "hook:deferral-detector": {
@@ -56,7 +56,7 @@
56
56
  "domain": "safety",
57
57
  "sourcePath": "src/core/PostUpdateMigrator.ts",
58
58
  "installedPath": ".instar/hooks/instar/deferral-detector.js",
59
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
59
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
60
60
  "since": "2025-01-01"
61
61
  },
62
62
  "hook:self-stop-guard": {
@@ -65,7 +65,7 @@
65
65
  "domain": "coherence",
66
66
  "sourcePath": "src/core/PostUpdateMigrator.ts",
67
67
  "installedPath": ".instar/hooks/instar/self-stop-guard.js",
68
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
68
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
69
69
  "since": "2025-01-01"
70
70
  },
71
71
  "hook:post-action-reflection": {
@@ -74,7 +74,7 @@
74
74
  "domain": "evolution",
75
75
  "sourcePath": "src/core/PostUpdateMigrator.ts",
76
76
  "installedPath": ".instar/hooks/instar/post-action-reflection.js",
77
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
77
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
78
78
  "since": "2025-01-01"
79
79
  },
80
80
  "hook:external-communication-guard": {
@@ -83,7 +83,7 @@
83
83
  "domain": "safety",
84
84
  "sourcePath": "src/core/PostUpdateMigrator.ts",
85
85
  "installedPath": ".instar/hooks/instar/external-communication-guard.js",
86
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
86
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
87
87
  "since": "2025-01-01"
88
88
  },
89
89
  "hook:scope-coherence-collector": {
@@ -92,7 +92,7 @@
92
92
  "domain": "coherence",
93
93
  "sourcePath": "src/core/PostUpdateMigrator.ts",
94
94
  "installedPath": ".instar/hooks/instar/scope-coherence-collector.js",
95
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
95
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
96
96
  "since": "2025-01-01"
97
97
  },
98
98
  "hook:scope-coherence-checkpoint": {
@@ -101,7 +101,7 @@
101
101
  "domain": "coherence",
102
102
  "sourcePath": "src/core/PostUpdateMigrator.ts",
103
103
  "installedPath": ".instar/hooks/instar/scope-coherence-checkpoint.js",
104
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
104
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
105
105
  "since": "2025-01-01"
106
106
  },
107
107
  "hook:free-text-guard": {
@@ -110,7 +110,7 @@
110
110
  "domain": "safety",
111
111
  "sourcePath": "src/core/PostUpdateMigrator.ts",
112
112
  "installedPath": ".instar/hooks/instar/free-text-guard.sh",
113
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
113
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
114
114
  "since": "2025-01-01"
115
115
  },
116
116
  "hook:claim-intercept": {
@@ -119,7 +119,7 @@
119
119
  "domain": "coherence",
120
120
  "sourcePath": "src/core/PostUpdateMigrator.ts",
121
121
  "installedPath": ".instar/hooks/instar/claim-intercept.js",
122
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
122
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
123
123
  "since": "2025-01-01"
124
124
  },
125
125
  "hook:claim-intercept-response": {
@@ -128,7 +128,7 @@
128
128
  "domain": "coherence",
129
129
  "sourcePath": "src/core/PostUpdateMigrator.ts",
130
130
  "installedPath": ".instar/hooks/instar/claim-intercept-response.js",
131
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
131
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
132
132
  "since": "2025-01-01"
133
133
  },
134
134
  "hook:stop-gate-router": {
@@ -137,7 +137,7 @@
137
137
  "domain": "safety",
138
138
  "sourcePath": "src/core/PostUpdateMigrator.ts",
139
139
  "installedPath": ".instar/hooks/instar/stop-gate-router.js",
140
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
140
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
141
141
  "since": "2025-01-01"
142
142
  },
143
143
  "hook:auto-approve-permissions": {
@@ -146,7 +146,7 @@
146
146
  "domain": "safety",
147
147
  "sourcePath": "src/core/PostUpdateMigrator.ts",
148
148
  "installedPath": ".instar/hooks/instar/auto-approve-permissions.js",
149
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
149
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
150
150
  "since": "2025-01-01"
151
151
  },
152
152
  "job:health-check": {
@@ -1538,7 +1538,7 @@
1538
1538
  "type": "subsystem",
1539
1539
  "domain": "sessions",
1540
1540
  "sourcePath": "src/core/SessionManager.ts",
1541
- "contentHash": "7ea591c3a48b752c066a2fdf0179804a6319177b3664bde1bcc20d9fc0a0d508",
1541
+ "contentHash": "07c12341d721732f00a07e617140a0b4c6a3ad89d0c450c78a79f052d835be1e",
1542
1542
  "since": "2025-01-01"
1543
1543
  },
1544
1544
  "subsystem:auto-updater": {
@@ -1562,7 +1562,7 @@
1562
1562
  "type": "subsystem",
1563
1563
  "domain": "updates",
1564
1564
  "sourcePath": "src/core/PostUpdateMigrator.ts",
1565
- "contentHash": "1498dde6dd7c50457734d66245474781b703ecea97ea6d51391ca4de91ddaf3d",
1565
+ "contentHash": "3f5b94f3892478477e6048a95a3b4b565c4b4050b803ad777356843b249c8889",
1566
1566
  "since": "2025-01-01"
1567
1567
  },
1568
1568
  "subsystem:scheduler": {
@@ -0,0 +1,131 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ An Instar session could be silently, permanently wedged by an agent-framework approval
9
+ prompt it cannot answer. Claude Code 2.1.176-177 added a hardcoded Bash safety prompt —
10
+ `cd <dir> && <cmd>` combined with a redirect/pipe pops "Do you want to proceed? ❯ 1. Yes
11
+ / 2. No / Esc to cancel" — that runs BEFORE all permission rules and PermissionRequest
12
+ hooks, so `--dangerously-skip-permissions` and `permissions.allow` cannot suppress it.
13
+ A remote-driven session (Telegram/dashboard) cannot press a terminal key, so it freezes
14
+ while still *looking* busy (PresenceProxy could even report "actively working"). The
15
+ empirically-confirmed reason it bit the fleet: the existing PromptGate/AutoApprover
16
+ pipeline was persisted `enabled:false` on deployed agents (a stale-`false` config wins
17
+ over a newer `true` default), so nothing answered the prompt.
18
+
19
+ - **New `src/monitoring/PermissionPromptAutoResolver.ts`** — an always-on, deterministic,
20
+ zero-LLM safety floor driven once per monitoring tick. It detects a live, focused
21
+ approval menu in a session's tmux tail and sends the approve keystroke itself.
22
+ - **Layer 2 (auto-answer):** requires ≥2 distinct registered prose patterns AND a live
23
+ glyph-led `❯`-cursor-on-an-approve-option line at the genuine bottom of the tail
24
+ while not generating; sends **`Enter`** only (confirm the highlighted "Yes"), with a
25
+ re-capture-before-send check. Because a text capture cannot *perfectly* distinguish a
26
+ real TUI selector from displayed content containing the same characters, this is
27
+ bar-raising mitigation (stated honestly), bounded by Enter-only (a false match is a
28
+ benign empty submit), a per-episode `MAX_ATTEMPTS=3` (reset-on-clear so distinct
29
+ prompts are independent), and a terminal defect.
30
+ - **Layer 3 (broad detector):** a prose-agnostic `detectPersistingMenu` surfaces ANY
31
+ persisting glyph-led menu the auto-answer declines (cursor-not-on-approve, or a future
32
+ prompt whose wording drifts) as a single deduped Attention defect — so nothing is ever
33
+ silently stranded, and it doubles as the prompt-string drift detector.
34
+ - **`src/core/paneTail.ts`** gains a pure `leadGlyphsOf(line)` helper so the detector can
35
+ test for the `❯` selector glyph (U+276F) specifically.
36
+ - **`StuckSignatureClassifier`** gains an `approval-prompt-waiting` kind; **`PresenceProxy`**
37
+ never surfaces it as a user message (the sole user-facing surface is the terminal
38
+ defect), killing the false "actively working" report for a prompt-wedged session.
39
+ - **The floor is unconditional — no persisted `enabled:true` to rot to `false`** (the
40
+ exact trap that caused the bug). The only opt-out is `monitoring.permissionPromptAutoResolver.emergencyDisable`
41
+ (absent ⇒ on). A computed guard-posture key (`guardPosture.ts`) + a runtime
42
+ `guardStatus()` + a `GUARD_MANIFEST` entry make a disabled floor a visible incident in
43
+ `GET /guards`.
44
+ - Existing wedged sessions recover automatically: the server restart that deploys the
45
+ update restarts the monitor loop, which auto-clears any session currently parked on the
46
+ prompt. No new setting, no manual step.
47
+
48
+ The resolver answers a UI-flow prompt only; it never widens what commands are allowed
49
+ (the destructive `dangerous-command-guard` denylist remains an independent guard). The
50
+ auto-approve is accepted under the operator full-machine-access trust model. Converged
51
+ through 6 spec-review rounds (6 internal reviewers + codex/gemini cross-model).
52
+
53
+ CMT-1785: the idle-error detector that decides "did this paused session stall on a transient API
54
+ error (nudge it) or stop normally (leave it)?" is migrated from a brittle bare-substring scan to a
55
+ tail-gated, frame-discriminated SIGNAL.
56
+
57
+ - New pure modules `src/core/paneTail.ts` (shared `liveTail`/`stripLineLead`/`wasGlyphLed` — ONE
58
+ definition of "the live tail", extracted so it is no longer copied per consumer) and
59
+ `src/core/IdleErrorClassifier.ts` (`classifyIdleError`).
60
+ - The old gate `TERMINAL_ERROR_PATTERNS.some(p => recentOutput.includes(p))` (a bare `.includes()`
61
+ over the whole capture) is replaced by `classifyIdleError`, which fires only when a terminal-error
62
+ token sits in the live tail (last 20 non-empty lines) on a Claude-EMITTED error frame — a two-tier
63
+ begins-with rule: **Tier A** the line begins with `API Error:`; **Tier B** the line is glyph-led
64
+ (`⏺`/`⎿`/…) and begins with one of the 11 patterns. The match set is a strict subset of the old
65
+ buffer-wide match, so the change can only SUPPRESS spurious fires, never add one.
66
+ - The idle-error capture is widened 30→45 rows (`RATE_LIMIT_SETTLED_CAPTURE_LINES`): Claude Code's
67
+ input box + footer render 15-25 rows BELOW the error, so a 30-row capture could miss it entirely.
68
+ - `StuckSignatureClassifier` is migrated onto the shared `liveTail` (behavior-preserving via
69
+ `.join('\n')`; its existing test corpus is the characterization guard).
70
+ - A once-per-idle-episode structured record (`{event:'idle-error-classify', result:'fired'|'suppressed', …}`)
71
+ lands in `logs/server.log` so a wave of suppressions on genuine errors (the under-fire risk) is
72
+ observable. Cleared in both the re-arm block and `sessionComplete` (no leak on the death path).
73
+
74
+ The signal still feeds the existing `apiErrorAtIdle` → `rateLimitSentinel` recovery actuator and gains
75
+ no blocking authority (Signal vs Authority).
76
+
77
+ ## What to Tell Your User
78
+
79
+ Your agent will no longer get silently stuck on a pop-up it can't answer. A recent
80
+ update to the coding tool added a safety pop-up ("Do you want to proceed? Yes / No") that
81
+ freezes the session, because nobody can press a key on it from your phone — and there's no
82
+ setting to turn it off. Now a small always-on layer answers that pop-up itself, only ever
83
+ pressing the harmless "Yes", and it's built into the code with no on/off switch that could
84
+ get stuck "off" (a stuck-off setting is exactly what caused the freeze). If it ever hits a
85
+ pop-up it genuinely can't clear (say a future version changes the wording), it stops and
86
+ sends you one "please take a look" notice instead of freezing silently. Nothing for you to
87
+ do; your agent just stays reachable.
88
+
89
+ Nothing you need to do. Under the hood, the agent got more reliable at telling a session that genuinely
90
+ stalled on an API error (which it nudges back to life) apart from one that's merely *showing* an old or
91
+ quoted error on screen. The old check was a blunt text search that sometimes kicked off a needless
92
+ recovery on a healthy session, and — more importantly — sometimes read too little of the screen and
93
+ missed a real stall. The new check looks at the live bottom of the screen, reads enough of it to clear
94
+ the input box, and only counts a line the tool actually emitted as an error. Net: fewer false recoveries
95
+ AND fewer missed real ones, so paused sessions get the right treatment faster. No new setting; behavior
96
+ is otherwise unchanged, and it's a one-line revert if ever needed.
97
+
98
+ ## Summary of New Capabilities
99
+
100
+ - An always-on safety floor that auto-answers an un-answerable framework approval prompt,
101
+ so a remote-driven session is never silently wedged.
102
+ - Honest, bounded detection (live glyph-led menu, Enter-only, re-capture, attempt cap) +
103
+ a prose-agnostic backstop so a drifted/unrecognized prompt surfaces rather than strands.
104
+ - Visible in `GET /guards` (`on-confirmed`); an audit trail at
105
+ `logs/permission-prompt-resolver.jsonl` (matched-pattern names only, never raw tail);
106
+ emergency opt-out via `monitoring.permissionPromptAutoResolver.emergencyDisable`.
107
+
108
+ - `paneTail` + `IdleErrorClassifier` — a precise, tail-gated, frame-discriminated idle-error signal
109
+ replacing the bare buffer-wide substring scan; widened capture (45 rows) so the input-box chrome can
110
+ no longer hide a real error; structured once-per-episode observability of fired-vs-suppressed
111
+ decisions. Internal robustness improvement to session-stall recovery; no user-facing surface to
112
+ configure.
113
+
114
+ ## Evidence
115
+
116
+ - `tests/unit/IdleErrorClassifier.test.ts` (28) — both sides of every boundary, pinned to REAL captured
117
+ render fixtures: fires on `⏺ API Error:` / ` ⎿ API Error:` / glyph-led network tokens / wrapped
118
+ errors; suppresses stale-scrollback, prose mentions, quoted source literals (the self-collision case),
119
+ a tool's own `Error: …ECONNREFUSED`; the 45-vs-30 input-box-chrome capture test; parametrized over all
120
+ 11 patterns; bounded audit fields; paneTail helpers.
121
+ - `tests/integration/idle-error-classifier-production-wiring.test.ts` (7) — the REAL exported
122
+ `TERMINAL_ERROR_PATTERNS` produce correct decisions on real panes; the SessionManager call-site is
123
+ wired to the classifier, the 45-row capture, the dual once-per-episode clears, and the structured record.
124
+ - `tests/e2e/idle-error-classifier-lifecycle.test.ts` (2) — the real SessionManager constructs and the
125
+ `apiErrorAtIdle` recovery handoff is attachable; the production-pattern live decision is correct.
126
+ - `tests/unit/monitoring/StuckSignatureClassifier.test.ts` (13) — unchanged, the characterization guard
127
+ proving the shared-`liveTail` migration is behavior-preserving.
128
+ - `npm run build` + `tsc --noEmit` clean; full unit suite green.
129
+ - Driven by the converged + approved spec; convergence report at
130
+ `docs/specs/reports/idle-error-tailgate-corroboration-convergence.md` (4 rounds; the review caught a
131
+ CRITICAL under-fire-on-the-common-error-form flaw before any code).
@@ -0,0 +1,65 @@
1
+ # Side-Effects Review — Idle-Error Detection: Tail-Gated, Frame-Discriminated Signal (CMT-1785)
2
+
3
+ Spec: `docs/specs/idle-error-tailgate-corroboration.md` (converged 4 rounds, approved).
4
+ Change: replace the bare `TERMINAL_ERROR_PATTERNS.some(includes)` idle-error detector at the
5
+ `SessionManager` idle path with a tail-gated, frame-discriminated classifier (`IdleErrorClassifier`)
6
+ fed by a shared `paneTail` helper; widen the idle-error capture 30→45 rows; add a once-per-episode
7
+ structured observability record.
8
+
9
+ ## 1. Over-block (what legitimate inputs does this reject that it shouldn't?)
10
+ The classifier can SUPPRESS a fire the old bare match would have made — that is the whole point —
11
+ but the risk is suppressing a GENUINE error (under-fire). Mitigated: (a) the two-tier begins-with
12
+ grammar fires on real Claude render forms (`⏺ API Error:` / ` ⎿ API Error:`), pinned by tests using
13
+ the actual captured fixtures; (b) the 45-row capture clears the input-box chrome that pushes the
14
+ error up; (c) the 20-line non-empty tail clears realistic chrome with margin; (d) a missed error
15
+ falls through to the pre-existing 15m idle-kill (slower recovery, never silent loss); (e) the
16
+ `result:'suppressed'` structured record makes a wave of real misses observable.
17
+
18
+ ## 2. Under-block (what failure modes does it still miss?)
19
+ - A genuine Claude error rendered in a form that is neither `API Error:`-led nor a glyph-led
20
+ begins-with-pattern line (unknown future render) → missed → 15m idle-kill fallback. The drift
21
+ test fails loudly if the known render fixtures stop matching.
22
+ - A residual false-POSITIVE (accepted): a tool result rendered glyph-led whose line begins with a
23
+ terminal token (e.g. `⎿ fetch failed`) fires. Bounded: the actuator is non-destructive (one wasted
24
+ nudge the verify step proves a no-op). Documented in the spec §5.
25
+
26
+ ## 3. Level-of-abstraction fit
27
+ Correct layer: this is a deterministic SIGNAL-emitter (Signal vs Authority), feeding the existing
28
+ `apiErrorAtIdle` → `rateLimitSentinel` recovery actuator. It gains no blocking authority. The shared
29
+ `paneTail` helper is the right home for the tail-gating logic (was being copied per consumer). The
30
+ recovery actuator it feeds is honestly named a deterministic Tier-0 non-destructive self-heal (nudge
31
+ → verify → escalate-to-notice + zombie-kill veto; no respawn).
32
+
33
+ ## 4. Signal vs authority compliance
34
+ Compliant. The classifier returns a boolean SIGNAL; the actuator decides and verifies before any
35
+ action. The match set is a strict subset of the old buffer-wide match (tail + frame ⊂ buffer-wide),
36
+ so the change is monotonic-suppressing — it can only reduce spurious actuations, never add one.
37
+
38
+ ## 5. Interactions
39
+ - The `detectRateLimited` branch runs FIRST and is untouched — a server throttle still routes to
40
+ `rateLimitedAtIdle` and never reaches the classifier (pinned by the wiring test's intent; the
41
+ branch order is preserved). The wider 45-row capture is a no-op for `detectRateLimited` (it slices
42
+ its own last ~20 lines).
43
+ - `errorNudgedSessions` (the nudge episode guard) is unchanged; the new `idleErrorClassified` set
44
+ mirrors its full lifecycle (armed once/episode; cleared in BOTH the re-arm block AND
45
+ `sessionComplete` — no leak on the session-death path).
46
+ - `StuckSignatureClassifier` migrates to the shared `liveTail` via `.join('\n')` — behavior-preserving
47
+ (its existing 13-test corpus passes unchanged = the characterization guard).
48
+
49
+ ## 6. External surfaces
50
+ None new. No HTTP route, no config, no agent-installed file, no user-facing message. The only new
51
+ output is structured `console.log` records to `logs/server.log` (operator log), once per idle episode,
52
+ credential-free (matchedPattern is a fixed token; no raw pane content is logged).
53
+
54
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
55
+ Machine-local BY DESIGN. The classifier reads one local tmux pane on the host running the session —
56
+ no cross-machine state, no generated URL, no replicated surface. The downstream `rateLimitSentinel`
57
+ has a Telegram-notify surface, but the change is monotonic-suppressing, so it can only REDUCE
58
+ user-facing cross-machine notices, never add one. Correct posture, declared explicitly.
59
+
60
+ ## 8. Rollback cost
61
+ Single-commit revert: restore the bare `.some(includes)` gate + the 30-row capture, delete
62
+ `IdleErrorClassifier.ts` + `paneTail.ts` + tests + the `idleErrorClassified` field/clears + the
63
+ structured record, and revert the `StuckSignatureClassifier` import (behavior-preserving, mechanical).
64
+ No migration, no durable state, no config. The revert IS the immediate operator action if a future
65
+ Claude render change is ever suspected of under-firing.