diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh index 85da6cc9..b1fb67c4 100755 --- a/.claude/hooks/session-start.sh +++ b/.claude/hooks/session-start.sh @@ -11,8 +11,13 @@ # 3. The Rust workspace is uncompiled, so the first `cargo test` pays a # cold build before it reports anything. # -# Idempotent: every step checks before acting. Nothing here writes to a -# tracked file, stages anything, or creates a commit. +# The tracker is re-synced on every session, not just when the database is +# missing: cloud containers reuse a cached workspace, so "already present" +# does not mean "still current", and a silently ageing tracker is the exact +# failure this hook exists to prevent. Installing bd and warming the build +# do check first, being genuinely one-time. +# +# Nothing here writes to a tracked file, stages anything, or creates a commit. set -uo pipefail ROOT="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" @@ -31,8 +36,10 @@ log() { printf '[session-start] %s\n' "$*"; } export PATH="$PATH:/root/go/bin:$HOME/go/bin" if ! command -v bd >/dev/null 2>&1; then log "installing bd (beads)…" - if curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh \ - | bash >/tmp/bd-install.log 2>&1; then + # 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 "bd installed" else log "WARNING: bd install failed; see /tmp/bd-install.log. Issue tracking unavailable." @@ -43,11 +50,12 @@ if [ -n "${CLAUDE_ENV_FILE:-}" ]; then fi command -v bd >/dev/null 2>&1 && bd metrics off >/dev/null 2>&1 -# ── 2. Hydrate the issue tracker from the Dolt remote ────────────────────── +# ── 2. Sync the issue tracker from the Dolt remote ───────────────────────── # `bd init` in-place rewrites .beads/config.yaml and commits, so the sync -# happens in a throwaway directory and only the database is copied back. -# The directory must be named after the database (see .beads/metadata.json). -if command -v bd >/dev/null 2>&1 && [ ! -d .beads/embeddeddolt ]; then +# happens in a throwaway directory and only the database is moved back. +# That directory must be named after the database (see .beads/metadata.json), +# since bd derives the database name from it. +if command -v bd >/dev/null 2>&1; then DB_NAME="$(jq -r '.dolt_database // "improvise"' .beads/metadata.json 2>/dev/null || echo improvise)" ORIGIN="$(git remote get-url origin 2>/dev/null || true)" case "$ORIGIN" in @@ -58,15 +66,47 @@ if command -v bd >/dev/null 2>&1 && [ ! -d .beads/embeddeddolt ]; then if [ -n "$ORIGIN" ]; then log "syncing issue tracker from $ORIGIN …" TMP="$(mktemp -d)" - mkdir -p "$TMP/$DB_NAME" - ( - cd "$TMP/$DB_NAME" && git init -q . && + STAGE="$TMP/$DB_NAME" + mkdir -p "$STAGE" + + # `bd init` exits non-zero when the remote is unreachable but STILL leaves + # a .beads/embeddeddolt behind — an empty database. Testing only for the + # directory would swap that over a good tracker, so check the status and + # confirm the result actually carries issues. An empty remote tracker is + # indistinguishable from a failed clone here; keeping what we already have + # is the safe way to be wrong. + sync_ok=0 + if ( + cd "$STAGE" && git init -q . && BD_NON_INTERACTIVE=1 bd init --non-interactive --skip-agents --skip-hooks -q \ --remote "git+$ORIGIN" - ) >/tmp/bd-sync.log 2>&1 - if [ -d "$TMP/$DB_NAME/.beads/embeddeddolt" ]; then - cp -rf "$TMP/$DB_NAME/.beads/embeddeddolt" .beads/embeddeddolt - log "issue tracker synced — 'bd ready' now reflects the remote, not issues.jsonl" + ) >/tmp/bd-sync.log 2>&1 && [ -d "$STAGE/.beads/embeddeddolt" ]; then + listing="$( (cd "$STAGE" && bd list --all 2>/dev/null) || true )" + case "$listing" in + "" | "No issues found."*) ;; + *) sync_ok=1 ;; + esac + fi + + if [ "$sync_ok" -eq 1 ]; then + # Swap the verified result in, parking the old database inside $TMP + # until the move succeeds — a failed install must leave the previous + # tracker in place rather than none at all. The holding spot is under + # $TMP, not .beads/, so nothing untracked ever appears in the repo. + [ -d .beads/embeddeddolt ] && mv .beads/embeddeddolt "$TMP/previous-db" + if mv "$STAGE/.beads/embeddeddolt" .beads/embeddeddolt; then + log "issue tracker synced — 'bd ready' now reflects the remote, not issues.jsonl" + elif [ -d "$TMP/previous-db" ]; then + mv "$TMP/previous-db" .beads/embeddeddolt + log "WARNING: could not install the synced tracker; kept the previous database." + log " It may be out of date — verify any bead against the code." + else + log "WARNING: could not install the synced tracker and there is no previous one." + log " .beads/issues.jsonl is a STALE export — verify any bead against the code." + fi + elif [ -d .beads/embeddeddolt ]; then + log "WARNING: tracker sync failed (see /tmp/bd-sync.log); kept the previous database." + log " It may be out of date — verify any bead against the code." else log "WARNING: tracker sync failed; see /tmp/bd-sync.log." log " .beads/issues.jsonl is a STALE export — verify any bead against the code." diff --git a/.gitignore b/.gitignore index 76ac9878..b5c597f5 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ proptest-regressions/ # ...except the checked-in seed knowledge base, which agents load at session start. prolog-sessions/* !prolog-sessions/kb.pl + +# Beads runtime locks (bd creates these next to .beads/) +*.gate.lock*