Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions changelogs/v0.18-current.md
Original file line number Diff line number Diff line change
Expand Up @@ -583,14 +583,25 @@
when it is absent or undeterminable, because stale-but-working strictly beats fresh-and-dead.
It reads the *measured* flag, not the `hasTrustDialogAccepted` the error text names —
`ailang-world` runs with trust=false/onboarded=true, the control proving the named flag is not
the gate. Consequence: reconciling is safe with no further action (the pin declines, loudly);
pinning switches on when a human runs `cd ~/.ailang-driver-pin/<mission> && claude` once.
the gate.

**Corrected the same day, by measurement.** That first predicate checked the pin worktree's
*own* entry, and it was wrong: `claude -p` runs fine from `~/.ailang-driver-pin/v1` while
`~/.claude.json` holds **no entry for it at all** — a git worktree inherits its source clone's
trust, having no separate identity to onboard. Checking the worktree would have refused a
demonstrably working target on every fire, leaving the pin permanently off — safe, but
permanently off is not the goal. The predicate is now the **source clone**, accepting when
either path is onboarded. The two measured cases discriminate it cleanly: `ailang-motoko`
(fresh clone, nothing onboarded anywhere) hangs and is refused; a worktree of an onboarded
clone works and is accepted. Consequence: on a rig whose main clone is already onboarded,
pinning needs **no human action at all** — the earlier `cd … && claude` instruction was an
artefact of the wrong predicate.

The same pass fixed a latent one-shot bug the new tests caught: worktree existence was decided
by string-matching `git worktree list`, which never matches git's recorded realpath when any
path component is a symlink (`/var` → `/private/var`). Fire 1 would pin, fire 2 would hit
`worktree add` on an existing directory and refuse — a fix that disables itself after first
use while reporting loudly. Now decided by asking the worktree itself. 48 assertions.
use while reporting loudly. Now decided by asking the worktree itself. 52 assertions.

