instar 1.3.873 → 1.3.874
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.
|
@@ -1098,12 +1098,19 @@ run_verification() {
|
|
|
1098
1098
|
# Run in a subshell so the resolved CWD + scrubbed env never disturb the hook itself.
|
|
1099
1099
|
# Combined stdout+stderr is byte-capped AT THE SOURCE (head -c) so a runaway log can
|
|
1100
1100
|
# never buffer whole. Exit code via ${PIPESTATUS[0]} (the command's, not head's).
|
|
1101
|
+
# SIGNAL-DEATH MAPPING (cardinal invariant): when the child dies from a SIGNAL —
|
|
1102
|
+
# e.g. SIGPIPE, the routine outcome once head -c hits the byte cap and closes the
|
|
1103
|
+
# pipe while the command is still writing — the perl runner must report 128+signal
|
|
1104
|
+
# (mirroring GNU timeout and shell $? semantics), NEVER `$?>>8` alone: the low byte
|
|
1105
|
+
# holds the signal and the HIGH byte is 0, so a bare `$?>>8` would map a
|
|
1106
|
+
# killed-by-signal FAILING check to exit 0 == PASS and allow a premature exit.
|
|
1107
|
+
# (Observed on macOS, where no GNU timeout exists and the perl rung is the default.)
|
|
1101
1108
|
if [[ "$rc_runner" == "perl" ]]; then
|
|
1102
1109
|
rc_raw=$(
|
|
1103
1110
|
env "${rc_env_args[@]}" PATH="$rc_path" \
|
|
1104
1111
|
bash -c '
|
|
1105
1112
|
cd "$1" 2>/dev/null || true
|
|
1106
|
-
"$5" -e '\''my($t,@c)=@ARGV; my $p=fork; if($p==0){setpgrp(0,0); exec @c or exit 127} $SIG{ALRM}=sub{kill("-KILL",$p); exit 124}; alarm($t); waitpid($p,0); exit($?>>8)'\'' "$2" bash -c "$3" 2>&1 | head -c "$4"
|
|
1113
|
+
"$5" -e '\''my($t,@c)=@ARGV; my $p=fork; if($p==0){setpgrp(0,0); exec @c or exit 127} $SIG{ALRM}=sub{kill("-KILL",$p); exit 124}; alarm($t); waitpid($p,0); exit(($?&127) ? 128+($?&127) : ($?>>8))'\'' "$2" bash -c "$3" 2>&1 | head -c "$4"
|
|
1107
1114
|
exit "${PIPESTATUS[0]}"
|
|
1108
1115
|
' _ "$cwd" "$RC_TIMEOUT_S" "$cmd" "$RC_CAPTURE_BYTES" "$rc_runner_bin"
|
|
1109
1116
|
)
|
|
@@ -1131,8 +1138,15 @@ run_verification() {
|
|
|
1131
1138
|
# 1b. UTF-8 scrub: a source head -c byte-cap can split a multibyte char, leaving a lone
|
|
1132
1139
|
# continuation byte that would later break jq --arg. iconv -c drops invalid bytes;
|
|
1133
1140
|
# fall back to an LC_ALL=C printable-only filter when iconv is absent.
|
|
1141
|
+
# PORTABILITY: macOS (BSD/citrus) iconv -c EMITS the correctly-scrubbed prefix but
|
|
1142
|
+
# still EXITS NON-ZERO on a truncated trailing multibyte char — so the fallback must
|
|
1143
|
+
# key on "produced no output from non-empty input", never on iconv's exit code
|
|
1144
|
+
# (an exit-code `||` would APPEND the fallback's output to iconv's, duplicating text).
|
|
1134
1145
|
if command -v iconv >/dev/null 2>&1; then
|
|
1135
|
-
rc_utf8=$(printf '%s' "$rc_san" | iconv -c -f utf-8 -t utf-8 2>/dev/null ||
|
|
1146
|
+
rc_utf8=$(printf '%s' "$rc_san" | iconv -c -f utf-8 -t utf-8 2>/dev/null || true)
|
|
1147
|
+
if [[ -z "$rc_utf8" && -n "$rc_san" ]]; then
|
|
1148
|
+
rc_utf8=$(printf '%s' "$rc_san" | LC_ALL=C tr -cd '\11\12\15\40-\176')
|
|
1149
|
+
fi
|
|
1136
1150
|
else
|
|
1137
1151
|
rc_utf8=$(printf '%s' "$rc_san" | LC_ALL=C tr -cd '\11\12\15\40-\176')
|
|
1138
1152
|
fi
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-07-18T20:
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-07-18T20:20:52.277Z",
|
|
5
|
+
"instarVersion": "1.3.874",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -5,6 +5,30 @@
|
|
|
5
5
|
|
|
6
6
|
## What Changed
|
|
7
7
|
|
|
8
|
+
The autonomous stop hook's real-check verification runner is now portable across the
|
|
9
|
+
timeout-ladder rungs on the exit-status contract for signal-death. On macOS (no GNU
|
|
10
|
+
`timeout`/`gtimeout`), the perl fallback rung mapped a check command killed by a signal to
|
|
11
|
+
exit 0 via `exit($?>>8)` — the signal lives in the LOW byte of the status word, so the
|
|
12
|
+
high byte is 0. The routine trigger is SIGPIPE: the capture pipeline byte-caps combined
|
|
13
|
+
output at the source (`head -c`), and a verbose check still writing when the cap closes
|
|
14
|
+
the pipe dies from signal 13. The failing check was then scored PASS and the hook
|
|
15
|
+
**allowed the autonomous session to exit early** — a cardinal-invariant violation
|
|
16
|
+
(any verification failure must route to keep-working). Linux was unaffected because GNU
|
|
17
|
+
`timeout` maps signal-death to 128+signal (141, non-zero → FAIL). The perl rung now uses
|
|
18
|
+
the same mapping: `exit(($?&127) ? 128+($?&127) : ($?>>8))`. Timeout (124), spawn-fail
|
|
19
|
+
(127), and normal exits are unchanged.
|
|
20
|
+
|
|
21
|
+
Same chain, second portability fix: macOS iconv `-c` emits the correctly-scrubbed UTF-8
|
|
22
|
+
prefix but exits non-zero when the byte cap truncated a trailing multibyte character; the
|
|
23
|
+
old exit-code `||` fallback then ALSO ran the C-locale printable filter and appended its
|
|
24
|
+
output (a text-duplication hazard). The fallback now triggers only when iconv produced no
|
|
25
|
+
output from non-empty input. The pinned sanitize → UTF-8 scrub → leak-scrub → clamp order
|
|
26
|
+
is unchanged.
|
|
27
|
+
|
|
28
|
+
This also fixes the deterministic macOS-only failure of
|
|
29
|
+
`tests/unit/autonomous-stop-hook-realcheck.test.ts` ("invalid-UTF-8 capture … → next
|
|
30
|
+
payload still builds") — the shipped hook was fixed; the test was not weakened.
|
|
31
|
+
|
|
8
32
|
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
33
|
|
|
10
34
|
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.
|
|
@@ -13,16 +37,35 @@ This is PR-A of a two-PR staged landing. The runtime apprenticeship gate, accept
|
|
|
13
37
|
|
|
14
38
|
## What to Tell Your User
|
|
15
39
|
|
|
40
|
+
Nothing visible changes in day-to-day use. On Macs, autonomous work sessions are now
|
|
41
|
+
stricter about proving they're really done: a verification check that gets cut off
|
|
42
|
+
mid-output can no longer be mistaken for a passing check, so a session can't slip out
|
|
43
|
+
early on a technicality. Sessions that genuinely finish and pass their checks behave
|
|
44
|
+
exactly as before.
|
|
45
|
+
|
|
16
46
|
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
47
|
|
|
18
48
|
## Summary of New Capabilities
|
|
19
49
|
|
|
50
|
+
- No new capabilities — a safety/portability fix. The autonomous completion guarantee
|
|
51
|
+
("a failing real check always means keep working") now holds identically on macOS and
|
|
52
|
+
Linux.
|
|
53
|
+
|
|
20
54
|
- Canonical stall-class registry + per-framework stall-coverage matrices, CI-validated on every push.
|
|
21
55
|
- Class-registry codemod that keeps every matrix complete as the class list grows.
|
|
22
56
|
- 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
57
|
|
|
24
58
|
## Evidence
|
|
25
59
|
|
|
60
|
+
- Instrumented reproduction on macOS 26 (Node 24): the shipped hook returned the
|
|
61
|
+
allow-exit message for `printf "\xe4\xb8\xad%.0s" $(seq 1 100000); exit 1`
|
|
62
|
+
(`PIPESTATUS[0]=0` from the perl rung) before the fix; after the fix it returns a valid
|
|
63
|
+
JSON `block` decision carrying the DATA-labeled, scrubbed, clamped output.
|
|
64
|
+
- `tests/unit/autonomous-stop-hook-realcheck.test.ts`: 24/24 pass on macOS (previously
|
|
65
|
+
1 deterministic failure); all sibling stop-hook suites (9 files, 95 tests) and the
|
|
66
|
+
`PostUpdateMigrator` autonomous-hook suites (24 tests) pass.
|
|
67
|
+
- Full push suite (`vitest.push.config.ts`) run from the worktree: zero failures.
|
|
68
|
+
|
|
26
69
|
- 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
70
|
- 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
71
|
- 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.
|
|
@@ -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.
|