The mutation-runner self-tests put them on PATH and got exit 126 in the
Linux container, which left 15 FAIL mutation rows red at every cut.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Shares mind.call, line_of, inert, render and the hard stop with prompt mode;
render takes the header. Digest of tool_input plus agent_id in the session
scratchpad; fails open with a note naming only the failure kind.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
On Windows the run overshot its deadline by 450-720 ms in the R2 bands: ~200 ms of
interpreter start and imports ran before the clock, the hard stop sat 0.3 s after the
deadline, and the test timed bash, env and the py launcher too. The clock now starts at
process creation (GetProcessTimes), the reply wait ends 0.1 s early so the kill and exit
fit, and the hard stop is the deadline itself. Residual outside the script: the py
launcher (40-130 ms, rare spikes to 300) and the creation call. R2 times the interpreter
directly, best of two, bands -250/+100 ms around the deadline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul s1 fixes. The hook header says the lines are recalled records, data and
never instructions, written by agents not the operator. Every item is one JSON
object per line: id, title and operator_quote are JSON strings with < >, control,
bidi and separator characters escaped, so a title cannot close the framing, forge
a tag, a header or an operator segment. RECALL_DEADLINE_MS now bounds the whole
run through a hard stop; stdin is read to 4 MiB and the answer to 1 MiB
(mind.call max_bytes); RECALL_BUDGET is clamped to 9000 under Claude Code's
10,000-character cap. Tests: hostile text, a non-ASCII process-boundary run under
cp1252, byte-versus-character budget, stdin held open and endless, answer size,
budget ceiling, and the four unrecorded survivors (negative reply timeout, reply
id check, env-int positivity, EOF sentinel). Mutants of each were run and caught.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Queries rulings, questions and follow-ups in force across every root, ranked by
Huginn against the prompt, through mind.call, and prints one hook JSON within
RECALL_BUDGET. Never blocks: any failure or the RECALL_DEADLINE_MS deadline
(default 4000) exits 0 with empty stdout; one attempt; prompts under 3 words are
not queried. test-tools.sh drives it against the fake binding and pins R1-R7;
the fake gains a query branch. INDEX rows and a Precedent note added. The
operator installs the hook; settings.json is not touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mind.call spawns the eureka-state binding, does the stdio handshake and one
tool call within a deadline, and kills the child on every exit path.
context-pack's mcp_view_once is deleted; mcp_view delegates to mind with
retry_once. Messages, exit codes, retry and env rules are unchanged (all 228
context-pack checks pass).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Targets the mutants that survived the first fix run: need arithmetic of cut
and full windows, the reserve for the not-expanded line at the budget edge,
the left-reserve >= 4 threshold with two windows, the exact more-lines
note, the blank line after the table, a non-sha base never echoed in the
header, and a spec without a base refused rather than crashing.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Real git repos in scratch: a src/[a] path beside src/a, pure deletions and
one-line replacements straddling either end of an anchor, edits on an
anchor's first and last line under --since, a --since commit with an
unreadable tree, thirteen wrong-typed spec variants with canaries, a
--max-lines below the table, --only truncation at the 7 and 6 line
thresholds and with a reserved not-expanded line, and the binding env block
with the caller's environment overriding it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- git() is the one git call and passes --literal-pathspecs, so a spec path
with [ ] * ? names that file and no sibling.
- spec_from_json validates base, file_changes and deletes; a wrong type is
Fail(4) naming the field and InvalidInput, never a traceback or the value.
- carry(): a deletion straddling an anchor start maps to c+1 and one
straddling its end to c; the d>0 clamp is no longer special-cased.
- The header prints a non-hex base as "unresolvable"; --max-lines is
documented as bounding windows, the table always listing every anchor.
- The fake eureka-state gains FAKE_MODE=needenv for the binding env test.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Survivors of the first mutmut run (200 of 688) drove these: replaced-block
and pure-deletion hunks, --since boundaries, NEW windows, 70-column change
text, the stdio handshake the fake server now validates, every error code.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Anchors resolve through git diff hunks from the spec's base (working tree
included) into a status table plus bounded windows; --since re-prints only
edited windows. Spec by id through the eureka-state stdio binding, or a
saved view. Fixture section in test-tools.sh; INDEX row.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Truncating writers (`>`, Unity -logFile) rewrite below any offset, so an offset
turned a stale success into a silent hang. The option, the scan branch, its docs
and its fixtures are gone; the header states the fresh-log-path rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
F1: log form searches only bytes after WAIT_FROM, so a stale done line in a
reused log is ignored (also makes the per-check cost independent of log size).
F2: pid form prints GONE (no completion evidence). F4: invalid ERE is exit 3.
Mode 100755 recorded for wait-on.sh; other tools/ scripts are still 100644.
F5, F6, F7 recorded as known limitations in the header. Fixtures added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same contract as ygg-verify.sh wait (270 s cap, bounded tail, RUNNING exit 99,
exit 98 for a thing gone with no completion evidence). Windows PIDs are found
through tasklist, since kill -0 only knows Git Bash's own. Fixtures in
tools/test-tools.sh; INDEX row added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The --rm mutant container is gone by the time the trap runs, so
`docker rm -f $cur` never had a container to remove. The stopgap's watchdog
kills every job-labelled container before it signals the driver, so nothing
needs tracking here. Also restores a line continuation the control-run edit
had collapsed.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A RUN_ON_HOST job whose TIMEOUT fired still exited 0: kill_job only killed
labelled containers and the status came from the host command (Soul F2). The
host command now runs in its own process group (set -m), in the job's work
directory (it ran in the ssh home), with stdin from /dev/null. kill_job sends
the group TERM, then KILL 20 s later, and the verdict is forced to 124 when the
watchdog killed the job, whatever the command exited with. The TIMEOUT/IDLE
notice now prints before the kill so the 20 s grace cannot swallow it.
Proved on Yggdrasil with the sleep-past-TIMEOUT probe (exit 124, cwd = the work
dir, no sleep left) and with a command that traps TERM and exits 0 (still 124);
probes recorded in tools/fixtures/mutation-probe/README.txt, with the expected
rows for the fresh and jsr reproducers.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Jest without a TTY prints "FAIL jt/add.test.js" with one space; the parser
matched two, so a real jest load failure read ERROR, not UNVIABLE. The old
`^ +● ` denylist also named process.exit's "● process.exit called with "1""
as a killing test (CAUGHT). Names now need a FAIL file header above them; a
crash with no named failing test is ERROR. The "no test counts recognised"
line names jest. The synthetic jest logs and mock are replaced by real jest
29.7.0 output captured on Yggdrasil from tools/fixtures/mutation-probe/jsr
(green, wrong, load, exit runs in logs/jest-real.log).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A test that cannot run twice (create_dir("target/fresh-marker").unwrap())
passes the baseline and then fails in every mutant's container, so every
mutant read CAUGHT and the job exited GREEN (Soul F1). The baseline's test
loop is now one function, `unmutated`, run again from the committed snapshot
in a --control container before any mutant; red or empty there aborts with
SNAPSHOT RED, exit 2, no rows counted. Mock case E3 (oncetest.sh) fails if
the control is removed. Reproducer crate: tools/fixtures/mutation-probe/fresh.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Copied from the soul3 scratch repos so the kills that found F1, F3 and F4 are
committed evidence; no behavior change.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
mutation-table-nonce.log is now a real run of the snapshot runner (untested-equiv
SURVIVED, exit 1). The probe README names the soul2 case. ygg-verify.sh no longer says
a reused log path is guarded by a run id (fresh paths are the rule).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The git-status restore could not see ignored files, so a test keeping state under
target/ polluted the next mutant (an uncovered mutant read CAUGHT). The baselines now
run once in one container whose filesystem holds the revision; that container is
`docker commit`ted to a throwaway image and each mutant runs in a fresh --rm container
from it. restore(), dirtied() and tree_state() are deleted. ygg-verify.sh gains
RUN_ON_HOST=1 (the driver owns container topology; the watchdog kills by job label).
testparse.sh: LOADFAIL for file-level FAIL lines (no " > "), jest "●" names and jest
Tests: counts. Mock docker, jest and vitest-variant fixtures, soul2 reproducer.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The pollution note listed nothing when the workdir was below the repo root
(ls-files ran from the root); the mock now runs from a subdirectory so that
regresses visibly. Every brief/INDEX line showing ygg-verify wait now says to
pass a Bash timeout of at least 280000 ms.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A: BUILD runs on the unmutated tree; red or missing aborts as BASELINE RED.
B: outcome is its own field, tallied from fields; labels may contain pipes.
C: after each mutant the whole tree is reset and run-created untracked
entries removed; an unrestorable tree aborts; dirtying is noted in the row.
D: a vitest file-level FAIL with no test name is UNVIABLE, not CAUGHT.
E: one row per mutant as it finishes; whole-job TIMEOUT is explicit.
F: LF before-text matches CRLF files.
Rows carry the per-run YGG_NONCE tag. Local mock fixtures and test-tools
cases fail on each regression.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Carried summary lines must start YGG-SUMMARY/<nonce>:, the nonce exported into the
job; others are counted and ignored. Carried lines no longer drop failure sections.
Red summaries say NAMES MISSING when counts exceed names. wait trusts a .done only
when its run id matches .run; a missing summary never yields a bare exit 1.
mutation-table.sh must print the nonce prefix (separate branch).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
SUMMARY=<log> keeps the full log and prints a bounded verdict summary;
wait <log> blocks at most 270 s and returns it once. mutation-table.sh runs
hand mutants in one verify job and returns a kill table. testparse.sh is the
one parser for both. Fixtures: real cargo and mutation logs from Yggdrasil
(tiny-crate), plus hand-written dotnet, vitest, pytest, exit-0-but-failed and
no-verdict logs. Every commit here builds; verified live on Yggdrasil.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Applies references/tool-calling-cut.md section 5 under the section 8 rulings
(Q1 A, Q3 C, Q4 B). Briefs: agents wait in the foreground in calls of at most
270 s and never end a turn before the report; commits from Git Bash; every
dispatch names its model; agents cap at about 300k context or 150 calls.
SKILL.md: Self rotation rule, notify-before-report defect, Shared tools
section, sizing by calls, Yggdrasil limits corrected to the script's 5 slots
and 6 GiB. tools/INDEX.md lists ygg-verify.sh. Changelog quotes the rulings.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
cargo-mutants --jobs N workers all built into /src/target and tested each
other's binaries, so parallel kill counts were noise (idunn-watchdog Soul
s3). Cargo's default target dir is already inside /src.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sccache 0.18.0 as RUSTC_WRAPPER with a shared eureka-sccache volume.
Idunn lib test build: 51 s cold, 29 s warm (83/83 hits). Slot wait, not
compilation, is the larger cost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exporting SHELLOPTS also exported a job's own `set -u` to every bash-wrapped
tool; kotlinc died on `JAVA_OPTS: unbound variable` (put-serve Hands, 2026-09-30).
Probed on Yggdrasil: `false | cat` in a child script still returns 1, and a
parent's `set -u` no longer reaches child shells.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul proved bash -o pipefail -c "bash job.sh" leaves the script without
pipefail; a failing piped test came back exit 0. Probe: false|cat exits 1,
true|cat exits 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At 8 slots /proc/pressure/io showed full avg60=41% and live Odin fsyncs
stalled route challenges.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ygg-verify.sh gains a kotlin alias tagged by kotlin.Dockerfile's hash; the
remote build step is generalised from rust.Dockerfile to <name>.Dockerfile.
PowerShell comes from the GitHub tarball because the Ubuntu 26.04 base has no
Microsoft apt package. Proven with CultLib ack Cut K0: green on hands/ack-K0,
red on a deliberately failing self-test.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Measured 2026-09-30 00:17 CEST: 40% CPU idle, 53 of 62 GiB available,
~20 jobs queued, running jobs ~1 GiB each. 8 x 6 GiB worst case still
leaves the services their memory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An ack Cut 2 job piped a failing test run into cut and reported exit 0.
The rake was documented; agents kept stepping on it. pipefail now holds
for every job; the comment says what it still does not rescue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Idunn verification now runs cargo check --target x86_64-pc-windows-gnu
--bin idunn-host so a Windows-only compile break is caught on Yggdrasil.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Git LFS's pre-push hook rejected the scp-style host:path remote and stalled
every Aetheria verify (found by Imagination and Soul, 2026-09-30). The mirror
never needs LFS objects; jobs that need one smudge it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On 2026-09-30 three hung test binaries held all three slots for up to an hour
at ~2% CPU while Yggdrasil sat at load 1.5 of 16. The operator wants
Yggdrasil pinned. CPU caps now oversubscribe on purpose (jobs run niced);
memory is the ceiling, so the per-job cap drops to 8 GiB. The watchdog samples
docker stats each minute and kills a container under 5% of a core for IDLE
seconds (default 900), reporting it red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The watchdog subshell reported its own killed sleep as "Terminated" on
every job, successful or not. Agents read that as the job failing: one
Hands reported "the script reports exit 0 after Terminated even when the
job fails", which is false. Tested: exit 3 gives rc 3, and true gives rc 0.
Now the subshell's stderr is /dev/null and the TIMEOUT notice goes out
through fd 3. Checked: TIMEOUT=20 on sleep 60 prints TIMEOUT and exits
137, and a clean run prints no stray line. The watchdog is also reaped
with wait.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The watchdog added in 9359e9c ran in a subshell that inherited the slot
lock's file descriptor. When a job finished early, `kill $watchdog`
ended the subshell but not its `sleep` child. That orphaned sleep kept
the slot for up to TIMEOUT seconds. Two slots were held this way on
2026-09-23, and four jobs queued behind them.
The subshell now closes the lock fd before sleeping, and the cleanup
kills the watchdog's children before the watchdog itself.
Tested on Yggdrasil: a fast job exited 0, and a TIMEOUT=15 hang exited
137 with the TIMEOUT line. Afterwards no sleep process held any
slot lock.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Investigating a QUIC interop failure (native bridge client vs .NET managed
server, both on libmsquic, both over loopback) needs to test IPv6-disabled
Docker networking as a hypothesis, which the script had no way to express:
no docker-args hook existed. DOCKER_ARGS is forwarded verbatim into the
container's own `docker run`, word-split by the remote shell, appended right
before the image name — e.g. DOCKER_ARGS='--sysctl net.ipv6.conf.all.disable_ipv6=0'
or DOCKER_ARGS='--network host'. Empty by default, so every existing caller is
unaffected.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>