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.
- package/.claude/skills/autonomous/SKILL.md +11 -3
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +11 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +30 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionRecoveryChannel.d.ts +103 -0
- package/dist/core/SessionRecoveryChannel.d.ts.map +1 -0
- package/dist/core/SessionRecoveryChannel.js +158 -0
- package/dist/core/SessionRecoveryChannel.js.map +1 -0
- package/dist/core/SessionRecoveryConsumer.d.ts +60 -0
- package/dist/core/SessionRecoveryConsumer.d.ts.map +1 -0
- package/dist/core/SessionRecoveryConsumer.js +110 -0
- package/dist/core/SessionRecoveryConsumer.js.map +1 -0
- package/dist/core/StuckInputSentinel.d.ts +59 -2
- package/dist/core/StuckInputSentinel.d.ts.map +1 -1
- package/dist/core/StuckInputSentinel.js +127 -1
- package/dist/core/StuckInputSentinel.js.map +1 -1
- package/dist/core/types.d.ts +23 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts +1 -0
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +28 -0
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +21 -21
- package/upgrades/1.3.211.md +63 -0
- package/upgrades/1.3.212.md +65 -0
- package/upgrades/side-effects/autonomous-stop-hook-path-fix.md +156 -0
- 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.
|