From 9aa17ba8b29f9e7e93d82ad30c1e61aa2dfdd007 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 19 Sep 2026 07:26:54 +0000 Subject: [PATCH] fix(agents): pin the bd installer and stop hiding jq failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two findings from the review of #7. The hook piped an unpinned installer from beads' `main` straight into a shell. That runs code nobody has seen, from a URL whose contents can change under us, and leaves nothing to inspect when something goes wrong. It now downloads first and runs the file, so /tmp/bd-install.sh is readable afterwards, and pins the revision: the peeled commit of tag v1.3.0, which is the version this work was done against. The comment carries the ls-remote incantation for bumping it. Byte-identical to `main` today, so the pin costs nothing now and is the whole point later. The seeding script read its input with `mapfile < <(jq …)`, and a process substitution discards the exit status of what ran inside it. A malformed or unreadable flake.lock therefore produced an empty list, which the next branch reported as "no github-sourced inputs; nothing to seed" and exited 0 — the script would claim success having seeded nothing. jq's output is now captured with the status checked, and emptiness is a separate case from failure. Verified by exercising the paths rather than reading them: malformed flake.lock -> error names the file, rc=1 valid lock, no github inputs -> "nothing to seed", rc=0 missing flake.lock -> error, rc=1 installer download -> /tmp/bd-install.sh matches f45b249 byte for byte shellcheck -S style clean on both scripts; the real seeding run still completes with flake.lock unmodified. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DyHK7L8S1XDX3EihTnSKaX --- .claude/hooks/session-start.sh | 22 ++++++++++++++++------ scripts/nix-seed-flake-inputs.sh | 20 +++++++++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh index 5c3cef9a..08a08435 100755 --- a/.claude/hooks/session-start.sh +++ b/.claude/hooks/session-start.sh @@ -69,15 +69,25 @@ install_tracker() { # The embedded Dolt engine needs CGO, so this builds from source (~3 min). # The container image is cached after the hook, so it is paid once. export PATH="$PATH:/root/go/bin:$HOME/go/bin" +# Pinned to a commit rather than `main`, and downloaded before it is run: +# piping a moving URL straight into a shell executes code nobody has seen, +# and leaves nothing to inspect afterwards. The installer stays at +# /tmp/bd-install.sh so what actually ran can be read. +# +# To take a newer beads, resolve the peeled commit of its tag: +# git ls-remote --tags https://github.com/steveyegge/beads | grep 'v1.3.0^{}' +BD_INSTALLER_REF=f45b249ce6b40ba62aecc03949e6371e8f7c79d8 # tag v1.3.0 +BD_INSTALLER_URL="https://raw.githubusercontent.com/steveyegge/beads/$BD_INSTALLER_REF/scripts/install.sh" + if ! command -v bd >/dev/null 2>&1; then - log "installing bd (beads)…" - # Redirect the whole pipeline: with only `bash` redirected, a curl failure - # (DNS, 403, TLS) would leave the log empty. - if { curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh \ - | bash; } >/tmp/bd-install.log 2>&1; then + log "installing bd (beads v1.3.0)…" + if ! curl -fsSL "$BD_INSTALLER_URL" -o /tmp/bd-install.sh 2>/tmp/bd-install.log; then + log "WARNING: could not download the bd installer; see /tmp/bd-install.log." + elif bash /tmp/bd-install.sh >>/tmp/bd-install.log 2>&1; then log "bd installed" else - log "WARNING: bd install failed; see /tmp/bd-install.log. Issue tracking unavailable." + log "WARNING: bd install failed; see /tmp/bd-install.log (the script that" + log " ran is at /tmp/bd-install.sh). Issue tracking unavailable." fi fi if [ -n "${CLAUDE_ENV_FILE:-}" ]; then diff --git a/scripts/nix-seed-flake-inputs.sh b/scripts/nix-seed-flake-inputs.sh index a28c1f22..17bc13be 100755 --- a/scripts/nix-seed-flake-inputs.sh +++ b/scripts/nix-seed-flake-inputs.sh @@ -41,18 +41,24 @@ if [ -z "${NIX_SSL_CERT_FILE:-}" ] && [ -r /root/.ccr/ca-bundle.crt ]; then fi NIX_FLAGS=(--extra-experimental-features 'nix-command flakes') -mapfile -t INPUTS < <( - jq -r '.nodes | to_entries[] | .value.locked - | select(.type == "github" and .rev) - | "git+https://github.com/\(.owner)/\(.repo)?rev=\(.rev)&shallow=1"' flake.lock \ - | sort -u -) +# Captured rather than piped straight into mapfile: a process substitution +# hides jq's exit status, so an unreadable or malformed flake.lock would look +# exactly like a lock with no github inputs and the script would exit 0 having +# seeded nothing. +if ! locked_inputs=$(jq -r '.nodes | to_entries[] | .value.locked + | select(.type == "github" and .rev) + | "git+https://github.com/\(.owner)/\(.repo)?rev=\(.rev)&shallow=1"' flake.lock); then + echo "error: could not read inputs from flake.lock — is it valid JSON?" >&2 + exit 1 +fi -if [ "${#INPUTS[@]}" -eq 0 ]; then +if [ -z "$locked_inputs" ]; then echo "no github-sourced inputs in flake.lock; nothing to seed" exit 0 fi +mapfile -t INPUTS < <(printf '%s\n' "$locked_inputs" | sort -u) + echo "seeding ${#INPUTS[@]} locked inputs over git (flake.lock is not modified)…" failed=0 for url in "${INPUTS[@]}"; do