tier: critical
One entry per ROOT CAUSE. Protocol: research-loop commands/CAPABILITIES.md § ISSUES protocol.
Grep before adding (+1 <project> <date> on a hit); closes in the fix's own commit, replaced by a
- [fixed <commit>] tombstone.
Sanitized text only. Host names, profile names, cluster paths and account names stay in the
reporting project's own ISSUES.md.
Seeded 2026-08-01 from the second migration lane.
-
[open] id: remote-toolkit#first-apply-staging-enoent 2026-10-11 infra-agent: the first apply of a small changed file fails on the remote side with
unable to set staged file permissions: ... chmod <beta staging path>: no such file or directory(a Transition problem), and the file lands only on the next cycle, ~30–60 s later — twice in one session, on a 9 KB and a 6 KB file of an active code-sync profile; meanwhileexec's flush gives up after 10 s ("may see slightly older files"), so a launch issued right after an edit can read the stale copy. Not the size cap of the 8dd1889 tombstone (KB-sized files). Worked around by comparing the remote digest before launching. → next: capturemutagen sync list -lat the moment of the failure; check whether anything prunes the beta staging directory between stage and apply. repro: edit a tracked file locally; within ~5 s runmutagen sync list -l --label-selector=rt-profile=<p>→Transition problems: <file>: unable to set staged file permissions ... no such file or directory; ~30 s later the file is on the remote with the new digest. +1 2026-10-12 infra-agent, twice more on the same profile, captured at the moment as the entry's next step asked:Transition problems: analysis/<file>.py: unable to set staged file permissions: unable to set permission bits: chmod /root/.mutagen/staging/<session>-beta/<2 hex>/<digest>: no such file or directorywithStatus: Applying changes. A MODIFIED file landed one cycle later; a NEW file (4 KB) had not landed after ~45 s and the session stayed inApplying changes—touchon the local file plusmutagen sync flushlanded it within ~20 s. So the self-heal is not guaranteed; the digest check before launch stays the workaround. +1 2026-10-13 infra-agent (same profile): a MODIFIED 3 KB script stuck ≥ 4 min inStaging files on betawith the same chmod/ENOENT Transition problem;exec's flush,mutagen sync flushand apause+resumeall left it stuck (the beta staging dir<session>-beta/existed and was empty, root disk 74 % used). It cleared only after the identical bytes were written to the beta path from another host (a pod on the same ceph), after which the session returned toWatching. So the self-heal is not guaranteed for modified files either; the digest check before use stays the workaround. 2026-10-14 sweep (Mutagen v0.18.1 source, no recurrence to observe): everyTransitionends instager.Finalize(), whichRemoveAlls the endpoint's whole staging root, and that root is named~/.mutagen/staging/<session-id>-beta— so only another process holding the SAME session id can delete a file this agent staged, and a stuck spell that survived pause + resume (a fresh agent) points at a second, actively driven agent for that session, not at a prune. Measured now: 8 synchronizer agents on the beta host, 8 local sessions to it, all started together (a reconnect ~22:58). wake-when: the failure recurs — at that moment count the beta host'smutagen-agent synchronizerprocesses and their ages against the Mac's sessions for that host; more than one agent for the session (or more agents than sessions) confirms the double-agent cause and points the fix at reconnect hygiene. -
[open] id: remote-toolkit#push-drops-exec-bit 2026-10-10 fast-matmul-compiler:
pushinstalls the file with the remote shell's default mode (cat > tmp; mv -f tmp dst), so a local executable bit is silently dropped and a pushed helper that must be found onPATH(or run as./x) fails with "Permission denied" while the push reported "verified". Worked around by a remotechmod 755after the push and a wrapper that refuses a non-executable helper. → fix shape (a contract CHOICE, not taken here): carry the local mode bits (or at least u+x) to the installed file after the digest check, and say so inpush's usage text. repro:chmod +x x.sh; rt -p <p> push x.sh x.sh; rt -p <p> exec --no-flush 'ls -l x.sh'→-rw-r--r--. +1 2026-10-10 14:46 fast-matmul-compiler, a worse shape: RE-pushing an UNCHANGED executable also rewrites it with the default mode, so a caller that re-stages before every launch (jobs/stage_intent.sh) made two live PATH helpers non-executable for ~16 s while a running job depended on them (no process hit the window). Caller-side mitigation shipped there (skip a push whose remote sha256 equals the local one; restore 755 on executables after the loop); the root fix here is unchanged. blocked-on: owner ruling (2026-10-14 sweep) —pushdocuments only an atomic, sha256-verified install, so which mode the installed file gets is a contract choice: (a) cp/scp semantics — a replaced file keeps its own mode, a new file takes the local mode under the remote umask (recommended: the convention callers already assume, and it ends the re-push that stripped +x from live helpers); (b) always carry the local mode (rsync -p; can loosen a deliberately tighter remote mode); (c) only guarantee u+x when the source has it. -
[fixed remote-toolkit@14bf380] id: remote-toolkit#new-profile-cannot-create-its-root 2026-09-30 attn-quant — a never-connected profile could not create its own
REMOTE_DIR(every exec/fetch formcds there first; push never creates directories); SKILL.md now names the one-off bootstrap from another profile on the same host (exec --no-flush 'mkdir -p <root>'). The behavioural alternative (exec from the parent when the root is missing) was not taken; reconsider if a session hits this after the doc exists. -
[fixed remote-toolkit@7142ac003002] id: remote-toolkit#bg-payload-expands-variables-early 2026-09-27 quant-robust:
exec --bgexpanded the payload's$var/$(...)in the login shell that parses the ssh argument, one layer before tmux started the job's shell — a silent wrong command._exec_bgnow builds the wrapper locally, base64s it, decodes it to~/.rt_logs/<session>.shon the host and has tmux's shell source it with., so the payload runs byte for byte (which also ends the nested-quote mangling named in the start-verification tombstone); canary 1g (loop variable logsxa xb) and 1h ($(pwd), nested quotes, trailing comment) pin it and fail on the oldrt; 219/219. +1 2026-09-27 quant-robust, second hit: a checkpoint-download loop fetched the default revision five times, exit 0. -
[fixed remote-toolkit@430292d6890e] id: remote-toolkit#exec-wrapper-swallows-closing-paren 2026-10-01 attn-quant: plain
execandfetchwrapped the command as( cmd )on one line, so a command ending in a heredoc got the terminator lineEOF )and one ending in a# commentlost the): the remote shell died on a syntax error before the trailer, andexecreported a 0-byte TRUNCATED stream. Both wrappers now put the command on its own lines inside the subshell; canary 17s / 17t / 18w fail on the oldrtand pass on the new one (222/222); a live heredoc read returned its output, rc 0.
Extended 2026-08-02 by this tool's FIRST audit — a de-correlated read of rt, SKILL.md,
CLAUDE.md and README.md against three questions (where is a state reported without being
established · where can data be lost · which documented claims are enforced nowhere), with
every finding reproduced against real Mutagen 0.18.1 on local-only endpoints or a stubbed SSH.
The entries below are that audit's, not a project's; each says whether it was CONFIRMED offline
or still needs a live host.
The lane has one shape. rt's callers are usually AGENTS, and almost every path that could
say "I could not establish this" instead returns 0. A human notices an odd line; a bash guard
branching on $? does not.
-
[fixed c4fe9d4] 2026-08-10 ceph-cleanup lane:
exec --bgreported "Background job started" and exit 0 for a job the remote shell had rejected, whilert logs <session>answered "Log not found" — two commands disagreeing about existence, neither saying it failed to start._exec_bgnow probestmux has-sessionOR the log file after launching and dies naming the job when neither is there; canary 1e/1f pin the refusal and its false-positive guard (1e fails with the check reverted). The payload mangling itself (nested quotes through three wrapping layers) is the encoding entry below, untouched: this closes the false statement, which is what made the mangling cost round trips. +1 2026-08-13 (single-line payload, nested quotes alone are enough). +1 2026-09-10 (fast-matmul WAKE 181): a$(sha256sum …)inside a single-lineexec --bgpayload came out asbash: syntax error near unexpected token '('from the tmux wrapper; the reported-failure path now works (it said so), and the fix on the caller's side was the documented one — stage the body as a script and background that. -
[fixed remote-toolkit@eef6d5d] 2026-08-07 audit: the OTHER half of the entry below —
execstill hands a large payload back as a stream, and the cut is downstream of anythingrtcan reach. The tombstone below closes the silence:execnow certifies whether the stream it received was whole, and reports its size. It does NOT close the loss, and the measurement that decides this is in that tombstone: no consumer in this workspace branches onrt exec's exit status — all four real script callers pipe its stdout intotar/grep/awk, and a pipeline reports its LAST stage's status, so the new 125 is discarded before anyone sees it, and three of the four send stderr to/dev/nullas well. The signal exists and the existing callers are deaf to it. ASK A HUMAN — this is a CONTRACT change, which is the line the routing protocol reserves: routing a payload above a threshold to a remote file and returning the PATH (plus a manifest digest) makes truncation structurally impossible instead of merely visible, but it changes whatexecreturns, so every caller above has to be migrated rather than merely recompiled. The question to put: do we add a separate verb (rt pull <remote-path> <local-path>, verified by digest,execuntouched) or changeexecitself above a size threshold? A separate verb costs nothing to the four existing callers and can be adopted one at a time; changingexecis the one that ends the shape for good. The design note at the foot of this file already proposes the verified-manifest form and is the same decision seen from the input side. repro:rt exec 'cat <file larger than the caller's capture limit>' | tar xf -— the tar is short, and neitherrt's new 125 nor its stderr line reaches the caller, because the pipeline swallowed both. +1 [2026-08-07 train-stab, INPUT side of the same contract]:base64 < local-file | rt exec 'base64 -d > remote-file'delivered an EMPTY stdin stream to the remote command in 2 of 3 invocations —base64 -dsucceeded on zero bytes, wrote a 0-byte file, exit 0, follow-up echo printed; one invocation of the identical pipeline had worked minutes earlier, so the delivery is intermittent, not absent. Cost: a 0-byte python script 'deployed' cleanly and a GPU eval run consumed onpython3 empty-file(rc=0, no output — indistinguishable from success until the artifact was checked). Workaround that ends the exposure: never pipe payload through exec stdin — stage via the profile's Mutagen dir, remote-sidecp, verify sha256 both sides. Strengthens the case for the verified-manifestrt pull/rt pushverb pair in the design note. +1 [2026-08-08 train-stab, same INPUT-side condition]: recurred twice in a row on a ~34KB payload (remote tar: gzip child status 1 = corrupted/truncated stream, not empty this time); cost compounded by a STALE-FILE MISDIRECTION — the target script existed from an earlier partial deploy, so the failed push left the OLD version in place and the next run produced a misleading old-format failure signature that consumed a debugging round. Mutagen-dir + remote-cp + md5-both-sides used for every deploy since; the workaround holds. [TRIAGE DO/L — harness session 28 cutover. Reviewed body sha1511676deca9] The entry says ASK A HUMAN — this is a CONTRACT change. RULED HERE INSTEAD, 2026-08-07, with a de-correlated second opinion (gpt-pro, conversation 6a7544ed).rt execstill hands a large payload back as a stream and the cut is downstream of anythingrtcan reach; the sibling tombstone made truncation VISIBLE (exit 125) and measured why that is not enough — all four real callers pipe stdout intotar/grep/awk, a pipeline reports its LAST stage, and three of the four discard stderr too. The signal exists and every caller is deaf. The ruling: add the separate verb. Do NOT makeexecs return type depend on output size. The size-threshold option was the entry's 'ends the shape for good' candidate and it is the worse one — the semantic TYPE of the result would depend on a threshold (stdout below it, a path+manifest above), so every consumer implements two protocols, an implementation limit becomes public semantics, and a payload that grows silently changes the response shape with no caller change. That institutionalises the shape rather than ending it. And the correction that makes this L rather than S: adding the verb WITHOUT migrating the four callers does not fix the defect. One bounded change set — add the verified transfer primitive, migrate all four known large-output callers, test truncation / digest mismatch / interrupted transfer / destination atomicity, and KEEP exit 125 onexecas a diagnostic for unknown consumers. Notert pull <remote> <local>may be too narrow if a caller GENERATES its payload rather than reading an existing file; the decision is a separate bulk-output contract, not that exact verb.pipefailis not a substitute — downstream stages may already have consumed partial bytes. A ruling would be needed only to RETIRE or redefinert exec, which this does not. +1 2026-08-07 train-stab, the INPUT side, and it changes the deliverable: the verb must be a PAIR.base64 < local | rt exec 'base64 -d > remote'delivered an EMPTY stdin stream in 2 of 3 invocations —base64 -dsucceeded on zero bytes, wrote a 0-byte file, exit 0. A 0-byte script 'deployed' cleanly and a GPU eval was consumed runningpython3 <empty file>, rc=0, no output, indistinguishable from success until the artifact was checked. Intermittent, not absent, which is why it survived. So the same contract is broken in BOTH directions and a pull-only verb closes half of it; the design note at the foot of that file already proposes the verified-manifest pair. Carry the interim workaround into the docs regardless of when the verb lands: never pipe payload throughexecstdin — stage via the profile's Mutagen dir, remotecp, verify sha256 both sides [FIXED 2026-08-08, harness session 29 — the verb PAIR, and all known callers migrated.]rt fetch <local> '<remote-command>'andrt push <local> <remote>. The remote stages the payload, digests it THERE, streams it, and reports the digest in the same nonce trailerexecalready uses;rtdigests what arrived and renames into place only on a match. So a cut anywhere — remote, transport, or rt's own stdout — is a digest mismatch, and on every non-zero path the destination is not written and the incoming file is removed. Exit codes reuse this repo's existing vocabulary rather than inventing one: 0 verified · 2 unverifiable (no sha tool — never a mismatch, the distinctionrt verifyalready draws) · 3 mismatch · 124 bound · 125 no outcome established · otherwise the remote command's own status. Nopullverb, deliberately: one of the two real large-output callers GENERATES its payload withtar cf -, so a path-takingpullcould not have served it — which is the narrowness the ruling warned about.cat <file>is just a command. The migration is the half that fixes the defect, and it is done.opt-dyn/evidence/sync_runs.shhad NO verification at all (exec 'tar cf -' | tar xf -) and mirrors the artifacts paper-trail's measures extract from; it now fetches to a temp and extracts from a verified file, and its remote command was changed tols -1 … | tar cf - -T -because an unmatched glob made tar emit a PARTIAL archive that the old pipeline extracted happily.rl-sgd/evidence/sync_runs.shhad hand-rolled the same discipline with an md5 round trip per file; the pipe is gone and both its guards are kept and documented as answering different questions — fetch and md5 both answer "did these bytes arrive intact" (independent digests, so a defect in one cannot silence both), and neither answers "are these the bytes we want", which is what its never-shrink guard is for. The three remainingrt execcall sites in those scripts arels,md5sumand agrep|sedreduction — line-count outputs, not bulk payloads. Migrating them would be motion, not coverage.exec's 125 is kept as a diagnostic for consumers that have not migrated, per the ruling. Tests:canary_rt.shblock 18, 16 checks — happy path, byte-exactness through fetch (NULs, no trailing newline), no-trailer, digest mismatch on a SAME-LENGTH corruption (a length check alone passes that), a non-zero remote command, no-sha-tool as 2-not-3, push's empty-stdin incident verbatim, an existing destination left untouched, no incoming files left behind on any path, and a false-positive guard proving the happy path still writes after every refusal above it. 178/178 (was 161)._sync_preflight_for_execwas EXTRACTED fromcmd_execrather than copied intofetch: it is a decision rule, and this workspace has already paid once for carrying one of those in two places. -
[fixed remote-toolkit@69be333] 2026-08-06 train-stab:
exectruncates a large remote stdout silently and still exits 0 — the lane's own shape, on the output path. A command whose stdout exceeds roughly 100KB comes back cut off, with no marker in the stream and no non-zero status, so a caller that pipes several files through oneexecreceives a partial payload that parses as a complete one. Measured: a multi-file pull returned 1 of 7 files and reported success; the loss was caught only because the caller happened to hold a manifest of what it had asked for. Same failure CONDITION as a cross-repo record from 2026-07-13 (rt execlosing 6 of 7 files): one pattern, two occurrences, three weeks apart. Closed as the entry itself split it — the additive half only. Where the cap is NOT, established by reading the path rather than by measuring a symptom: there is no cap inrt, andsshdoes not truncate._exec_syncinherited its stdout straight through. So the ~100KB cut is in whatever CAPTURES rt's stdout — an agent's tool-output limit, a harness buffer — andrtcan neither see nor prevent it. That is why the fix is two assertions and not a bigger buffer: the remote shell now writes a TRAILER to stderr after the command exits (a per-invocation nonce token plus the command's status), so a stream that ends without it is reportedoutput is TRUNCATEDand exits 125 — "no outcome established", beside the 124 already used for the bound, and a real transport status is never replaced by it. And the payload is COUNTED on the way through, with a complete one at or above 64 KiB (RT_EXEC_REPORT_BYTES) reporting its size, which is the number a caller compares against what it actually holds. Below the threshold nothing is printed: a small output behaves exactly as before. stdout stays a byte-exact pass-through, and that is load-bearing rather than tidy — a live caller pipestar cf -throughexecintotar xf -, so the trailer had to go on stderr (filtered back out by a nonce-matching awk) and the count had to be taken bytee/wcover a fifo rather than by any line-oriented filter. Pinned by a NUL-byte, no-trailing-newline payload. Consumers counted before touching the status set (rg --no-ignore, 2026-08-07): 405 static occurrences in 168 files across 83 directories, but that is the MENTION count and it is the wrong predicate — the decision changes an exit status, so the predicate is who branches on it. Excluding prose, notes and captured transcripts leaves four real script callers, and not one branches onrt exec's status: every one pipes stdout intotar/grep/awk, which discards it. So 125 breaks nobody, and — the half worth writing down — helps nobody either without the follow-up entry above. Two defects in this fix were found by attacking it, not by the suite, and both are the shape the fix is about. (1) Waiting for the accounting readers reinstated an UNBOUNDED wait: they see EOF only when every holder of the payload pipe closes it, which after--timeoutkills ssh can include something ssh left behind — a 1s bound returned after 4s._exec_reapbounds that wait at 2s and then kills, and becausewcwrites its total only at EOF the count is then reported as unestablished, never as 0. (2) The trailer proves the COMMAND wrote everything it had and says nothing about whetherrthanded it all on:exec 'cat 400KB' | head -c 5reported "81920 bytes of stdout, complete" — the same unsupported benign claim, reintroduced one layer out. The pass-through's own status is now a third input, and a consumer that closed early is reported as a PREFIX with a non-zero status even when the command exited 0. Note the boundary that check had to find: at 70 KB the defect does not reproduce, because every byte still reaches rt's stdout and the kernel buffers it — the claim only becomes rt's to get wrong once forwarding itself cannot finish. A third, smaller correction came from a mutation run on this commit's own comment: the wrapper's trailingexit $?was described as what keepsexec's status the command's, and it is not — the status is read out of the trailer, and removing that line left the suite green. It is kept for the narrower reason now written beside it. Tests: 19 checks (block 17), mutation-scored in both directions — reverting_exec_syncfails 4, dropping the subshell that protects the trailer fails 1, leaking the trailer into stderr fails 1, eating the partial stderr line fails 1, ignoring the forwarding status fails 1, always claiming it fails 4, reporting on every output fails 1, reporting an uncounted stream as 0 fails 1. Suite 142/142 before, 161/161 after. The pre-existing--timeoutcheck was widened (stub sleep 4→12s, threshold 4→6s) because the reap grace left it no margin on either side — fix-induced, and it flaked once under load before the change -
[fixed 8dd1889] 2026-08-12 ceph-cleanup lane:
erroringcould not distinguish a condition that will clear from one that never can, andstatus --alllisted a dead profile among the healthy ones in the same visual form — measured at two days without a single completed synchronisation, reading to a session as "currently retrying".SuccessfulCycleswas in the recordrtalready fetches and was being discarded; zero of them is exactly "has never once succeeded", which is the fact the state word cannot carry. A non-synchronising row is now marked, the never-completed fact is named when the count is established and zero, and the summary counts those separately from the live sessions. Not established stays non-zero and prints nothing — a count that could not be read must never render as "never synchronised". Verified on this machine's real sessions: 10 profiles, 2 never once synchronised, previously indistinguishable. Elapsed-time escalation, this entry's other fix shape, was NOT built and is not owed: it needed new persisted state to answer, by proxy, the question the cycle count answers directly and for free. Canary 180→194; 4 mutants. Body → git history. -
[fixed 8dd1889] 2026-08-12 ceph-cleanup lane: a file above
MUTAGEN_MAX_STAGING_FILE_SIZEwas fetched in full, discarded, and reported as achmod/ENOENT on a content-addressed staging path that never mentioned the cap — measured at two files (1.34 GB, 820 MB) against a 256 MB cap re-fetching ~2.1 GB per cycle forever, reading as a corrupted staging area._sync_error_hintnames the limit behind the failure shape at all three surfaces that print the raw error, covering the entry-count breaker in the same pass (statushad never named it either). Offered as a SHAPE match and it says so: the staging path names no file, sorthas measured nothing, and a hint that overstated itself would send the next operator to raise a cap that was not the cause. The entry's "better" shape — a pre-flight scan atconnectthat refuses or warns on oversize entries — was NOT built: refuse-vs-warn on a live connect path is a behaviour CHOICE, and the diagnostic closes the measured harm (going around the tool to diagnose) without it. 3 mutants. Body → git history. -
[fixed 13f9815] 2026-08-12 ceph-cleanup lane: a sync whose remote root had been deleted wholesale still reported
active(measured: ~563 GiB / 192k files deleted out-of-band over 12 hours; the profile readactivethroughout). Ruled 2026-09-05 (Shuyang): the REPORT half only —rt statusprintsSync root: ABSENT locally / on the remoteandstatus --alltags the row⚠ LOCAL/REMOTE sync root ABSENT (<dir>)and counts it in the summary; one stat per endpoint, the remote one only while Mutagen reports the beta endpoint connected (through the profile's own ssh identity), and a host that does not answer is(remote root not established), never absent. Propagation untouched, and the line says so. Canary 19a-19j; mutants: a non-zero ssh read as absent (19d), the connected-endpoint gate dropped (19f). The behaviour half — which way Mutagen resolves an absent root — stays a human decision at the moment it arises; the report exists so that moment is seen. Body (two fix shapes recorded as NOT built, the 2026-09-04 recommendation) → git history. -
[fixed 13f9815] 2026-08-13 grpo-speed s53: stopping a foreground
rt execfrom the local side killed only the local ssh, the remote command ran on as an orphan, and nothing said so (six orphans found by hand). Ruled 2026-09-05 (Shuyang):exec --bgis the one stoppable lane by design; plainexecgains the honest half —_ssh_boundedcarries the unbounded call too, watched for INT/TERM/HUP: the handler sets a flag, the loop (or an early-returningwait) kills the ssh as a tree and reportsStopped from this side (SIGx): … does NOT stop the REMOTE command, exits 128+signal, and the accounting marks what arrived as a PREFIX cut by the stop (decided by the flag, never by a status ≥128 — a remote command killed on the host comes back with the same numbers). Never fires on a run that ends on its own. SKILL/README name the lane. Canary 14j-14m; mutants: traps removed (14j), stop branch dropped from the accounting (14l), kill-after-wait ordering (14k — found live: 13s on a 12s stub, i.e. no stop at all). Body + the 2026-09-05 sweep analysis → git history. -
[fixed remote-toolkit@67a8df8] 2026-08-01 dllm-reasoning: nothing told a caller when a staged file had actually ARRIVED, so callers waited a fixed sleep and then installed a stale copy. Measured: a patched launcher script was staged, waited on for ten seconds, and the still-stale remote copy was copied into place —
bash -npassed because the OLD file is valid shell, so the run started and died instantly on an unknown case, holding 8 GPUs idle for ~25 minutes. [RULED 2026-08-05, human, decisions §5.3] THE ARRIVAL ASSERTION ONLY — the per-run staging wrapper stays a separate project (register §3.3), and this fix does not touch it.rt verify [--timeout N] [--interval N] <path>...hashes the named paths locally, has the remote hash the same paths, and compares whole manifests, polling until they agree. The local side is recomputed AFTER the remote answer, so a file edited during the check is not certified — the contract is "equality was observed and held across the check", not "these bytes are frozen", which nothing here could promise. Three exit codes, the same split the flush codes use: 0 equality observed, 2 blocked or unverifiable, 3 the deadline elapsed and the sides still differ — and 3 deliberately does NOT say "still syncing", because a mismatch is not evidence of progress. Everything that can never be verified is decided BEFORE any waiting and exits 2: an absolute path, a traversal, a directory, a symlink, a path absent locally, a path matching the LIVE session's ignore rules (it can never arrive by syncing, so waiting for it waits forever), a sync session that is not propagating, and a missing sha256 tool on either side — the last being unverifiable, never a mismatch and never an arrival. The ignore matcher implements only the pattern shapesrtitself generates, so a MATCH is authoritative and a NON-match claims nothing; reimplementing Mutagen's full grammar in Bash would produce a checker that is confidently wrong. Verified end-to-end against a real remote: staged edit → rc 3 naming the differing file with both digests → wait → rc 0 → remote confirmed to hold the new bytes -
[fixed remote-toolkit@67a8df8] 2026-08-02 audit: "flushed" did not mean the endpoints were equal, and nothing in
rtestablished that they were. Mutagen's flush forces a synchronization CYCLE; it does not prove byte-for-byte equality. CONFIRMED with the real engine:two-way-safewith an unresolved conflict, and an ignored path, both flush with exit 0 while the endpoints differ. Closed with the entry above, which it explicitly rides on: the DOC half now says plainly in both SKILL.md and README.md that a returned flush means "a cycle completed", not "the remote has your file", and points atrt verifyas the assertion a flush is not -
[fixed remote-toolkit@9f58754] 2026-08-01 reason-select: a courier bucket degraded silently as finished handoffs accumulated, and the tool's only lever made it worse. Measured: 10,292 files / 2.6 GB in the tree put a flush in
Scanning filesfor 20+ minutes — a 9 KB push took ~40 minutes and a 2.7 MB pull had not arrived after 25 — while the same bucket at 232 files / 41 MB flushed in 7 seconds. Everything in it was FINISHED pulls nobody cleared._sync_flushnow reports the alpha endpoint's file count and byte size before flushing, defaulting to a warning above 2000 files or 1 GiB (between the two measured points, leaning early since the whole point is to speak before the stall);rt statusprints the size on every call. The message carries the standing lesson, because the intuitive fix is the wrong one: PRUNE the bucket — a growing ignore list is the tell that it is being used as storage, and ignores do not take effect until the session is recreated anyway. Two things the size check must not do, both pinned by tests: it must not treat an UNSCANNED endpoint's zeros as a measurement (a breaker-halted session reportsScanned: falsewith0|0|0, and_sync_countsrefuses rather than answering), and it must not print on everyrt exec— it is rate-limited to once per hour per profile, because a warning printed hundreds of times is one that gets filtered, and this workspace already has a caller grepping^!! Sync flushout of its output. The counts ride in the existing status probe; probe cost was measured before assuming it mattered (11 ms against 438 ms for a trivialrt exec) +1 2026-09-15 research-project wrap: the same large-tree scan condition recurred at 91,745 files / 6.8 GB; a staged cell was still absent after roughly 30 minutes although sync was active. The verifiedrt pushroute completed the needed small transfers. This instance contains durable experiment evidence rather than disposable completed courier files, so the old PRUNE recommendation cannot be applied until the project owner selects the artifact-storage policy. No sync root, ignore rule or evidence file was changed for this routing update; no costly live scan was repeated. doctrine-grep: carried —SKILL.mdarrival section says flush/active status is not byte equality and directs callers tort verify; its payload section sayspushalready bypasses Mutagen and needs no--no-flushflag. Existing diagnostics/workaround reached the incident; the remaining blocker is the project owner’s git-versus-remote-artifact storage policy, not an unimplemented transfer primitive. This +1 preserves the tombstone’s diagnostic scope and does not claim scan latency was fixed. -
[fixed remote-toolkit@fc02cb5] 2026-08-01 grpo-speed: bounded reads through
execfailed on macOS with "command not found: timeout" — the GNU binary is not present on darwin (it isgtimeout, from coreutils) and callers wrotetimeout N rt … exec …. Fixed by implementing the bound IN THE WRAPPER rather than detectinggtimeout:rt exec --timeout SECSis pure Bash, needs no GNU binary and no PATH assumption, and exits 124 so a caller that already branched ontimeout(1)'s status does not have to learn a new code. Documented at the bounded-read path in both SKILL.md and README.md, including the nastier sibling form recorded by another project —timeout 120 <cmd> | head -3exits 0 becauseheadsucceeded, so a read that NEVER RAN is indistinguishable from a successful read that returned nothing. The bound is on the WAIT, not the work, and says so when it fires: killing the local ssh does not reliably kill the remote process. A live test caught a real bug in the first implementation: backgrounding_ssh(a shell FUNCTION) makes$!the subshell's pid, sokillleft the real ssh running — the bound returned at 3s while the remote command's output still arrived at 30s, because the surviving ssh held the pipe open.sshis now invoked directly.--timeoutwith--bgis refused, since a background job outlives the shell by design
-
[fixed remote-toolkit@b9e35f8] 2026-08-02 a background job's recorded exit status was
tee's, not the command's.{ cmd; } 2>&1 | tee log; echo "EXIT_CODE=$?"records the LAST element of the pipeline, andteeessentially always succeeds — so every job that ever started was recordedEXIT_CODE=0and listed byrt logsas[DONE], including one that died on its first line. A caller reading that list to decide whether a run finished was told yes, always. Fixed with a POSIX marker file (${PIPESTATUS[0]}is bash-only and tmux starts the remote LOGIN shell, so on a dash host it would expand to nothing — the same class of silent failure). The command runs in an explicit subshell so that a literalexit Ncannot terminate the pipeline's own subshell before the marker is written.rtgot its first tests with this:test/canary_rt.shsourcesrt, stubs_ssh, and RUNS the captured remote command locally, so the four failure shapes are pinned by behaviour rather than by wording -
[fixed remote-toolkit@4a4b376] 2026-08-02 a failed flush was converted into a successful CLI call — and the reason the tool held two OPPOSITE policies for it (
execran anyway,slurm submitaborted) is that "flush failed" was one word for two states._sync_flushnow returns 2 (paused / mutagen error — nothing synchronised) or 3 (the time bound elapsed; the daemon is still working).execrefuses on 2 and continues loudly on 3, because refusing on a routine timeout would make--no-flushhabitual and turn the protection off entirely;slurm submitrefuses on both, since a queued job outlives the shell;sync flushexits non-zero. Human ruling 2026-08-02 — the two states had to be separated before any single policy could be right -
[fixed remote-toolkit@1a309cf,36adcb5] 2026-08-02 an unhealthy sync session was classified
active. The classifier had no negative case: anything carryingIdentifier:that did not matchStatus:.*Pausedand did not matchConnected: Nowas called healthy, so every state Mutagen has that those two strings do not name reported as healthy. The one that actually happens is the entry-count circuit breaker, which reportsConnected: Yeson BOTH endpoints withStatus: Waiting … for rescan. Re-confirmed against real Mutagen 0.18.1 2026-08-05._sync_statusnow classifies from a structuredmutagen sync list --templateprobe of.Paused/.Status/.LastError/.Alpha.Connected/.Beta.Connected, and the classification is POSITIVE:activerequires a status verb on a recognised-healthy list, so a verb this tool does not know (a future Mutagen adding a brake) reportsunknownloudly instead of rejoiningactivesilently. Seven states rather than four; the new ones areerroring(a live error — measured NOT sticky, it clears within one rescan interval once the condition clears, so it is a live condition and not a scar; the entry-count breaker lives here),halted(one of Mutagen's three SAFETY BRAKES) andunknown(the daemon could not be asked, or two sessions matched one profile's labels — previously encoded asnone, i.e. "there is no sync session", a claim about the remote that nothing established). The first version of this fix keyed on.LastErroralone and an independent review refuted it: measured 2026-08-05 by driving each condition, all three ofHaltedOnRootEmptied/HaltedOnRootDeletion/HaltedOnRootTypeChangereport an EMPTY.LastErrorwith both endpointsConnected: true, so an error-only classifier called all three healthy — the same defect this entry is about, one state over. The entry's own fix shape said "classify onLast error:and on the status verb"; only half of it had been implemented.haltedanderroringare separate states because the REMEDY differs: anerroringsession is fixed by repairing the ignore list and recreating, while recreating ahaltedone would silently decide which side's contents win, so its message points atmutagen sync resetinstead. Two consequences were larger than the classification itself: (a) measured 2026-08-05, a flush against a halted session does not fail, it HANGS — still running after 15s — so it used to burnRT_FLUSH_TIMEOUTand return 3, "the daemon is still syncing", whichexeccontinues on;_sync_flushnow returns 2 forhalted,erroringandunknown. (b)connectsays plainly that it cannot recover either non-converging state instead of resuming a no-op and printing "Already connected" — resume is for PAUSED sessions.connectalso refuses to create a session while the daemon is unaskable, which could otherwise have made a second session for a profile that already had one. A THIRD fail-open was found by the same review, in the consumer rather than the classifier:cmd_execgated its whole flush block on_has_sync, a BOOLEAN that cannot carry "I could not find out", so an unaskable daemon skipped the flush and ran the command with no warning — the most permissive path taken for the least established state.execnow refuses onunknownand names--no-flushas the deliberate escape. SKILL.md's claim that a breaker halt shows assync=pausedwas false and is corrected — that recovery path never fired -
[fixed remote-toolkit@7b6a027] 2026-08-02
disconnectreported success when termination failed, and deleted the local record either way.mutagen sync terminate … || truediscarded every failure, then the state directory was removed andDisconnectedprinted with rc 0 — so a session that was still running, still propagating deletions, lost its only local record and the caller was told the job was done._sync_terminatenow returns mutagen's own status and echoes its message;clear_stateruns only after termination is ESTABLISHED, and "established" means re-asking the daemon rather than trusting the exit code of the command that was supposed to do it — a terminate that reports success while the session survives is also a failure.unknown(daemon unaskable) does not license deleting the record either: it cannot terminate a session it cannot see. Every refusal says the state was NOT cleared, so the operator knows the record survived -
[fixed remote-toolkit@4a4b376] 2026-08-02
disconnectkilled every background job while its own documentation said it preserved work — two intentions under one name, with no flush before or after, so a running job was interrupted and its last output could never reach local. Default is now the documented behaviour and it SAYS how many it leaves running;--kill-jobsis the deliberate form and flushes first.clear_statealso stopped deleting the Slurm submission history, which is the only local record of what was queued and has nothing to do with whether a sync session exists. Human ruling 2026-08-02: separate the two intentions, default to the safe one -
[fixed remote-toolkit@71c815b] 2026-08-02 agent-supplied values reached the remote shell unquoted.
slurm cancelinterpolated its arguments straight intoscancel $*, sort slurm cancel "123; <command>"constructed a remote compound command;--nameand a log ID likewise entered unquoted positions, and aREMOTE_DIRor script path containing an apostrophe broke the ad-hoc single quoting. Two controls, because one does not cover every site:_shq(POSIX single-quote encoding, verified to round-trip every metacharacter under dash as well as bash — the remote runs a login shell) is applied at all eleven interpolation sites, and a GRAMMAR (_require_jobidaccepting12345/12345_7/12345_[1-9],_require_jobnameaccepting[a-zA-Z0-9][a-zA-Z0-9._-]*) guards the values that also flow into theexec --bgpayload, where correct quoting through three nested layers is more fragile than refusing the characters. AREMOTE_DIRcontaining an apostrophe is now REFUSED atload_configwith a clear message rather than silently building a malformed remote command, since that one value cannot be carried through the--bgpayload at all. Tests RUN the captured command against stubscancel/sbatchbinaries and assert the resulting argv, so they pin what the remote shell would do rather than how the source reads. Three test-design errors were caught by mutation runs and are recorded because each is the same trap: the first REMOTE_DIR check tested$(…), which'…'already blocked, so it passed with the fix reverted;_ssh's capture-to-variable stub read empty inside_slurm_submit's command substitution, which would have read as "nothing was sent"; and the log-id check was passing becauseload_configdied on an emptyRT_CONFbefore ever reaching the validator — sourcingrtnever runs_init_profile -
[fixed remote-toolkit@d2305f8] 2026-08-02
status --allenumerated state directories, not configured profiles, so a configured-but-never-connected profile was invisible — exactly when you would run it to find out what exists, and theremoteSKILL's first instruction is to run this command to enumerate profiles. Now walks<host>/*.confand unions in any state dir that has no config (a profile whose.confwas deleted while connected is still syncing; dropping it would reproduce this defect one directory over), marks that case, and prints the count and the directory it looked in — a bare row list cannot say whether "not listed" means "does not exist" or "not looked for". A directory that is not a valid host-group segment (_archive/) is skipped by the same rule that validates-p, not by a name list. Config identity is read in a SUBSHELL: configs are sourced bash, so reading eleven in one shell would leak each one's vars into the next and a profile that omitsREMOTE_DIRwould display the previous profile's. NUMBER CORRECTION: the 2026-08-02 record said "11 profile config files, 4 shown". Re-derived 2026-08-05 on the same machine: 11 configs, 10 state dirs, 1 invisible — the defect reproduces, the magnitude does not -
[fixed remote-toolkit@72d44fa] 2026-08-02 documented sync scope was materially wider than the implementation. The defaults ignore
outputs/,checkpoints/,wandb/and others; the docs said files sync to and from the remote and never enumerated the exclusions, so a result written to any of those paths never became part of the local replica and nothing said why. The default set is now enumerated in both SKILL.md and README.md, next to the three consequences that cost the time — and a canary iterates the code's ownRT_DEFAULT_IGNORESarray against both documents, so a pattern added tortalone now FAILS the suite rather than silently widening the gap this entry is about (with a positive fixture proving that check can still fire).rt sync statusprints the live session's effective ignores via--template, not the profile's current text, and — the case that actually bites — DIFFS the two and names the patterns that exist in only one, because Mutagen freezes ignores at session-CREATE time, so editingMUTAGEN_IGNOREchanges nothing about a running session while making the config file wrong about what is excluded. With no live session the list is labelled as what the next connect WOULD apply, since calling it "effective" would claim a session state that does not exist._sync_createand the drift check share one_ignore_patternsproducer so the two cannot disagree about what the config means -
[fixed remote-toolkit@30e180c] 2026-08-30 staging a source TREE in a shared
scratchprofile trips the per-session entry breaker, and the docs only described the runaway case. The--max-entry-countparagraph told you to fixMUTAGEN_IGNOREand recreate — but a 1522-file repo working copy is not junk to ignore, so that recipe has no move. Measured:scratch-shsat at 2724 entries (693 dirs + 2031 files) against a 5000 cap; a 27 MB tree took it over,Last error: alpha scan error: exceeded allowed entry count, and the session halted for every consumer of that profile. Deleting the directory did NOT clear the halt;rt disconnect && rt connectdid. SKILL.md §4 now carries the positive form: tar the tree, sync the single file, extract on the remote OUTSIDE the synced directory
The standing claim in the harness register — "Mutagen is being used as a job-control protocol: eventual bidirectional sync decides which code a GPU job launches and which result is authoritative" — was put to the de-correlated reader as a question, and came back TRUE with one qualification: ignored result paths are remote-only rather than bidirectionally authoritative, and under the default mode the LOCAL side wins every conflict, so a local edit can overwrite a remote result regardless of which was written later.
Its recommended smallest incision, recorded here rather than acted on: a per-run staging wrapper
around exec --bg and slurm submit — copy a deterministic input snapshot to a unique remote run
directory, verify its manifest digest, launch THERE, write outputs there, and fetch them with a
verified manifest. Mutagen stays a browsing convenience and stops being the correctness boundary.
That is a design change to a shared tool and belongs to a human.
- [fixed remote-toolkit@bb2d45dd3e41] 2026-09-08 fast-matmul-compiler: verified single-file transfer lacked a sync-independent download path. Owner approved 2026-09-12: add
rt fetch --no-flushusing the shared exec preflight override, keeping default flush behavior, SSH reachability, SHA-256 verification and failure-without-destination-write semantics;pushalready bypasses sync. CLI help, README and SKILL expose both routes; offline canaries exercise default flush, paused/unknown overrides, SSH refusal, changed bytes and remote-command failure. - [fixed remote-toolkit@b120bc5102ef] 2026-09-27 research project:
rt pushinto a remote directory that did not exist yet reported only "push FAILED (status 98)"; the remote's own RT_NO_DEST_DIR line goes to stderr and is easily cut by a caller's| tail -1, so four pushes read as an unexplained failure. The status-98 arm now says the parent directory is missing, that push never creates directories, and tomkdir -pfirst. No contract change (push still refuses to create directories); canaries 217/217.