- **`dev.ailang.mission-recovery` — a probe-stall costs ~20 min of loop time instead of
90.** When every controller probe times out the driver refuses, and with
Expand Down
38 changes: 27 additions & 11 deletions tools/launchd/lib/pin-root.sh
Original file line number Diff line number Diff line change
Expand Up @@ -161,27 +161,43 @@ pin_root_to_committed_ref() {
_pin_stale "$ref has no tools/launchd/$script — refusing to re-exec into a missing driver"; return 1
fi

# ONBOARDING GATE — the one that makes this whole mechanism dangerous by default.
# A checkout Claude Code has never seen makes headless `claude -p` block on a trust dialog it
# cannot display, so EVERY model probe hangs to its timeout and the driver refuses with "NO
# usable model in prefs" — indistinguishable from a quota outage. That cost the motoko mission
# its entire first unattended fire (charter V22, commit 76ee4056c). A pin worktree is BY
# CONSTRUCTION a path Claude Code has never seen, so pinning into an un-onboarded one trades
# stale-but-working for fresh-and-dead. Staleness is the strictly smaller harm: refuse.
# ONBOARDING GATE — a checkout Claude Code has never seen makes headless `claude -p` block on
# a trust dialog it cannot display, so EVERY model probe hangs to its timeout and the driver
# refuses with "NO usable model in prefs" — indistinguishable from a quota outage. That cost
# the motoko mission its entire first unattended fire (charter V22, commit 76ee4056c).
# Staleness is the strictly smaller harm than a dead loop, so an unusable target means refuse.
#
# THE PREDICATE IS THE SOURCE REPO, NOT THE WORKTREE PATH. The first cut checked the pin
# worktree's own entry and was measured WRONG on 2026-08-12: `claude -p` runs fine from
# ~/.ailang-driver-pin/v1 while ~/.claude.json has NO entry for it at all — a git worktree
# inherits its source clone's trust, having no separate identity to onboard. Checking the
# worktree would have refused a demonstrably working target on every fire, leaving the pin
# permanently off. The two measured cases discriminate cleanly:
#
# ailang-motoko fresh CLONE, source entry ABSENT -> probes HANG (iteration 1 lost)
# ~/.ailang-driver-pin/v1 WORKTREE of an onboarded clone,
# own entry ABSENT -> probes WORK (measured)
#
# So: accept when EITHER path is onboarded. That refuses exactly the motoko shape (nothing
# onboarded anywhere) and accepts the worktree-of-a-good-clone shape. `$wt` is still checked
# first because AILANG_DRIVER_PIN_DIR can point somewhere that is NOT a worktree of $src.
#
# `hasCompletedProjectOnboarding`, NOT the `hasTrustDialogAccepted` the error text names —
# 76ee4056c measured all three checkouts and ailang-world (trust=false, onboarded=true) WORKS,
# which is the control proving the flag the message points at is not the gate.
# 76ee4056c measured ailang-world (trust=false, onboarded=true) WORKING, the control proving
# the flag the message points at is not the gate.
#
# Undeterminable is treated as un-onboarded, deliberately: pinning blind risks ~12 min of hung
# probes and a dead loop, refusing costs staleness that is already reported. jq lives at
# /usr/bin/jq, inside launchd's default PATH, so its absence means something is genuinely wrong.
_pin_onboarded() { # $1 = path -> echoes true/false
jq -r --arg p "$1" '.projects[$p].hasCompletedProjectOnboarding // false' "$HOME/.claude.json" 2>/dev/null
}
if [ -n "${AILANG_DRIVER_SKIP_ONBOARD_CHECK:-}" ]; then
:
elif ! command -v jq >/dev/null 2>&1; then
_pin_stale "cannot verify Claude Code onboarding for $wt (jq not found) — refusing to pin into a possibly-unusable checkout"; return 1
elif [ "$(jq -r --arg p "$wt" '.projects[$p].hasCompletedProjectOnboarding // false' "$HOME/.claude.json" 2>/dev/null)" != "true" ]; then
_pin_stale "$wt is not onboarded in Claude Code — every model probe would hang there (charter V22). Run once, interactively: cd $wt && claude"; return 1
elif [ "$(_pin_onboarded "$wt")" != "true" ] && [ "$(_pin_onboarded "$src")" != "true" ]; then
_pin_stale "neither $wt nor its source clone $src is onboarded in Claude Code — every model probe would hang there (charter V22). Run once, interactively: cd $src && claude"; return 1
fi

# MISSION_WORKDIR moves too, and that is the point: mission-control.sh:40 reads it AHEAD of
Expand Down
20 changes: 19 additions & 1 deletion tools/launchd/test_pin_root.sh
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ echo "== 7. un-onboarded pin target => REFUSE, do not exec into a probe-hang =="
printf '{"projects":{}}' > "$HOME/.claude.json"
UO=$(/bin/bash "$DRV" 2>&1)
check "refuses to pin" "$UO" "STATUS=STALE"
check "names the onboarding cause" "$UO" "is not onboarded in Claude Code"
check "names the onboarding cause" "$UO" "onboarded in Claude Code"
check "gives the exact human fix" "$UO" "&& claude"
check "fire still runs, unpinned" "$UO" "MARKER=STALE-CONTENT"
checkno "never reports pinned" "$UO" "STATUS=pinned"
Expand All @@ -158,6 +158,24 @@ printf '{"projects":{"%s":{"hasCompletedProjectOnboarding":true}}}' "$T/pinwt" >
OB=$(/bin/bash "$DRV" 2>&1)
check "onboarding flag DOES satisfy" "$OB" "STATUS=pinned"

echo "== 8b. SOURCE-clone onboarding satisfies it — the case the first cut got WRONG =="
# Measured 2026-08-12: `claude -p` runs fine from ~/.ailang-driver-pin/v1 while ~/.claude.json
# has NO entry for it — a worktree inherits its source clone's trust. The first predicate checked
# only the worktree's own entry, so it would have refused a demonstrably working target on every
# fire and left the pin permanently off. This is the production shape: worktree absent, source ok.
printf '{"projects":{"%s":{"hasCompletedProjectOnboarding":true}}}' "$T/clone" > "$HOME/.claude.json"
SC=$(/bin/bash "$DRV" 2>&1)
check "source onboarding is enough" "$SC" "STATUS=pinned"
check "and it really pinned" "$SC" "MARKER=FRESH-CONTENT"

echo "== 8c. NEITHER onboarded => still refuse (the motoko shape) =="
# The gate must not have been widened into a no-op: a fresh clone with nothing onboarded anywhere
# is exactly what cost motoko iteration 1, and it must still be refused.
printf '{"projects":{"%s":{"hasCompletedProjectOnboarding":true}}}' "/some/unrelated/path" > "$HOME/.claude.json"
NN=$(/bin/bash "$DRV" 2>&1)
check "neither path onboarded => STALE" "$NN" "STATUS=STALE"
check "message names BOTH paths" "$NN" "nor its source clone"

echo "== 9. undeterminable (no jq) fails SAFE, not open =="
mkdir -p "$T/nojq"
for b in git mktemp date basename dirname cat rm sleep kill grep tr tail printf mkdir; do
Expand Down
Loading