instar 1.3.210 → 1.3.212

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 (32) hide show
  1. package/.claude/skills/autonomous/SKILL.md +11 -3
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +11 -0
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  6. package/dist/core/PostUpdateMigrator.js +30 -0
  7. package/dist/core/PostUpdateMigrator.js.map +1 -1
  8. package/dist/core/SessionRecoveryChannel.d.ts +103 -0
  9. package/dist/core/SessionRecoveryChannel.d.ts.map +1 -0
  10. package/dist/core/SessionRecoveryChannel.js +158 -0
  11. package/dist/core/SessionRecoveryChannel.js.map +1 -0
  12. package/dist/core/SessionRecoveryConsumer.d.ts +60 -0
  13. package/dist/core/SessionRecoveryConsumer.d.ts.map +1 -0
  14. package/dist/core/SessionRecoveryConsumer.js +110 -0
  15. package/dist/core/SessionRecoveryConsumer.js.map +1 -0
  16. package/dist/core/StuckInputSentinel.d.ts +59 -2
  17. package/dist/core/StuckInputSentinel.d.ts.map +1 -1
  18. package/dist/core/StuckInputSentinel.js +127 -1
  19. package/dist/core/StuckInputSentinel.js.map +1 -1
  20. package/dist/core/types.d.ts +23 -0
  21. package/dist/core/types.d.ts.map +1 -1
  22. package/dist/core/types.js.map +1 -1
  23. package/dist/lifeline/TelegramLifeline.d.ts +1 -0
  24. package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
  25. package/dist/lifeline/TelegramLifeline.js +28 -0
  26. package/dist/lifeline/TelegramLifeline.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/data/builtin-manifest.json +21 -21
  29. package/upgrades/1.3.211.md +63 -0
  30. package/upgrades/1.3.212.md +65 -0
  31. package/upgrades/side-effects/autonomous-stop-hook-path-fix.md +156 -0
  32. package/upgrades/side-effects/codex-wedge-self-recovery.md +162 -0
@@ -0,0 +1,63 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The autonomous-mode stop hook was being registered in `settings.json` at
9
+ `.instar/hooks/instar/autonomous-stop-hook.sh` — a path where the hook is **never
10
+ deployed**. The hook ships only in the skill directory
11
+ (`.claude/skills/autonomous/hooks/autonomous-stop-hook.sh`). So on every `Stop`
12
+ event the hook command hit a missing file, failed silently, and the autonomous
13
+ loop never re-injected the task list. The session stayed alive but stopped
14
+ self-continuing between turns — it only advanced when the user sent a message.
15
+
16
+ Three coordinated fixes:
17
+
18
+ 1. **`.claude/skills/autonomous/SKILL.md` (Step 2a)** now registers the deployed
19
+ skill path and self-heals: it strips any prior (including wrong-path)
20
+ autonomous stop-hook entry and writes exactly one correct-path entry.
21
+ 2. **`PostUpdateMigrator.ensureAutonomousStopHook()`** gained a repair pass that
22
+ rewrites any existing wrong-path registration in `settings.json` to the skill
23
+ path. Without it, the "already registered?" presence check treated the
24
+ wrong-path entry as present and never corrected it — so existing agents would
25
+ stay broken forever.
26
+ 3. **`PostUpdateMigrator.migrateAutonomousStopHookTopicKeyed()`** re-deploys the
27
+ fixed `SKILL.md` to existing agents, gated on a marker unique to the corrected
28
+ prompt (stock prompts upgrade; customized prompts are left untouched).
29
+
30
+ ## What to Tell Your User
31
+
32
+ If you ran an autonomous session and noticed it went quiet between turns —
33
+ continuing only when you messaged it, rather than driving itself forward — this
34
+ fixes the cause. The loop's re-engagement step was pointed at a file that was
35
+ never installed, so it silently did nothing each time the session tried to
36
+ continue. After this update the autonomous loop re-engages on its own as
37
+ intended, and any agent that already had the broken registration is repaired
38
+ automatically on update. No action is needed on your side.
39
+
40
+ ## Summary of New Capabilities
41
+
42
+ - Autonomous sessions reliably self-continue between turns (the stop hook now
43
+ resolves to a file that actually exists).
44
+ - Existing agents with the broken registration are healed in place on update —
45
+ both the `settings.json` entry and the deployed skill prompt.
46
+ - The skill registration is now self-healing: activating autonomous mode always
47
+ ends with exactly one correct-path stop-hook entry, regardless of prior state.
48
+
49
+ ## Evidence
50
+
51
+ - New unit suite `tests/unit/PostUpdateMigrator-autonomousHookPath.test.ts`
52
+ (9 tests): wrong-path → repaired; no duplicate added; siblings untouched;
53
+ already-correct → no-op; missing → registered; idempotent; SKILL.md content
54
+ asserts the corrected path + marker; topic-keyed migration re-deploys a stock
55
+ SKILL.md and skips a customized one.
56
+ - Empirical check of the SKILL.md Step 2a snippet against a settings file
57
+ carrying the wrong-path entry plus sibling hooks: the wrong entry is rewritten
58
+ to `bash ${CLAUDE_PROJECT_DIR}/.claude/skills/autonomous/hooks/autonomous-stop-hook.sh`,
59
+ siblings are preserved, and a second run is a no-op (idempotent).
60
+ - Existing autonomous/migration suites stay green
61
+ (`autonomous-skill-deployment`, `PostUpdateMigrator-buildStopHook`,
62
+ `autonomous-stop-hook-notify`, `installCodexHooks`, `emit-session-clock` —
63
+ 50 tests). `tsc --noEmit` clean.
@@ -0,0 +1,65 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ A codex conversational session can wedge: the server is healthy and messages are
9
+ delivered, but the session sits paused with an injected message stuck at the
10
+ prompt, never draining into a turn. `StuckInputSentinel` already detects this
11
+ (marker-based), but its recovery topped out at keypresses — which, live, weren't
12
+ enough; the session needed a full server restart + queue replay, performed by
13
+ hand. This adds an **escalating self-recovery** so a codex session heals itself
14
+ with no external nudge.
15
+
16
+ The hard part is a process boundary: the detector (`StuckInputSentinel`) runs in
17
+ the server process, but the restart authority (`ServerSupervisor` + queue replay)
18
+ runs in the lifeline process. So the fix is split cleanly:
19
+
20
+ - **`SessionRecoveryChannel`** — a cross-process request/ack channel. The server
21
+ emits recovery requests (sole writer of the request file); the lifeline writes
22
+ acks (sole writer of the ack file) — single-writer-per-file, atomic, so the two
23
+ processes never race.
24
+ - **Sentinel escalation** — after the keypress ladder exhausts but the marker is
25
+ still stuck, the sentinel requests a tier-C recovery, polls the ack, verifies,
26
+ and bounds the wait (no restart loop).
27
+ - **`SessionRecoveryConsumer`** (lifeline) — reads tier-C requests and performs
28
+ `performGracefulRestart` + `replayQueue`, dry-run-first, guarded by a **durable**
29
+ cooldown (a server restart wipes the sentinel's in-memory bound, so the loop
30
+ guard has to survive the restart).
31
+
32
+ Ships **dark** behind `monitoring.codexWedgeRecovery` (default off; `dryRun:true`
33
+ first). With no config it is byte-for-byte the legacy behavior.
34
+
35
+ ## What to Tell Your User
36
+
37
+ When one of my codex-driven sessions gets stuck — paused with a message that never
38
+ turns into a reply — I can now work myself loose without anyone stepping in:
39
+ first by nudging the prompt, and if that is not enough, by cleanly restarting my
40
+ own server and replaying the queued message. It is turned off by default and ships
41
+ in a watch-only dry-run mode first, with a built-in cooldown so it can never get
42
+ into a restart loop. You decide when to turn it on.
43
+
44
+ ## Summary of New Capabilities
45
+
46
+ - A codex session that wedges (input delivered, not draining) can self-recover by
47
+ escalating from keypresses to a server restart + queue replay — no external agent.
48
+ - The escalation is bounded and has a durable cross-restart cooldown, so a wedge
49
+ that a restart can't fix can never cause a restart loop.
50
+ - Ships dark + dry-run on the Graduated-Feature-Rollout track
51
+ (`monitoring.codexWedgeRecovery`); absence of config = off, so no migration is
52
+ needed and existing agents are unaffected until explicitly enabled.
53
+
54
+ ## Evidence
55
+
56
+ - 78 tests across 3 tiers: unit (`SessionRecoveryChannel` 15, `StuckInputSentinel`
57
+ escalation 7, `SessionRecoveryConsumer` 8, plus the existing stuck-input suites)
58
+ + integration (`tests/integration/codex-wedge-recovery.test.ts` 3 — the full
59
+ sentinel→channel→consumer→ack→sentinel loop through the real channel, live +
60
+ dry-run + dark-disabled paths).
61
+ - `tsc --noEmit` clean; `pnpm build` clean.
62
+ - Dark-by-default verified: with no config, the sentinel never escalates and the
63
+ lifeline never starts the consumer (asserted in unit + integration).
64
+ - Spec: `docs/specs/CODEX-SESSION-WEDGE-SELF-RECOVERY.md` (grounded gap-analysis +
65
+ cross-process design + the durable-cooldown finding + the increment plan).
@@ -0,0 +1,156 @@
1
+ # Side-Effects Review — Autonomous loop re-engagement fix (stop-hook registration path)
2
+
3
+ **Version / slug:** `autonomous-stop-hook-path-fix`
4
+ **Date:** `2026-06-03`
5
+ **Author:** `Echo`
6
+ **Second-pass reviewer:** `Echo (self, single-pass — Tier-1 fix)`
7
+
8
+ ## Summary of the change
9
+
10
+ The autonomous-mode stop hook is what re-injects the task list on every `Stop`
11
+ event so an autonomous session keeps working between turns. It was registered in
12
+ `settings.json` at `.instar/hooks/instar/autonomous-stop-hook.sh`, but the hook
13
+ is only ever deployed to the skill dir (`.claude/skills/autonomous/hooks/`). The
14
+ registered path therefore pointed at a non-existent file: every `Stop` failed
15
+ silently and the loop never re-engaged. Files touched:
16
+ `.claude/skills/autonomous/SKILL.md` (Step 2a registration snippet),
17
+ `src/core/PostUpdateMigrator.ts` (`ensureAutonomousStopHook` repair pass +
18
+ `migrateAutonomousStopHookTopicKeyed` SKILL.md re-deploy), and a new unit suite.
19
+ The decision surface is purely *which file path is written into the Stop-hook
20
+ registration* — there is no message block/allow logic.
21
+
22
+ ## Decision-point inventory
23
+
24
+ - `SKILL.md Step 2a registration` — modify — registers the deployed skill path
25
+ (was the never-deployed `.instar/hooks/instar/` path) and self-heals stale entries.
26
+ - `PostUpdateMigrator.ensureAutonomousStopHook` — modify — adds a repair pass that
27
+ rewrites wrong-path entries before the existing presence check.
28
+ - `PostUpdateMigrator.migrateAutonomousStopHookTopicKeyed` — modify — adds SKILL.md
29
+ to the marker-gated re-deploy list so existing agents receive the fixed prompt.
30
+ - No message-gating / block-allow decision point is touched.
31
+
32
+ ---
33
+
34
+ ## 1. Over-block
35
+
36
+ No block/allow surface — over-block not applicable. The change only corrects a
37
+ filesystem path string in a hook registration; it never rejects any input.
38
+
39
+ ---
40
+
41
+ ## 2. Under-block
42
+
43
+ No block/allow surface — under-block not applicable. One adjacent correctness
44
+ note: the repair targets specifically the `.instar/hooks/instar/autonomous-stop-hook.sh`
45
+ wrong path. If a future custom registration used some *third* wrong path, the
46
+ repair would not catch it — but that path was never shipped by instar, so there
47
+ is nothing in the install base to miss. The registration helper still adds a
48
+ correct entry if none exists at all.
49
+
50
+ ---
51
+
52
+ ## 3. Level-of-abstraction fit
53
+
54
+ Correct layer. This is a deterministic settings/file-path migration — exactly the
55
+ class `PostUpdateMigrator` owns (Migration Parity Standard). The SKILL.md snippet
56
+ is the install/activation-time registrar; the migrator is the in-place repair for
57
+ already-deployed agents. No reasoning/LLM layer is involved or appropriate: the
58
+ correct path is a fixed, known string, so a structural fix is the right tool (and
59
+ matches the sibling `build-stop-hook.sh` path-repair precedent).
60
+
61
+ ---
62
+
63
+ ## 4. Signal vs authority compliance
64
+
65
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
66
+
67
+ - [x] No — this change has no block/allow surface.
68
+
69
+ It rewrites a hook-registration path in `settings.json`; it holds no message or
70
+ operation authority. The autonomous stop hook itself (unchanged here) remains the
71
+ authority for whether a session continues, and it is session-id-gated.
72
+
73
+ ---
74
+
75
+ ## 5. Interactions
76
+
77
+ - **Shadowing:** The repair pass runs *before* the existing `hasAutonomousHook`
78
+ presence check inside `ensureAutonomousStopHook`. This is intentional and is the
79
+ core of the fix: previously the presence check matched the wrong-path entry and
80
+ short-circuited the correct registration, so the wrong path was permanent. After
81
+ repair, the entry already holds the correct path, so the presence check finds it
82
+ and adds no duplicate (verified by the "no duplicate" test).
83
+ - **Double-fire:** Two registrars exist — the SKILL.md Step 2a (activation-time,
84
+ dynamic add/remove) and `ensureAutonomousStopHook` (update-time, permanent). Both
85
+ now converge on the *same* command string, so they can no longer disagree or
86
+ produce two divergent entries. The SKILL.md snippet strips-then-appends, and the
87
+ migrator's presence check prevents duplicates; combined they always yield exactly
88
+ one correct entry (verified).
89
+ - **Races:** No shared concurrent state. `settings.json` is edited by the same
90
+ single-writer migration path as every other hook registration.
91
+ - **Feedback loops:** None. A one-time path correction; the corrected entry is a
92
+ fixed string, so subsequent passes are no-ops (idempotency verified).
93
+
94
+ ---
95
+
96
+ ## 6. External surfaces
97
+
98
+ - **Other agents on the machine:** none — per-project `settings.json` only.
99
+ - **Install base:** existing agents are healed on next update (the intended
100
+ surface). The `migrateAutonomousStopHookTopicKeyed` re-deploy is marker-gated and
101
+ fingerprint-guarded, so a customized `SKILL.md` (missing the stock
102
+ `ALL_TASKS_COMPLETE` fingerprint) is left untouched — verified by test.
103
+ - **External systems (Telegram/Slack/GitHub/Cloudflare):** none.
104
+ - **Persistent state:** `.claude/settings.json` and the deployed `SKILL.md`. The
105
+ edit is convergent (always lands on the one correct entry), so re-running is safe.
106
+ - **Timing/runtime:** none — the migration is synchronous on the existing update path.
107
+
108
+ ---
109
+
110
+ ## 7. Rollback cost
111
+
112
+ Pure code + template change. Back-out is `git revert` + ship the next patch. There
113
+ is one consideration: this migration *writes a corrected entry* into existing
114
+ agents' `settings.json`. A revert would stop applying the correction but would NOT
115
+ re-break already-corrected agents (the correct path resolves to a real file, so a
116
+ reverted build leaves them working). No data migration, no agent reset, no
117
+ user-visible regression during the rollback window. Worst case of the fix itself
118
+ being wrong: an agent ends up with a Stop-hook entry pointing at the skill path —
119
+ which is exactly where the file lives — so the failure mode of the fix is "it
120
+ works".
121
+
122
+ ---
123
+
124
+ ## Conclusion
125
+
126
+ The review produced no design changes. The fix is a narrow, deterministic path
127
+ correction with a self-healing registrar and an in-place repair migration that
128
+ satisfies the Migration Parity Standard for both the `settings.json` entry and the
129
+ deployed skill prompt. The only decision surface is a fixed path string, the
130
+ repair is idempotent and convergent, and the customized-prompt guard is preserved.
131
+ Clear to ship.
132
+
133
+ ---
134
+
135
+ ## Second-pass review (if required)
136
+
137
+ **Reviewer:** Echo (self) — Tier-1 fix, single-pass per the tiered process.
138
+ **Independent read of the artifact: concur**
139
+
140
+ The change has no message-gating surface, the repair-before-presence-check
141
+ ordering is the load-bearing correctness point and is tested, and the Migration
142
+ Parity obligations (settings repair + marker-gated SKILL.md re-deploy + customized
143
+ guard) are all covered. No concerns.
144
+
145
+ ---
146
+
147
+ ## Evidence pointers
148
+
149
+ - `tests/unit/PostUpdateMigrator-autonomousHookPath.test.ts` — 9 tests (repair,
150
+ no-duplicate, sibling-preservation, no-op-when-correct, register-when-missing,
151
+ idempotency, SKILL.md content marker, topic-keyed re-deploy + customized-skip).
152
+ - Empirical SKILL.md snippet run against a wrong-path settings fixture: wrong entry
153
+ rewritten to the skill path, siblings preserved, second run idempotent.
154
+ - Regression: `autonomous-skill-deployment`, `PostUpdateMigrator-buildStopHook`,
155
+ `autonomous-stop-hook-notify`, `installCodexHooks`, `emit-session-clock` — 50
156
+ tests green. `tsc --noEmit` clean.
@@ -0,0 +1,162 @@
1
+ # Side-Effects Review — Codex session-wedge self-recovery
2
+
3
+ **Version / slug:** `codex-wedge-self-recovery`
4
+ **Date:** `2026-06-03`
5
+ **Author:** `Echo`
6
+ **Second-pass reviewer:** `Echo (self) — Tier-2, single-pass under standing preapproval; ships dark`
7
+
8
+ ## Summary of the change
9
+
10
+ Adds an escalating self-recovery for wedged codex sessions (input delivered but
11
+ not draining). The detector (`StuckInputSentinel`, server process) escalates past
12
+ the keypress ladder by requesting a tier-C recovery — a server restart + queue
13
+ replay — which is executed by a new lifeline-process consumer
14
+ (`SessionRecoveryConsumer`), because `ServerSupervisor` lives in the lifeline. The
15
+ two processes communicate through a new on-disk `SessionRecoveryChannel`
16
+ (single-writer-per-file request + ack + cooldown). Files: `SessionRecoveryChannel.ts`
17
+ (new), `SessionRecoveryConsumer.ts` (new), `StuckInputSentinel.ts` (escalation state
18
+ machine + recovered-request cleanup), `types.ts` (config shape), `server.ts` +
19
+ `TelegramLifeline.ts` (wiring). Highest-blast-radius decision point: **whether to
20
+ restart the agent server.** Ships dark (`monitoring.codexWedgeRecovery`, default
21
+ off + dryRun).
22
+
23
+ ## Decision-point inventory
24
+
25
+ - `StuckInputSentinel exhausted-record path` — modify — after the keypress ladder,
26
+ escalate (request tier C) instead of stopping, when enabled.
27
+ - `SessionRecoveryConsumer.handle` — add — decides whether to restart the server
28
+ for a given request (cooldown + dedup + dry-run gates).
29
+ - `monitoring.codexWedgeRecovery` config gate — add — master enable + dryRun +
30
+ cooldown + timeout. Default off.
31
+ - No message block/allow decision point is touched.
32
+
33
+ ---
34
+
35
+ ## 1. Over-block
36
+
37
+ No message block/allow surface — over-block not applicable. The closest analogue
38
+ is "would it restart a server it shouldn't?" Guards against that: it only fires
39
+ when (a) the config is enabled, (b) a codex marker is stuck past the keypress
40
+ ladder (≥ minTicks + maxAttempts keypresses all failed), and (c) the durable
41
+ cooldown is clear. In dry-run (the shipping default) it never restarts at all.
42
+
43
+ ---
44
+
45
+ ## 2. Under-block
46
+
47
+ No message block/allow surface. Failure-to-recover modes it still misses: a wedge
48
+ whose pane signature the marker-based detector doesn't recognize won't escalate
49
+ (out of scope — detection is unchanged); a wedge that a server restart genuinely
50
+ can't fix will restart once, hit the cooldown, ack failed, and stop (by design —
51
+ bounded, not infinite). Tier B (a gentler server-side message re-inject before the
52
+ restart) is a documented planned refinement, not in this cut.
53
+
54
+ ---
55
+
56
+ ## 3. Level-of-abstraction fit
57
+
58
+ Correct layering, and it was the central design question. Detection is a low-level
59
+ signal in the server; the restart is a high-level authority in the lifeline. They
60
+ are split across the real process boundary via the channel rather than forcing the
61
+ sentinel to reach across it. Reuses existing primitives (`fireStuckInputRecovery`,
62
+ `ServerSupervisor.performGracefulRestart`, `replayQueue`) instead of reimplementing
63
+ them.
64
+
65
+ ---
66
+
67
+ ## 4. Signal vs authority compliance
68
+
69
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
70
+
71
+ - [x] No — this change has no message block/allow surface.
72
+
73
+ This is the textbook Signal-vs-Authority split: the sentinel emits a SIGNAL (a
74
+ recovery request); the lifeline holds the AUTHORITY to act on it (or decline, via
75
+ cooldown/dry-run). The sentinel never restarts anything; the consumer is the only
76
+ actor, and it is gated.
77
+
78
+ ---
79
+
80
+ ## 5. Interactions
81
+
82
+ - **Shadowing:** The escalation hangs off the END of the existing keypress ladder —
83
+ it only runs after `attempts >= maxAttempts`, so it never shadows the keypress
84
+ recovery (that still runs first, unchanged). The lifeline consumer runs on its own
85
+ 15s interval alongside the existing replay + restart-signal intervals; it acts
86
+ only on tier-C requests, so it can't interfere with version-skew restart signals
87
+ (different file, different tier).
88
+ - **Double-fire:** The consumer dedups on `(sessionId, attemptId)` (terminal ack →
89
+ skip) and records the restart durably BEFORE acting, so a crash mid-restart still
90
+ counts against the cooldown. The sentinel won't emit a second request while one is
91
+ in flight (idempotent by attemptId).
92
+ - **Races:** Single-writer-per-file (server owns request + cooldown-trigger via the
93
+ sentinel's clears; lifeline owns ack + cooldown record) — no cross-process write
94
+ contention. Atomic writes via SafeFsExecutor.
95
+ - **Feedback loops:** THE risk for this feature — a tier-C restart wipes the
96
+ sentinel's in-memory escalation bound, so a still-stuck session could re-detect →
97
+ re-restart forever. Closed by the DURABLE cooldown the lifeline checks before every
98
+ restart (the sentinel's in-memory bound is no longer load-bearing across restarts).
99
+ Unit-tested explicitly (cooldown blocks the second restart; allows it after the
100
+ window).
101
+
102
+ ---
103
+
104
+ ## 6. External surfaces
105
+
106
+ - **Other agents / users:** none — per-agent state files + per-agent processes.
107
+ - **External systems:** none (Telegram/Slack/GitHub untouched). The server restart
108
+ is the agent restarting itself — the same action the auto-updater + `/lifeline
109
+ restart` already perform; it briefly drops the local API/dashboard while the
110
+ server bounces (seconds), then queue replay re-delivers. This only happens when
111
+ the feature is enabled AND not in dry-run.
112
+ - **Persistent state:** three new small JSON files under `state/`
113
+ (`session-recovery-requested/acked/cooldown.json`). Inert when the feature is off.
114
+ - **Timing:** the consumer ticks every 15s only when enabled.
115
+
116
+ ---
117
+
118
+ ## 7. Rollback cost
119
+
120
+ Pure code + new files; the feature is dark by default. Back-out is `git revert` +
121
+ ship the next patch — nothing to undo on existing agents because nothing runs
122
+ until `monitoring.codexWedgeRecovery.enabled` is set (which no agent has). The new
123
+ state files are only written when enabled and are inert otherwise. No data
124
+ migration, no user-visible regression. Disabling live is a config flip (set
125
+ `enabled:false` or `dryRun:true`).
126
+
127
+ ---
128
+
129
+ ## Conclusion
130
+
131
+ The review's central concern — the restart-loop feedback path — was found DURING
132
+ the build (a tier-C restart wipes the in-memory bound) and closed with a durable
133
+ cooldown, which is the load-bearing safety mechanism and is unit-tested. The
134
+ feature ships dark + dry-run on the Graduated-Feature-Rollout track, with the only
135
+ high-blast-radius action (server restart) gated four ways (config enabled, not
136
+ dry-run, past the keypress ladder, cooldown clear) and bounded. Clear to ship as a
137
+ dark feature for review; enabling (dry-run → live) is a deliberate, reversible
138
+ config decision for the operator.
139
+
140
+ ---
141
+
142
+ ## Second-pass review (if required)
143
+
144
+ **Reviewer:** Echo (self) — Tier-2, ships dark; recommend a dry-run soak before live.
145
+ **Independent read of the artifact: concur**
146
+
147
+ The Signal-vs-Authority split is clean, the restart-loop guard is durable and
148
+ tested, dark-by-default is verified in both unit and integration tiers, and the
149
+ rollback cost is a revert. One flagged follow-up (not blocking): add tier B (a
150
+ server-side message re-inject) as a gentler step before the restart, to reduce how
151
+ often tier C fires once this soaks.
152
+
153
+ ---
154
+
155
+ ## Evidence pointers
156
+
157
+ - `tests/unit/SessionRecoveryChannel.test.ts` (15), `tests/unit/StuckInputSentinel-escalation.test.ts` (7),
158
+ `tests/unit/SessionRecoveryConsumer.test.ts` (8), `tests/integration/codex-wedge-recovery.test.ts` (3),
159
+ plus existing stuck-input suites — 78 total green.
160
+ - `docs/specs/CODEX-SESSION-WEDGE-SELF-RECOVERY.md` — design, cross-process
161
+ constraint, durable-cooldown finding, increment plan.
162
+ - `tsc --noEmit` + `pnpm build` clean.