instar 1.3.873 → 1.3.875

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.873",
3
+ "version": "1.3.875",
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-07-18T20:16:47.485Z",
5
- "instarVersion": "1.3.873",
4
+ "generatedAt": "2026-07-18T20:25:13.928Z",
5
+ "instarVersion": "1.3.875",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
14
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
23
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
32
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
41
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
50
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
59
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
68
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
77
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
86
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
95
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
104
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
113
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
122
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
131
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
140
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
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": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
149
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
150
150
  "since": "2025-01-01"
151
151
  },
152
152
  "job:health-check": {
@@ -1562,7 +1562,7 @@
1562
1562
  "type": "subsystem",
1563
1563
  "domain": "updates",
1564
1564
  "sourcePath": "src/core/PostUpdateMigrator.ts",
1565
- "contentHash": "b8e7e8d2210384e18a9a676f344c306005757886bebdb97a5afb71678069a3f2",
1565
+ "contentHash": "e63a480f47775520068fe640a60c1f95edac847024de9fdb61dcfd5fe5e3e745",
1566
1566
  "since": "2025-01-01"
1567
1567
  },
1568
1568
  "subsystem:scheduler": {
@@ -0,0 +1,117 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The `mcp-health-autorefresh.sh` built-in hook (auto-restart-on-MCP-inaccessible: at
9
+ session start it probes `claude mcp list` and, when an allowlisted MCP like playwright
10
+ failed to connect, performs ONE loop-guarded `/sessions/refresh` so the tool re-registers)
11
+ was **silently inert in production on coreutils-less Macs** — the platform instar agents
12
+ actually run on. Its probe ran `timeout 45 claude mcp list`; bare `timeout` is a GNU
13
+ coreutils binary absent on stock macOS, the command-not-found was swallowed by
14
+ `2>/dev/null || true`, and the empty capture hit the script's quiet-exit guard. No error,
15
+ no log — the recovery feature simply never ran. Linux CI never saw it because `timeout`
16
+ always exists there.
17
+
18
+ The generated script (authored in `src/core/PostUpdateMigrator.ts` →
19
+ `getMcpHealthAutorefreshHook()`) now bounds the probe with the same portable timeout
20
+ LADDER the autonomous stop hook's real-check runner uses: `timeout` → `gtimeout` →
21
+ perl-alarm fallback (fork + setpgrp + group-KILL on a 45s alarm, exit 124 on timeout,
22
+ and the correct 128+signal exit mapping for a signal-killed child — GNU-timeout
23
+ semantics). With no bounded runner present at all, the script stays dark rather than run
24
+ the probe unbounded. All safety properties are untouched: dark by default,
25
+ explicit-false wins, allowlist-scoped, hard once-per-(session, failed-set) loop-guard.
26
+
27
+ Migration parity: this hook is always-overwrite in `migrateHooks()`, so every deployed
28
+ agent gets the fixed script automatically on this update — no manual step.
29
+
30
+ This also fixes the deterministic macOS-only failure of
31
+ `tests/unit/PostUpdateMigrator-mcpAutorefresh.test.ts`.
32
+
33
+ The autonomous stop hook's real-check verification runner is now portable across the
34
+ timeout-ladder rungs on the exit-status contract for signal-death. On macOS (no GNU
35
+ `timeout`/`gtimeout`), the perl fallback rung mapped a check command killed by a signal to
36
+ exit 0 via `exit($?>>8)` — the signal lives in the LOW byte of the status word, so the
37
+ high byte is 0. The routine trigger is SIGPIPE: the capture pipeline byte-caps combined
38
+ output at the source (`head -c`), and a verbose check still writing when the cap closes
39
+ the pipe dies from signal 13. The failing check was then scored PASS and the hook
40
+ **allowed the autonomous session to exit early** — a cardinal-invariant violation
41
+ (any verification failure must route to keep-working). Linux was unaffected because GNU
42
+ `timeout` maps signal-death to 128+signal (141, non-zero → FAIL). The perl rung now uses
43
+ the same mapping: `exit(($?&127) ? 128+($?&127) : ($?>>8))`. Timeout (124), spawn-fail
44
+ (127), and normal exits are unchanged.
45
+
46
+ Same chain, second portability fix: macOS iconv `-c` emits the correctly-scrubbed UTF-8
47
+ prefix but exits non-zero when the byte cap truncated a trailing multibyte character; the
48
+ old exit-code `||` fallback then ALSO ran the C-locale printable filter and appended its
49
+ output (a text-duplication hazard). The fallback now triggers only when iconv produced no
50
+ output from non-empty input. The pinned sanitize → UTF-8 scrub → leak-scrub → clamp order
51
+ is unchanged.
52
+
53
+ This also fixes the deterministic macOS-only failure of
54
+ `tests/unit/autonomous-stop-hook-realcheck.test.ts` ("invalid-UTF-8 capture … → next
55
+ payload still builds") — the shipped hook was fixed; the test was not weakened.
56
+
57
+ Instar now carries a canonical registry of every known way a framework session can stop (`src/data/stall-classes.ts`: eight classes, from mid-turn interrupts to context-window walls), and every supported framework must answer for each class in a stall-coverage matrix at `docs/frameworks/<framework>-stall-coverage.md`. A new validator (`src/core/stallCoverageValidator.ts`) enforces the standard structurally: exact status tokens (`covered | covered-dark | declared-gap | not-applicable`), resolvable detector/recovery symbols, positive-control evidence containing the framework's raw stall signature in a test the push suite actually collects, tracked refs on every declared gap, and a calendar aging ratchet on auto-seeded debt. A CI ratchet test in the whole-tree push suite validates all four seed matrices on every push, and an offline-first codemod (`scripts/stall-class-codemod.mjs`) seeds `declared-gap (new-class, unreviewed)` rows into every matrix whenever a class is added — so matrices cannot rot between onboardings.
58
+
59
+ The four seed matrices ship honest: claude-code writes its existing stall family down for the first time (six classes covered or covered-dark, one declared gap — the drive-5 defect #9 interrupted-resume class, one structural N/A); codex-cli is honest zeros (every class a declared gap, each filed to the framework-issues ledger with a commitment anchor); gemini-cli is all not-applicable (framework dead upstream, `revalidateOn: framework-revival`); pi-cli is honest declared gaps for a ships-dark framework.
60
+
61
+ This is PR-A of a two-PR staged landing. The runtime apprenticeship gate, acceptance machinery, and the recurring `stall-matrix-live-check` job are PR-B — nothing in PR-A gates any runtime behavior.
62
+
63
+ ## What to Tell Your User
64
+
65
+ If your agent runs on a Mac: a small self-healing feature that was accidentally asleep
66
+ now works. When an important tool server (like the browser tool) fails to come up at the
67
+ start of a session, the agent can restart that session once — automatically and at most
68
+ once — so the tool comes back, instead of quietly running without it. Nothing to
69
+ configure; the feature still respects its existing off-by-default/allowlist settings.
70
+
71
+ Nothing visible changes in day-to-day use. On Macs, autonomous work sessions are now
72
+ stricter about proving they're really done: a verification check that gets cut off
73
+ mid-output can no longer be mistaken for a passing check, so a session can't slip out
74
+ early on a technicality. Sessions that genuinely finish and pass their checks behave
75
+ exactly as before.
76
+
77
+ No user-visible behavior changes in this release. This is infrastructure honesty: your agent's platform now keeps a complete, continuously-validated map of how sessions can get stuck for every supported framework, so stall detection and recovery stop being learned one silent production stall at a time.
78
+
79
+ ## Summary of New Capabilities
80
+
81
+ - No new capabilities — a portability fix that makes the existing (config-gated)
82
+ MCP auto-refresh recovery actually run on macOS, identically to Linux.
83
+
84
+ - No new capabilities — a safety/portability fix. The autonomous completion guarantee
85
+ ("a failing real check always means keep working") now holds identically on macOS and
86
+ Linux.
87
+
88
+ - Canonical stall-class registry + per-framework stall-coverage matrices, CI-validated on every push.
89
+ - Class-registry codemod that keeps every matrix complete as the class list grows.
90
+ - Honest coverage baselines for claude-code, codex-cli, gemini-cli, and pi-cli (the claude-code stall family is now written down; every other framework's debt is tracked, not invisible).
91
+
92
+ ## Evidence
93
+
94
+ - Root cause reproduced on macOS 26 (no `timeout`/`gtimeout` installed): the probe line
95
+ produced an empty LIST and the script exited silently before any observable action.
96
+ - Perl-rung contract proven live on this machine: hung command bounded (exit 124),
97
+ normal exit codes passed through (3 → 3), signal-death mapped to 128+signal
98
+ (SIGTERM → 143), stdout captured intact.
99
+ - `tests/unit/PostUpdateMigrator-mcpAutorefresh.test.ts`: 9/9 pass (previously 1
100
+ deterministic failure on macOS), including `bash -n` syntax validity and the
101
+ always-overwrite migration-parity pin.
102
+ - 14 targeted migrator + stop-hook sibling test files: 139/139 pass on this branch.
103
+
104
+ - Instrumented reproduction on macOS 26 (Node 24): the shipped hook returned the
105
+ allow-exit message for `printf "\xe4\xb8\xad%.0s" $(seq 1 100000); exit 1`
106
+ (`PIPESTATUS[0]=0` from the perl rung) before the fix; after the fix it returns a valid
107
+ JSON `block` decision carrying the DATA-labeled, scrubbed, clamped output.
108
+ - `tests/unit/autonomous-stop-hook-realcheck.test.ts`: 24/24 pass on macOS (previously
109
+ 1 deterministic failure); all sibling stop-hook suites (9 files, 95 tests) and the
110
+ `PostUpdateMigrator` autonomous-hook suites (24 tests) pass.
111
+ - Full push suite (`vitest.push.config.ts`) run from the worktree: zero failures.
112
+
113
+ - The CI ratchet (`tests/unit/stall-coverage-ratchet.test.ts`) validates all four seed matrices, REQUIRED_MATRIX_FRAMEWORKS file presence, and spec-table/registry agreement — green on this tree.
114
+ - Validator boundary tests (`tests/unit/stall-coverage-validator.test.ts`) cover both sides of every hermetic decision boundary from the spec's §5 list.
115
+ - Evidence tests (`tests/unit/stall-evidence-claude-code.test.ts`) prove each claude-code covered-row detector genuinely fires on a realistic raw stall signature.
116
+ - Codemod fleet-regression tests (`tests/unit/stall-class-codemod.test.ts`) prove: class-addition-without-codemod reds stale matrices; the codemod seeds correctly, idempotently, and honors --dry-run.
117
+ - Spec: `docs/specs/framework-stall-coverage-matrix.md` (5 review rounds to convergence incl. cross-model review; operator-approved 2026-07-18).
@@ -0,0 +1,126 @@
1
+ # Side-Effects Review — MCP Auto-Refresh Hook macOS Timeout Portability
2
+
3
+ **Version / slug:** `mcp-autorefresh-timeout-macos`
4
+ **Date:** `2026-07-18`
5
+ **Author:** Echo (autonomous, Tier-1 fix cycle, tracked ref CMT-896)
6
+ **Second-pass reviewer:** self-reviewed-final-diff (session-lifecycle adjacent — the hook can trigger a /sessions/refresh — second pass required; performed as a genuinely fresh re-read of this artifact against the final diff; see "Second-pass review" below)
7
+
8
+ ## Summary of the change
9
+
10
+ `tests/unit/PostUpdateMigrator-mcpAutorefresh.test.ts` ("DEV agent auto-enables…") failed
11
+ deterministically on macOS while Linux CI was green. Root cause: the generated
12
+ `mcp-health-autorefresh.sh` (authored in
13
+ `src/core/PostUpdateMigrator.ts` → `getMcpHealthAutorefreshHook()`) ran its health probe
14
+ as `LIST=$(timeout 45 "$CLAUDE_BIN" mcp list 2>/dev/null || true)`. Bare `timeout` is GNU
15
+ coreutils — absent on coreutils-less Macs (this machine has neither `timeout` nor
16
+ `gtimeout` nor Homebrew). The command-not-found error was swallowed by `2>/dev/null || true`,
17
+ LIST stayed empty, and the script's `[ -n "$LIST" ] || exit 0` guard exited silently —
18
+ so the auto-restart-on-MCP-inaccessible feature was **silently inert in production on
19
+ macOS**, the platform instar agents actually run on. Same platform-gap family as the
20
+ autonomous stop hook's real-check runner fix (previous cycle).
21
+
22
+ Files modified (single source file):
23
+ - `src/core/PostUpdateMigrator.ts` — the generated hook's probe line becomes a portable
24
+ bounded-runner LADDER mirroring the stop hook: `timeout` → `gtimeout` → perl-alarm
25
+ fallback. The perl rung forks, `setpgrp` + group-KILL on a 45s alarm (exit 124), and
26
+ maps child status with `exit(($?&127) ? 128+($?&127) : ($?>>8))` — GNU-timeout
27
+ semantics, so a signal-killed probe is never mistaken for a clean exit. If NO bounded
28
+ runner exists, the script stays dark (exit 0) — it never runs the probe unbounded.
29
+
30
+ ## The eight questions
31
+
32
+ 1. **Over-block** — None identified. On Linux (and Macs with coreutils) the first rung is
33
+ byte-identical to the old behavior. On coreutils-less Macs the change strictly
34
+ *enables* previously-dead functionality; it rejects nothing new. The no-runner-at-all
35
+ case (no timeout, no gtimeout, no perl) exits 0 exactly as the script effectively did
36
+ before — but now by explicit design with a comment, not by accident.
37
+ 2. **Under-block** — The fix closes the silent-inert hole. Remaining, pre-existing and
38
+ unchanged: a `claude mcp list` that exits 0 but prints garbage is still trusted as a
39
+ listing (grep simply won't match); the 45s bound is unchanged. The perl rung's
40
+ KILL-on-alarm is stricter than GNU `timeout`'s default TERM-then-KILL — acceptable
41
+ here because the probe is read-only (`mcp list` mutates nothing worth a graceful
42
+ shutdown). No issue identified.
43
+ 3. **Level-of-abstraction fit** — Correct layer: the bounded-runner contract lives inside
44
+ the generated script at the single probe callsite, exactly parallel to the stop hook's
45
+ ladder (the established in-repo pattern for "bound a command portably"). A shared
46
+ sourced library for the two ladders would be a bigger refactor of generated-script
47
+ plumbing than this fix warrants and would couple two independently-shipped hooks;
48
+ deliberately not done.
49
+ 4. **Signal-vs-authority compliance** — Not a message-flow decision point; deterministic
50
+ exit-status plumbing in an existing dark-by-default hook. The hook's authority shape
51
+ is unchanged: dark default, explicit-false wins, allowlist scope, hard loop-guard
52
+ (at most ONE refresh per (session, failed-set)). No brittle blocking heuristic added
53
+ (`docs/signal-vs-authority.md` reviewed — this adds no gate). No issue identified.
54
+ 5. **Interactions** — The ladder only changes HOW the probe is bounded, not what
55
+ downstream sees: LIST parsing, allowlist matching, marker loop-guard, and the
56
+ /sessions/refresh call are untouched. On machines with `timeout` (all of CI, most
57
+ Linux) the executed bytes are the same as before, so zero interaction delta there.
58
+ The perl rung's process-group KILL cannot touch the parent script (child is
59
+ `setpgrp`'d into its own group). No double-fire: rungs are exclusive `elif`s.
60
+ 6. **External surfaces** — None new. No network, config, API, or route changes. The
61
+ generated file's content changes (comment + ladder), which the existing unit suite
62
+ re-pins (`bash -n` syntax validity, safety-invariant greps, migration parity). Agents'
63
+ observable behavior change: on coreutils-less Macs an allowlisted failed MCP can now
64
+ actually trigger the (config-gated, loop-guarded) single session refresh — i.e. the
65
+ feature works as its spec and config already documented.
66
+ 7. **Multi-machine posture (Cross-Machine Coherence)** — Machine-local BY DESIGN: the
67
+ hook runs at session start on the machine hosting the session, probing THAT machine's
68
+ MCP registrations; its marker state (`.instar/state/mcp-autorefresh-marker.json`) is
69
+ per-machine and must not replicate (another machine's MCP health is meaningless here).
70
+ The fix makes behavior machine-uniform across the pool (a Mac and a Linux box now run
71
+ the same probe contract). No user-facing notice, no durable cross-machine state, no
72
+ URLs.
73
+ 8. **Rollback cost** — Low. Single-file revert of one commit restores the prior generated
74
+ script; the next migration pass always-overwrites the deployed copy back (same
75
+ delivery path as the fix). No data migration, no state repair — the marker file format
76
+ is unchanged. Reverting re-opens the silent-inert-on-macOS hole, so the back-out plan
77
+ is revert-and-re-fix.
78
+
79
+ ## Migration Parity (explicit)
80
+
81
+ `migrateHooks()` writes this hook with an unconditional `fs.writeFileSync` into
82
+ `.instar/hooks/instar/mcp-health-autorefresh.sh` on every migration run (always-overwrite,
83
+ never install-if-missing) — verified in code (src/core/PostUpdateMigrator.ts, the
84
+ migrateHooks try-block) and pinned by the passing test "migration parity: migrateHooks
85
+ always-overwrites the hook into hooks/instar/". Deployed Macs therefore receive the fixed
86
+ script automatically on their next instar update; no config or manual step.
87
+
88
+ ## Class-Closure Declaration
89
+
90
+ - **defectClass:** `unbounded-self-action` — **closure: n/a** (negative declaration).
91
+ No new self-action is introduced: the diff's added `kill()` is the perl timeout rung
92
+ reaping its OWN bounded child probe (process hygiene inside a one-shot session-start
93
+ probe), not a controller emit. The hook's pre-existing self-triggered action (the
94
+ single `/sessions/refresh`) is unchanged and remains hard-bounded by the
95
+ once-per-(session, failed-set) marker loop-guard — it structurally cannot loop.
96
+ Mirrors the machine-readable declaration in the instar-dev trace.
97
+
98
+ ## Second-pass review (self-reviewed-final-diff)
99
+
100
+ Fresh re-read of the final `git diff` against this artifact:
101
+
102
+ - **Template-literal escaping audited character-by-character**: every shell `$` in the
103
+ added block is escaped `\$` (TS template literal), including all perl variables
104
+ (`\$t`, `\$p`, `\$SIG`, `\$?`); no bare `${` remains that TS would interpolate; the
105
+ perl program contains no single quotes so the bash single-quoted `-e '…'` wrapping is
106
+ sound. The generated output was syntax-checked via the suite's `bash -n` test (passes).
107
+ - **Live contract proof re-run** (this machine, no timeout/gtimeout): perl rung returns
108
+ 124 on a hung `sleep 30` with a 2s bound; passes through exit 3; maps SIGTERM death to
109
+ 143; captures stdout correctly. The dev-gate unit test exercises the full script path
110
+ through the perl rung end-to-end (mock `claude` → probe → dead-port session resolution).
111
+ - **First-pass omission caught and fixed in this artifact**: the initial draft of Q2 did
112
+ not name the KILL-vs-TERM strictness difference between the perl rung and GNU timeout's
113
+ default; added with the read-only-probe justification.
114
+ - **Honest scope note**: this branch (off origin/main) does not contain the previous
115
+ cycle's stop-hook fix — `tests/unit/autonomous-stop-hook-realcheck.test.ts` still shows
116
+ that one known failure HERE (1 failed | 23 passed), which is the other PR's subject and
117
+ resolves when both merge. All 14 targeted sibling/migrator files (139 tests) pass on
118
+ this branch, including the previously-failing mcpAutorefresh suite (9/9).
119
+ - **Checked for other bare-`timeout` callsites in generated scripts** (grep for
120
+ `timeout N` across `src/core/PostUpdateMigrator.ts`, `src/data/http-hook-templates.ts`,
121
+ `src/scaffold/`, `src/templates/`, `templates/`, excluding curl's `--connect-timeout`/
122
+ `--max-time` flags): the line fixed here was the ONLY bare coreutils-`timeout`
123
+ invocation; no other generated script carries this defect class. Sweep converged clean
124
+ in one re-pass. <!-- tracked: CMT-896 -->
125
+ - Conclusion: artifact accurate against the final diff; no unlisted side effects found.
126
+ Reviewer concurs with shipping.
@@ -0,0 +1,130 @@
1
+ # Side-Effects Review — Real-Check Runner macOS Signal-Death Portability Fix
2
+
3
+ **Version / slug:** `realcheck-utf8-macos-portability`
4
+ **Date:** `2026-07-18`
5
+ **Author:** Echo (autonomous, Tier-1 fix cycle)
6
+ **Second-pass reviewer:** self-reviewed-final-diff (session-lifecycle-adjacent → second pass required; performed as a genuinely fresh re-read of this artifact against the final diff — see "Second-pass review" below)
7
+
8
+ ## Summary of the change
9
+
10
+ `tests/unit/autonomous-stop-hook-realcheck.test.ts` ("invalid-UTF-8 capture … → next
11
+ payload still builds") failed deterministically on macOS (Node 24) while Linux CI was
12
+ green. Instrumented reproduction showed the hook did not emit broken JSON — it emitted the
13
+ **allow-exit** message: the failing verification command was scored as a PASS. Root cause:
14
+ the perl timeout-ladder rung (used when GNU `timeout`/`gtimeout` are absent — i.e. on
15
+ macOS, where instar agents actually run) ended with `exit($?>>8)`. When the child command
16
+ is killed by a **signal** — routinely SIGPIPE, because the source byte-cap
17
+ (`head -c $RC_CAPTURE_BYTES`) closes the pipe while the command still writes — `$?`'s low
18
+ byte holds the signal and the high byte is 0, so `$?>>8` == 0 == PASS. GNU `timeout` maps
19
+ the same death to 128+signal (141), which is why Linux CI never saw it. This was a
20
+ cardinal-invariant violation (a verification failure mode allowed a premature exit), not
21
+ just a test-portability nit.
22
+
23
+ Files modified (single file):
24
+ - `.claude/skills/autonomous/hooks/autonomous-stop-hook.sh`
25
+ 1. Perl runner exit mapping: `exit($?>>8)` → `exit(($?&127) ? 128+($?&127) : ($?>>8))`
26
+ — signal-death now reports 128+signal, byte-identical to GNU `timeout` and shell
27
+ semantics. Timeout (124), spawn-fail (127), and normal exits are untouched.
28
+ 2. UTF-8 scrub fallback (same §5.3 chain, step 1b): macOS iconv `-c` emits the
29
+ correctly-scrubbed prefix but exits non-zero on a truncated trailing multibyte char;
30
+ the old `iconv … || tr -cd …` therefore ran BOTH commands and concatenated their
31
+ outputs (text duplication hazard for mixed ASCII/multibyte captures). The fallback
32
+ now keys on "iconv produced no output from non-empty input", never on exit code.
33
+ 3. Comments documenting both portability behaviors. The PINNED ORDER
34
+ (sanitize → UTF-8 scrub → leak-scrub → clamp) is semantically unchanged.
35
+
36
+ No test was weakened; the shipped hook was fixed.
37
+
38
+ ## The eight questions
39
+
40
+ 1. **Over-block** — By design this change *adds* blocking: a check command killed by a
41
+ signal (SIGPIPE from the byte cap, OOM-kill, external kill) now scores FAIL →
42
+ keep-working instead of PASS → exit. That is the cardinal invariant's required
43
+ direction ("any failure mode routes to keep-working"), not an over-block. A
44
+ genuinely-passing check is unaffected: a command that completes successfully exits 0
45
+ on its own before the wrapper reads status, and `($?&127)==0` preserves the old
46
+ mapping exactly. Edge considered: a check whose *last* action prints past the 65,536-
47
+ byte capture cap and would then have exited 0 — its SIGPIPE death now blocks the exit.
48
+ That command never got to its exit-0, so its success was never observable; treating an
49
+ unobservable success as non-pass is the fail-safe reading the invariant mandates (and
50
+ is identical to today's Linux/GNU-timeout behavior, so it introduces no new stringency
51
+ anywhere CI runs). No issue identified.
52
+ 2. **Under-block** — The fix closes the known signal-death→PASS hole. Remaining misses
53
+ are pre-existing and unchanged: a check that *itself* swallows failures (e.g.
54
+ `cmd || true`) still reports 0; the destructive-pattern pre-block remains a
55
+ pattern-list, not a sandbox. Nothing in this change widens them. The `cut -c` clamp
56
+ can still re-split a multibyte char after the UTF-8 scrub on both platforms (GNU and
57
+ BSD `cut -c` are byte-oriented in the C locale); this is tolerated today because `jq
58
+ --arg` replaces invalid bytes with U+FFFD on both platforms (verified live on macOS in
59
+ this cycle; Linux CI green proves the same), so the JSON payload remains valid. Left
60
+ as-is deliberately to keep this fix minimal; the scrub step guarantees jq receives at
61
+ most one truncated tail, never arbitrary garbage.
62
+ 3. **Level-of-abstraction fit** — Correct layer. The exit-status contract belongs to the
63
+ timeout-ladder rung itself: each rung must present the same observable contract
64
+ (0=pass, 124=timeout, 127=spawn-fail, non-zero=fail, 128+n=signal). Fixing the perl
65
+ rung to match GNU timeout keeps the ladder's consumers (the outcome switch at
66
+ §"Outcome") rung-agnostic. The iconv fallback fix likewise stays inside step 1b of the
67
+ pinned chain. No higher-layer gate should own POSIX status-word decoding.
68
+ 4. **Signal-vs-authority compliance** — This is not a message-flow decision point; it is
69
+ deterministic exit-status plumbing inside an existing gate. The authority structure is
70
+ unchanged: the real-check outcome still only *holds* completion (keep-working block);
71
+ the only path to exit remains judge-MET + check-PASS. Per `docs/signal-vs-authority.md`
72
+ there is no brittle blocking heuristic added — POSIX status decoding is exact, not
73
+ heuristic. No issue identified.
74
+ 5. **Interactions** — The 124 (ALRM handler exits directly) and 127 (exec-fail) paths are
75
+ untouched and cannot collide with the new mapping (the handler exits before `waitpid`
76
+ status is consulted; exec-fail is a normal exit). The P19 breaker consumes
77
+ outcome=fail rows identically regardless of exit code value. The audit row
78
+ (`logs/autonomous-realcheck.jsonl`) now records e.g. exitCode 141 where macOS
79
+ previously recorded 0 — consumers treat exitCode as opaque display data. No
80
+ double-fire, no shadowing, no race with adjacent cleanup. No issue identified.
81
+ 6. **External surfaces** — None new. No network, no config keys, no API change, no
82
+ template/migration surface: the hook ships inside the `autonomous` skill and
83
+ `installBuiltinSkills()`/`PostUpdateMigrator` handling for it is unchanged (the file
84
+ is delivered by the existing skill-content migration path; this edit rides the next
85
+ release exactly like any prior hook edit — verified that
86
+ `PostUpdateMigrator-autonomousStopHook.test.ts` passes). Timing dependence is
87
+ *reduced*: the outcome no longer depends on whether the platform's runner happens to
88
+ be GNU timeout or perl.
89
+ 7. **Multi-machine posture (Cross-Machine Coherence)** — Machine-local BY DESIGN. The
90
+ stop hook runs inside the one session process on the machine hosting the autonomous
91
+ run; its verdict never replicates and needs no merged read. The fix makes the
92
+ *behavior contract* machine-uniform (a run that fails its check on a Mac now blocks
93
+ exactly as it would on Linux), which improves cross-machine coherence of the
94
+ autonomous-run guarantee without any replication path. No user-facing notice is
95
+ emitted by this change (the block guidance text is unchanged), so one-voice gating is
96
+ unaffected; no durable state or URLs are created.
97
+ 8. **Rollback cost** — Low. Single-file, two-expression revert (`git revert` of one
98
+ commit); no data migration, no agent state repair, no config. Reverting restores the
99
+ macOS signal-death→PASS hole, so the rollback itself would be a safety regression —
100
+ the back-out plan is revert-and-re-fix, not revert-and-stay.
101
+
102
+ ## Second-pass review (self-reviewed-final-diff)
103
+
104
+ Fresh re-read of the final `git diff` against this artifact, hunting for anything the
105
+ first pass papered over:
106
+
107
+ - **Verified the arithmetic**: `($?&127)` extracts the termination signal; for SIGPIPE
108
+ (13) the new expression exits 141, matching `bash`'s `$?` and GNU timeout. For a normal
109
+ exit N, `$?&127`==0 and the expression reduces to the old `$?>>8` — byte-identical
110
+ legacy behavior. Perl's `exit()` takes the value mod 256; 128+127=255 is in range, no
111
+ wrap hazard.
112
+ - **Checked the ALRM race honestly**: if the alarm fires during `waitpid`, the handler
113
+ `exit 124`s immediately — the new mapping is never reached; timeout classification is
114
+ preserved. If the child dies from the handler's KILL in a lost race, 128+9=137 → FAIL →
115
+ keep-working — safe direction.
116
+ - **Flagged and resolved one first-pass omission**: the first draft of Q1 did not
117
+ consider the "command succeeds but is killed printing its final output" case; added it
118
+ explicitly — the conclusion (fail-safe, matches existing Linux behavior) holds.
119
+ - **iconv fallback re-check**: the new `[[ -z "$rc_utf8" && -n "$rc_san" ]]` guard means
120
+ an all-invalid-bytes capture (iconv emits nothing) still gets the C-locale printable
121
+ filter (yielding empty — acceptable, valid), and a non-empty scrub is never
122
+ double-appended. `|| true` keeps `set -e`-adjacent safety (the hook runs without
123
+ `set -e`, but the guard costs nothing). Confirmed no OTHER `iconv … ||` callsites exist
124
+ in the hook (grep: this is the only one).
125
+ - **Scope check**: diff touches exactly one shipped file plus the three ceremony
126
+ artifacts; no test files modified — the failing test was fixed by fixing the hook, as
127
+ required. The pinned-order comment block remains accurate (order unchanged; step 1b
128
+ made exit-code-portable, step semantics identical).
129
+ - Conclusion: artifact is accurate against the final diff; no unlisted side effects
130
+ found. Reviewer concurs with shipping.
@@ -1,30 +0,0 @@
1
- # Upgrade Guide — vNEXT
2
-
3
- <!-- assembled-by: assemble-next-md -->
4
- <!-- bump: patch -->
5
-
6
- ## What Changed
7
-
8
- Instar now carries a canonical registry of every known way a framework session can stop (`src/data/stall-classes.ts`: eight classes, from mid-turn interrupts to context-window walls), and every supported framework must answer for each class in a stall-coverage matrix at `docs/frameworks/<framework>-stall-coverage.md`. A new validator (`src/core/stallCoverageValidator.ts`) enforces the standard structurally: exact status tokens (`covered | covered-dark | declared-gap | not-applicable`), resolvable detector/recovery symbols, positive-control evidence containing the framework's raw stall signature in a test the push suite actually collects, tracked refs on every declared gap, and a calendar aging ratchet on auto-seeded debt. A CI ratchet test in the whole-tree push suite validates all four seed matrices on every push, and an offline-first codemod (`scripts/stall-class-codemod.mjs`) seeds `declared-gap (new-class, unreviewed)` rows into every matrix whenever a class is added — so matrices cannot rot between onboardings.
9
-
10
- The four seed matrices ship honest: claude-code writes its existing stall family down for the first time (six classes covered or covered-dark, one declared gap — the drive-5 defect #9 interrupted-resume class, one structural N/A); codex-cli is honest zeros (every class a declared gap, each filed to the framework-issues ledger with a commitment anchor); gemini-cli is all not-applicable (framework dead upstream, `revalidateOn: framework-revival`); pi-cli is honest declared gaps for a ships-dark framework.
11
-
12
- This is PR-A of a two-PR staged landing. The runtime apprenticeship gate, acceptance machinery, and the recurring `stall-matrix-live-check` job are PR-B — nothing in PR-A gates any runtime behavior.
13
-
14
- ## What to Tell Your User
15
-
16
- No user-visible behavior changes in this release. This is infrastructure honesty: your agent's platform now keeps a complete, continuously-validated map of how sessions can get stuck for every supported framework, so stall detection and recovery stop being learned one silent production stall at a time.
17
-
18
- ## Summary of New Capabilities
19
-
20
- - Canonical stall-class registry + per-framework stall-coverage matrices, CI-validated on every push.
21
- - Class-registry codemod that keeps every matrix complete as the class list grows.
22
- - Honest coverage baselines for claude-code, codex-cli, gemini-cli, and pi-cli (the claude-code stall family is now written down; every other framework's debt is tracked, not invisible).
23
-
24
- ## Evidence
25
-
26
- - The CI ratchet (`tests/unit/stall-coverage-ratchet.test.ts`) validates all four seed matrices, REQUIRED_MATRIX_FRAMEWORKS file presence, and spec-table/registry agreement — green on this tree.
27
- - Validator boundary tests (`tests/unit/stall-coverage-validator.test.ts`) cover both sides of every hermetic decision boundary from the spec's §5 list.
28
- - Evidence tests (`tests/unit/stall-evidence-claude-code.test.ts`) prove each claude-code covered-row detector genuinely fires on a realistic raw stall signature.
29
- - Codemod fleet-regression tests (`tests/unit/stall-class-codemod.test.ts`) prove: class-addition-without-codemod reds stale matrices; the codemod seeds correctly, idempotently, and honors --dry-run.
30
- - Spec: `docs/specs/framework-stall-coverage-matrix.md` (5 review rounds to convergence incl. cross-model review; operator-approved 2026-07-18).