Skip to content

fix(driver): pin launchd drivers to committed code, and make an unpinned fire LOUD (#558) - #666

Merged
MarkEdmondson1234 merged 3 commits into
devfrom
fix/driver-pin-root
Aug 12, 2026
Merged

fix(driver): pin launchd drivers to committed code, and make an unpinned fire LOUD (#558)#666
MarkEdmondson1234 merged 3 commits into
devfrom
fix/driver-pin-root

Conversation

@sunholo-voight-kampff

Copy link
Copy Markdown
Collaborator

Closes the class behind #558, which has now been measured four times and repaired twice by hand.

The defect: one root, three symptoms

launchd invokes each driver by absolute path into the shared clone, and everything a fire reads hangs off that single root (mission-control.sh:40):

Artefact Resolves from Measured harm
the driver tools/launchd/*.sh $0 #556 retired qwen3.5, nightly ran it 24/24 two days later (iter-131); 564cc4640 lane fix inert on V1 on 2026-08-12, 12 commits behind, while both sibling missions had it
the skill .claude/skills/… cwd stale skill, iter-128
the charter design_docs/*mission*.md cwd stale charter, iter-129

Nothing keeps that clone current and nothing reports the drift — a fallback whose only witness is a log nobody reads. Every previous repair patched one artefact, which fixes a third of a bug.

The fix

tools/launchd/lib/pin-root.sh re-execs the driver out of a worktree pinned to committed origin/dev, moving script, skill and charter together.

  • It re-points MISSION_WORKDIR too. mission-motoko.env:8 and mission-world.env:5 pin it, and mission-control.sh:40 reads it ahead of $0-relative resolution — so pinning only the script would leave those two missions on a fresh driver against a stale charter. A half-fix that reports green is worse than none. Sprint worktrees are unaffected (SKILL.md:1662 uses absolute paths).
  • A failed pin is loud, not fatal. Aborting would make network availability a hard dependency of every fire. The fire proceeds and posts "driver ran UNPINNED" to both human channels. Continuing silently would have rebuilt the exact defect 564cc4640 removed one layer up.
  • Position is load-bearing: after the state block (a failure needs LOG/MSG_FROM/MISSION_GH_ISSUE to report with), before the probes (a re-exec restarts the script — pinning later bills every lane twice), before the pidfile write (the re-exec’d copy would read its parent’s pid and yield to itself, no-opping every fire).

Both notices now share one _mc_notify. Two near-identical notifiers is how one of them silently rots.

Testing

First automated coverage tools/launchd has ever had — 37 assertions, make test-launchd-drivers, /bin/bash 3.2.57 explicitly (the rig has no newer one).

  • Real origin + deliberately-stale clone: re-exec observed, root moved, fresh content read, args survive — plus a control proving the clone really was stale.
  • No recursion on the second pass; drift carried across the exec.
  • Fetch failure → STALE + reason + fire still runs, never reports pinned.
  • Driver absent from the ref → our guard fires, not bash’s No such file. The first version of that test passed for the wrong reason and was rewritten.
  • Notices awk-extracted from the real file, never retyped: fires when degraded, silent when healthy, rc=1 on either channel → WARNING and still exits 0, unset issue → WARNING not a silent skip. Lane block regression-tested through the extraction.
  • A set -u bug caught by inspection: PIN_DRIFT was initialised only inside the helper, so the helper-absent branch — the pre-helper clone it exists to survive — would have aborted at the DRY RUN line. The fallback crashing only on the fallback path.

Two things stated rather than hidden

  1. Chicken and egg. This cannot take effect until the shared clone receives it once, and that first reconcile is human (Principle 0 forbids unattended branch ops on a shared dirty tree).
  2. Scope. Wired into mission-control.sh only. The other five entry points follow once this has survived live fires. ailang-world is a separate repo with a forked 513-line driver — it needs a port, not a copy.

🤖 Generated with Claude Code

sunholo-voight-kampff and others added 3 commits August 12, 2026 09:45
…ned fire LOUD (#558)

THE DEFECT — one root, three symptoms. launchd invokes each driver by absolute path
into the shared clone, and everything a fire reads hangs off that single root
(mission-control.sh:40 `REPO="${MISSION_WORKDIR:-$(dirname $0/../..)}"; cd "$REPO"`):
the driver, the skill under .claude/skills/, and the charter under design_docs/.
Nothing keeps that clone current and nothing reports the drift, so all three go stale
together and every previous repair patched one artefact — a third of a bug.

Measured four times now: #556's retired qwen3.5 ran 24/24 two days after retirement
(iter-131); a stale skill (iter-128); a stale charter (iter-129); and 2026-08-12,
564cc46's lane-degradation fix inert on V1 at 12 commits behind while ailang-motoko
and ailang-world both had it. Two one-time human reconciles, zero durable fixes.

THE FIX. tools/launchd/lib/pin-root.sh re-execs the driver out of a worktree pinned to
committed origin/dev, which moves script, skill and charter in one step. Three details
carry the correctness:

  * It re-points MISSION_WORKDIR too. mission-motoko.env:8 and mission-world.env:5 pin
    it, and mission-control.sh:40 reads it AHEAD of $0-relative resolution — so pinning
    only the script leaves those two on a fresh driver against a stale charter and skill.
    That half-fix reports green, which is worse than no fix. Sprint worktrees are
    unaffected: the skill creates them by absolute path (SKILL.md:1662).
  * A failed pin is LOUD, not fatal. Aborting would make network availability a hard
    dependency of every fire — trading rare silent staleness for common loud outage. So
    the fire proceeds and posts "driver ran UNPINNED" on both human channels. Continuing
    SILENTLY would have rebuilt the exact defect 564cc46 removed one layer up.
  * Position is load-bearing: after the state block (so a failure has LOG/MSG_FROM/
    MISSION_GH_ISSUE to report with), before the probes (a re-exec restarts the script,
    so pinning later bills every lane twice), before the pidfile write (the re-exec'd
    copy would read its parent's pid and yield to itself, no-opping every fire).

Both notices now share one _mc_notify, extracted from 564cc46's block: two
near-identical notifiers is how one of them silently rots.

TESTED — first automated coverage tools/launchd has ever had (37 assertions,
`make test-launchd-drivers`, /bin/bash 3.2.57 explicitly, since the rig has no newer one):
  - real origin + deliberately-stale clone: re-exec observed, ROOT moved, FRESH content
    read, args survive; plus a control proving the clone really was stale (AILANG_DRIVER_PIN=0
    reads STALE-CONTENT from the clone path).
  - no recursion on the second pass; drift carried across the exec.
  - fetch failure -> STALE + reason + fire still runs, never reports pinned.
  - driver absent FROM THE REF -> our guard fires, not bash's "No such file" (the first
    version of that test passed for the wrong reason and was rewritten).
  - notices awk-extracted from the real file, never retyped: fires when degraded, SILENT
    when healthy, rc=1 on either channel -> WARNING and still exits 0, unset issue ->
    WARNING not a silent skip. Lane block regression-tested through the extraction.
  - `set -u` bug caught by inspection and fixed: PIN_DRIFT was initialised only inside the
    helper, so the helper-absent branch — the pre-helper clone this branch exists to
    survive — would have aborted at the DRY RUN line. The fallback crashing only on the
    fallback path.

CHICKEN AND EGG, stated rather than hidden: this cannot take effect until the shared
clone receives it once, and that first reconcile is human — Principle 0 forbids
unattended branch ops on a shared dirty tree. Wired into mission-control.sh only;
the other five entry points follow once this has survived live fires.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit added `make test-launchd-drivers` and no job invoked it. A suite that
only runs when someone types the command is decoration — and it is the same shape as the
bug this branch fixes: a protection that is silently not in effect.

macOS runner deliberately. The rig is bash 3.2.57 and GitHub's macOS images still ship 3.2
as /bin/bash; ubuntu ships bash 5, where every `declare -A` and `${v,,}` that would break
the rig passes clean. Testing rig shell portability on bash 5 measures the wrong instrument.
A guard step asserts BASH_VERSINFO[0] == 3 and fails the job if the runner is ever upgraded,
so the coverage cannot silently degrade into a bash-5 run that still reports green.

Also made test_pin_root.sh hermetic: it builds a real git lab, and a CI runner has no
user.name/user.email and may default init.defaultBranch anywhere, so the lab commits would
have failed there and the suite would have reported a git-config problem as a pin failure —
green on the author's box, red everywhere else. Now pins identity and neutralises global and
system git config. Verified by running it with HOME=/nonexistent and the identity env
unset: 20/20.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ther than on a date

Adds the five remaining launchd entry points to the V1 queue as a single rollout item, with
the gate written as evidence (>=3 consecutive fires logging a successful pin, read from the
driver log rather than from the file's presence on disk) instead of a delay.

Three things recorded deliberately, because each has already cost an iteration:

  * INERT UNTIL RECONCILED. #666 changes nothing on the rig until the shared clone receives
    it once, and that is a human branch op — the standing fast-forward authorisation does not
    cover it (local dev is 1 ahead, not 0). This is exactly what deferred m-planner-codex-lane
    at iter-131: a sprint that lands green, passes CI, and changes nothing while the report
    claims the capability was gained.
  * ORDER. Merge #666 first, reconcile second. The reverse brings the lane fix without the
    pin, and the clone starts drifting again immediately.
  * THE GATE'S LIMIT. Three green fires exercise the re-exec, the root move and the fetch, but
    not the failure path, which production will not produce on demand. That arm is covered by
    the test suite alone, so a passing gate is evidence about the happy path and must not be
    reported as confidence in the whole fix.

ailang-world is explicitly out of scope: different repo, hand-synced fork (513 lines vs 671,
differently-shaped fallback site), portable only by hand. Handed over as msg_20260812_085746;
World's loop decides when.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@MarkEdmondson1234
MarkEdmondson1234 merged commit d4863fa into dev Aug 12, 2026
21 checks passed
@MarkEdmondson1234
MarkEdmondson1234 deleted the fix/driver-pin-root branch August 12, 2026 08:30
sunholo-voight-kampff added a commit that referenced this pull request Aug 12, 2026
… gate on onboarding (#558) (#667)

TWO DEFECTS IN #666, one of which would have taken the V1 loop down on the first fire after
the reconcile. Found by reading 76ee405, which landed on dev while #666 was in review.

DEFECT 1 — the pin walks into a dead checkout. A pin worktree is BY CONSTRUCTION a path
Claude Code has never seen, and an un-onboarded checkout makes headless `claude -p` block on
a trust dialog it cannot display. Every model probe then hangs to its timeout (120s x2 x3
models ~= 12 min) and the driver refuses with "NO usable model in prefs" — which reads as a
quota outage, not as the harness fault it is. That had just cost the motoko mission its whole
first unattended fire (charter V22). Measured here before writing any code:

    onboarded=true    /Users/voightkampff/dev/sunholo-data/ailang
    onboarded=ABSENT  /Users/voightkampff/.ailang-driver-pin/v1     <- the pin target

So #666, the moment the shared clone received it, would have converted a working-but-stale
loop into a dead one. Stale-but-working strictly beats fresh-and-dead, so the pin now reads
projects[<wt>].hasCompletedProjectOnboarding from ~/.claude.json and REFUSES when it is absent
— reported on both human channels like every other pin failure, with the exact one-line human
fix in the message. Undeterminable (no jq) is treated as un-onboarded on purpose: pinning
blind risks 12 minutes of hung probes, refusing costs staleness that is already reported.
jq is /usr/bin/jq, inside launchd's default PATH.

It gates on hasCompletedProjectOnboarding, NOT the hasTrustDialogAccepted the error text
names: 76ee405 measured ailang-world at trust=false/onboarded=true and WORKING, which is the
control proving the flag the message points at is not the gate. Test 8 pins both arms so a
future edit cannot quietly switch to the wrong flag and stay green.

DEFECT 2 — a fix that disables itself after one use. Worktree existence was decided by
string-matching `git worktree list`, but git records the RESOLVED realpath, so on any path
reached through a symlink (/var -> /private/var, the macOS everyday case) the match silently
fails, fire 2 takes the `add` branch against an existing directory, and the pin refuses from
then on. Loud, but permanently useless. Now decided by asking the worktree itself. Caught by
the new repeat-fire test, not by review — the first suite only ever fired once.

CONSEQUENCE FOR DEPLOYMENT: reconciling the shared clone is now safe with no further action —
the pin declines and says why. Pinning switches ON when a human runs, once per checkout:
    cd ~/.ailang-driver-pin/<mission> && claude

TESTED: 48 assertions (31 + 17), `make test-launchd-drivers`, bash 3.2.57, CI job on macOS.
New: un-onboarded => refuse + fire still runs + never reports pinned; trust-flag-alone does NOT
satisfy while onboarding-flag does; no-jq fails safe; second consecutive fire pins. The happy
path runs against a synthetic ~/.claude.json in a fake HOME rather than with the check disabled,
so a gate regressed to always-refuse fails tests 1-6 and one regressed to always-allow fails 7.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants