Files
improvise/scripts/nix-seed-flake-inputs.sh
Claude 9aa17ba8b2 fix(agents): pin the bd installer and stop hiding jq failures
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyHK7L8S1XDX3EihTnSKaX
2026-09-19 07:26:54 +00:00

82 lines
3.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Seed the Nix store with this flake's locked inputs, fetched over git.
#
# Why this exists: Nix's `github:` fetcher downloads a repository as a tarball
# from github.com/<owner>/<repo>/archive/<rev>.tar.gz. In a Claude Code cloud
# session that endpoint is not served — GitHub traffic bypasses the
# environment's domain allowlist and goes through a proxy that scopes API and
# release-asset requests to repositories attached to the session, so a flake
# input from any other owner gets a 403. Raising network access to Full does
# not help, because GitHub never goes through that allowlist.
#
# The git protocol IS served. And `flake.lock` already records a narHash for
# every input, which Nix looks up in the store and its substituters before
# fetching anything (see `nix flake` in the Nix manual). The git and tarball
# fetchers produce byte-identical trees for a given revision, so fetching over
# git lands the input at exactly the store path the lock names.
#
# So this script reads flake.lock and fetches each locked revision over
# git+https. It never writes to flake.lock. Nix still verifies every tree
# against the narHash the lock already records: a mismatch fails loudly here
# rather than being papered over. Afterwards `nix develop` finds every input
# in the store and never reaches for a tarball.
#
# Cost: a few minutes and a few hundred MB of git cache on a cold container;
# the full devShell build is several GB more. Run it when you actually need
# the devShell — the preinstalled cargo toolchain covers ordinary work.
#
# Usage: scripts/nix-seed-flake-inputs.sh && nix develop
set -uo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.." || exit 1
for tool in nix jq; do
command -v "$tool" >/dev/null 2>&1 || { echo "error: $tool not found on PATH" >&2; exit 1; }
done
[ -f flake.lock ] || { echo "error: no flake.lock here" >&2; exit 1; }
# Nix needs the proxy CA when it talks to github over https.
if [ -z "${NIX_SSL_CERT_FILE:-}" ] && [ -r /root/.ccr/ca-bundle.crt ]; then
export NIX_SSL_CERT_FILE=/root/.ccr/ca-bundle.crt
fi
NIX_FLAGS=(--extra-experimental-features 'nix-command flakes')
# 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 [ -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
rev="${url##*rev=}"; rev="${rev%%&*}"
label="${url#git+https://github.com/}"
label="${label%%\?*}@${rev:0:7}"
if nix "${NIX_FLAGS[@]}" flake prefetch --json "$url" >/dev/null 2>&1; then
printf ' ok %s\n' "$label"
else
printf ' FAIL %s\n' "$label"
failed=$((failed + 1))
fi
done
if [ "$failed" -gt 0 ]; then
echo "$failed input(s) could not be seeded; 'nix develop' will still try a tarball for those." >&2
exit 1
fi
echo "done — 'nix develop' should now resolve without touching the tarball endpoint"