Commit graph Eureka/tools
Author SHA1 Message Date
40bed7ee25 mutation-mock fakebin: docker and sudo are executable (mode 100755)
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>
2026-10-23 20:22:00 +02:00
da193e3e02 test-tools: admit R9 Unavailable names the tool kind; R8 budget and unchecked-member cases; restore two literal CRs in the context-pack fixtures
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 19:41:06 +02:00
e12435e7e2 test-tools: the recall admit section, R1-R12 driven with real hook JSON against the fake binding
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 19:36:28 +02:00
13133bcb40 recall.py admit: a PreToolUse hook that holds an admit with rulings or questions once, listing in-force neighbours
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>
2026-10-23 19:36:28 +02:00
8701da3a86 recall: the deadline clock starts at process creation and the kill fits inside it; R2 times the interpreter, not the shell
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>
2026-10-22 18:44:11 +02:00
0342dceef9 recall: recalled text is inert data; the deadline is the whole run; budget, stdin and answer are bounded
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>
2026-10-20 21:44:02 +02:00
f35410a8b2 tools: recall.py prompt, the UserPromptSubmit hook that pushes nearest in-force rulings into context
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>
2026-10-20 16:34:20 +02:00
5e71d8e4ff tools: one mind client (mind.py); context-pack's private MCP client deleted
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>
2026-10-20 15:55:46 +02:00
903794ca0c tools/INDEX.md: context-pack.py row names the briefs and SKILL.md step 1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:07:12 +02:00
5bf88b80cb context-pack tests: pin the --only reserve and charge, the cut-window budget, header base, missing base
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>
2026-10-09 02:50:19 +02:00
dbcb41a046 context-pack tests: glob paths, straddles, --since boundaries, spec shapes, budgets, binding env
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>
2026-10-09 01:40:21 +02:00
459475f581 context-pack: literal pathspecs, typed spec-shape refusals, straddling deletions map to the first survivor
- 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>
2026-10-09 01:40:21 +02:00
a474da7a5f context-pack tests: pin hunk clamps, boundaries, unsafe paths, protocol and error codes
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>
2026-10-09 00:07:08 +02:00
11f6526283 context-pack tests: pin error messages, retry condition, file-end and CRLF handling
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 22:13:13 +02:00
aa6ce2b097 tools/context-pack.py: carry a cut_spec's file_changes anchors to the worktree
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>
2026-10-08 21:35:42 +02:00
d9e45c8740 wait-on: delete WAIT_FROM; reused log paths are forbidden instead
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>
2026-10-08 15:21:58 +02:00
30e199cf55 wait-on: WAIT_FROM start marker, GONE line for pid form, regex validation, executable bit
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>
2026-10-08 15:04:20 +02:00
a886d4524a Add tools/wait-on.sh: one waiter for pids, logs and files
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>
2026-10-08 14:15:45 +02:00
Meta Vens
838b859423 mutation-table: delete the dead cur branch of the cleanup trap
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>
2026-10-01 18:25:36 +02:00
Meta Vens
4803a76dc2 ygg-verify: a RUN_ON_HOST job the watchdog killed exits 124, in its work directory, and its process group is signalled
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>
2026-10-01 18:25:35 +02:00
Meta Vens
f68bd4099f testparse: jest names come only from "●" lines under a FAIL header; real jest fixtures
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>
2026-10-01 18:14:45 +02:00
Meta Vens
b4942b7571 mutation-table: a control run of the unmutated snapshot gates the fan-out (SNAPSHOT RED)
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>
2026-10-01 18:14:39 +02:00
Meta Vens
e5bdc386df Fixtures: Soul's fresh (rerun-hostile test) and jsr (jest/vitest mutants) reproducers
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>
2026-10-01 18:07:08 +02:00
Meta Vens
2fbf696f79 Fixtures and docs: real nonce log from the soul2 reproducer, INDEX describes the snapshot isolation
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>
2026-10-01 17:46:11 +02:00
Meta Vens
675669150b mutation-table: docker commit has no -q 2026-10-01 17:39:07 +02:00
Meta Vens
62bc4d5c1d mutation-table: Docker owns the per-mutant snapshot; classify jest and vitest file-level load failures
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>
2026-10-01 17:36:49 +02:00
Meta Vens
118f157124 Merge parser and wait fixes into the tool-calling branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:46:39 +02:00
Meta Vens
4bcd434828 mutation-table: name the mutated file from the workdir; briefs: wait calls take a 280000 ms Bash timeout
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>
2026-10-01 16:43:26 +02:00
Meta Vens
b02f577b39 Fixtures: Soul's real-toolchain mutation probes (rust data.txt pollution, vitest import break)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 16:40:38 +02:00
Meta Vens
b6f52d5ec4 mutation-table: BUILD baseline, field-based outcomes, whole-tree restore, file-level load failures, live rows, CRLF, nonce tag
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>
2026-10-01 16:40:25 +02:00
Meta Vens
15c8ff6f8a ygg-verify: per-run nonce authenticates carried lines, run id guards wait on a reused path
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>
2026-10-01 16:35:01 +02:00
Meta Vens
c23428622f testparse: zero-test dotnet/pytest runs, -q pytest counts, dotnet and pytest names with spaces
Soul fixtures (real dotnet, pytest, vitest, cargo logs) committed as evidence.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 16:35:00 +02:00
Meta Vens
60a09dc17c Tools: ygg-verify summary and wait modes, mutation-table runner, shared test parser
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>
2026-10-01 15:09:07 +02:00
Meta Vens
cf8e7e6a51 Tool-calling cut: foreground waits, rotation rule, tools/INDEX.md, stale Yggdrasil limits
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>
2026-10-01 13:34:52 +02:00
Meta Vens
a755a60f94 ygg-verify: stop forcing CARGO_TARGET_DIR; parallel mutants shared it
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>
2026-10-01 01:14:27 +02:00
Meta Vens
c17cf6bf80 ygg-verify: rust image caches compiled crates with sccache
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>
2026-09-30 20:14:43 +02:00
Meta Vens
c62eeac548 ygg-verify: carry pipefail to child shells via BASH_ENV, not SHELLOPTS
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>
2026-09-30 18:39:18 +02:00
Meta Vens
ef487e547a ygg-verify: initialise line before the verdict loop (set -u)
A Soul run hit "line: unbound variable", turning a remote exit 0 into a
local exit 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:56:58 +02:00
Meta Vens
4cf81d39da ygg-verify: export SHELLOPTS=pipefail so job scripts inherit pipefail
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>
2026-09-30 16:56:07 +02:00
Meta Vens
2a079f2e19 ygg-verify: document the pipefail + grep -q trap
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 08:54:25 +02:00
Meta Vens
b4f22571b8 ygg-verify: back to 5 slots; disk IO is the ceiling
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>
2026-09-30 01:51:51 +02:00
Meta Vens
d9a6a08226 Stopgap: kotlin verify image (JDK 21, kotlinc 2.2.21, Node 24, pwsh) and generic eureka-verify-<name> image build
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>
2026-09-30 01:05:39 +02:00
Meta Vens
a49b0943c1 ygg-verify: 8 slots at 6 GiB each
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>
2026-09-30 00:18:51 +02:00
Meta Vens
84b09f3408 ygg-verify: force pipefail on the job command
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>
2026-09-30 00:12:40 +02:00
Meta Vens
32b71f5859 Stopgap rust image: bake the x86_64-pc-windows-gnu target (Idunn W1)
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>
2026-09-29 23:54:30 +02:00
Meta Vens
da97acccfa ygg-verify: push over ssh:// with LFS push skipped
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>
2026-09-29 12:26:29 +02:00
Meta Vens
6c413cc110 ygg-verify: 5 slots at 8 GiB, and kill jobs that idle 15 minutes
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>
2026-09-29 12:00:12 +02:00
Meta Vens
0cc222ab8b Stop ygg-verify printing "Terminated" on every run
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>
2026-09-29 08:48:05 +02:00
Meta Vens
dbe594e395 Stop ygg-verify's watchdog from holding a slot after its job ends
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>
2026-09-23 14:02:05 +02:00
Meta Vens
976c83bffa Add a DOCKER_ARGS passthrough for one-off network/sysctl variants
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>
2026-09-23 13:51:06 +02:00