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
31 changes: 28 additions & 3 deletions scripts/check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# scripts/check.sh --fix markdown # apply the fixes a check can apply
#
# Checks: shell, markdown, yaml, profiles, queue, reconcile, status, skills,
# pane, voice, onboarding. Only `markdown` has a fixer; `--fix` is a no-op for
# pane, voice, onboarding, sync. Only `markdown` has a fixer; `--fix` is a no-op for
# the rest, so `scripts/check.sh --fix` is always safe to run.
#
# Requires: shellcheck, rumdl, python3 (with PyYAML), lua. A missing tool
Expand Down Expand Up @@ -167,6 +167,30 @@ check_reconcile() {
fi
}

# The SessionStart hook, and the one script whose output is its only evidence.
# It runs before anyone is watching, so a refusal it reports that did not
# actually happen is believed: "offline" reads as a network blip, nobody looks,
# and every session inherits the stale `main` the script exists to prevent.
# That is how `timeout 15 git fetch` shipped — `timeout` is GNU coreutils and is
# absent on a stock macOS, so it exited 127 and every Mac session reported an
# unreachable origin while the network was fine. The selftest builds a PATH
# holding only the tools the script may use, which reproduces that condition on
# any platform, and holds the rest of the contract around it: a refusal changes
# no tracked state, a real outage is still reported, and a fast-forward that
# brings instructions raises restart-lead.
check_sync() {
need git sync || return
need jq sync || return

if ./scripts/sync-selftest.sh >/dev/null; then
ok "sync: bounds the fetch without coreutils, refuses without touching the tree, and raises the hand-over"
else
# Re-run visibly: a failing claim is the whole message.
./scripts/sync-selftest.sh
fail "sync: scripts/sync-selftest.sh"
fi
}

# The status command's two promises, both invisible until they cost something.
# It must DEGRADE — a missing `gh`, a missing thurbox, a missing queue each
# cost exactly their own section and never the reading — and it must carry
Expand Down Expand Up @@ -570,7 +594,7 @@ for arg in "$@"; do
done

if [ ${#checks[@]} -eq 0 ]; then
checks=(shell markdown yaml profiles queue reconcile status skills pane voice onboarding)
checks=(shell markdown yaml profiles queue reconcile status skills pane voice onboarding sync)
fi

for c in "${checks[@]}"; do
Expand All @@ -582,12 +606,13 @@ for c in "${checks[@]}"; do
queue) check_queue ;;
reconcile) check_reconcile ;;
status) check_status ;;
sync) check_sync ;;
skills) check_skills ;;
pane) check_pane ;;
voice) check_voice ;;
onboarding) check_onboarding ;;
*)
printf 'error: unknown check %q (want: shell markdown yaml profiles queue reconcile status skills pane voice onboarding)\n' "$c" >&2
printf 'error: unknown check %q (want: shell markdown yaml profiles queue reconcile status skills pane voice onboarding sync)\n' "$c" >&2
exit 2
;;
esac
Expand Down
45 changes: 44 additions & 1 deletion scripts/sync-checkout.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@

set -uo pipefail

# How long the fetch below may take before the hook gives up and works from the
# local checkout. A session start is the wrong place to wait on a network.
FETCH_TIMEOUT_SECS=15

# Changing one of these means the running Mission Control session is holding
# stale instructions; changing one of the wiring paths means the installed
# thurbox extension no longer matches the manifest it was rendered from. Neither
Expand Down Expand Up @@ -71,7 +75,46 @@ repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || exit 0
cd "$repo_root" || exit 0

# Network call: bounded, never hangs a session start.
if ! timeout 15 git fetch --quiet origin 2>/dev/null; then
#
# THE BOUND CANNOT COME FROM `timeout`, which is GNU coreutils and is not on a
# stock macOS. Calling it there exits 127 — "command not found" — which is
# indistinguishable from a failed fetch to the guard below, so every session on
# a Mac reported "could not reach origin (offline?)" while the network was fine,
# and then silently worked from a stale `main`. That is the exact outcome this
# script exists to prevent, and it is invisible: an "offline" line reads as a
# blip, so nobody looks. Homebrew's coreutils installs the same tool as
# `gtimeout`; with neither present we bound the fetch ourselves, because an
# unbounded fetch in a SessionStart hook would hang the session open.
fetch_bounded() {
if command -v timeout >/dev/null 2>&1; then
timeout "$FETCH_TIMEOUT_SECS" git fetch --quiet origin 2>/dev/null
return $?
fi
if command -v gtimeout >/dev/null 2>&1; then
gtimeout "$FETCH_TIMEOUT_SECS" git fetch --quiet origin 2>/dev/null
return $?
fi

# The portable watchdog. Poll rather than rely on `wait -n` or SIGALRM:
# this runs under whatever bash the machine has, and macOS ships 3.2.
git fetch --quiet origin 2>/dev/null &
local pid=$!
local waited=0
while kill -0 "$pid" 2>/dev/null; do
[ "$waited" -ge $((FETCH_TIMEOUT_SECS * 10)) ] && break
sleep 0.1
waited=$((waited + 1))
done

if kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null
wait "$pid" 2>/dev/null
return 124 # what `timeout` returns when it fires, for the same reason
fi
wait "$pid"
}

if ! fetch_bounded; then
emit "control-plane sync: could not reach origin (offline?). Working from the local checkout."
fi

Expand Down
Loading