fix(migrate-v2): bash 3.2 compatibility + reset coverage

migrate-v2.sh
  Replace `declare -A STEP_RESULTS` with two parallel indexed arrays
  (STEP_NAMES + STEP_STATUSES) plus a `record_step` helper. macOS ships
  bash 3.2 which has no associative arrays — `declare -A` errored out
  silently and every `STEP_RESULTS["1a-env"]=...` triggered a fatal
  bash arithmetic error (interpreting "1a" as a number). Visible
  symptom: `steps: {}` in handoff.json. Latent symptom: phase 2c's
  install loop sometimes bailed mid-iteration before invoking the
  channel install script, leaving channel code uninstalled while
  reporting `overall_status: success`.

migrate-v2-reset.sh
  Cover the gaps that left install side-effects in place between
  iterations:
    - Remove untracked adapter files in src/channels/ (mirror the
      pattern already used for container/skills/).
    - Restore tracked setup helpers that channel installs overwrite
      (setup/whatsapp-auth.ts, setup/pair-telegram.ts, setup/index.ts)
      and remove untracked ones they create (setup/groups.ts).
    - Restore package.json + pnpm-lock.yaml (channel installs add
      deps like @whiskeysockets/baileys).
  Setup/migrate-v2/* is intentionally not touched — that's where user
  WIP lives.

Verified end-to-end: reset → migrate → all 9 steps reported in
handoff.json with status "success", phase 2c install actually runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gavriel Cohen
2026-05-02 14:50:21 +03:00
parent aec7ddd099
commit 416c283dcb
2 changed files with 73 additions and 31 deletions
+37 -8
View File
@@ -6,17 +6,27 @@
# bash migrate-v2-reset.sh && bash migrate-v2.sh
#
# What it removes:
# - data/ (v2 DBs, session state)
# - logs/ (migration + setup logs)
# - .env (merged env keys)
# - groups/*/ (non-git group folders copied from v1)
# - data/ (v2 DBs, session state)
# - logs/ (migration + setup logs)
# - .env (merged env keys)
# - groups/*/ (non-git group folders copied from v1)
# - container/skills/*/ (untracked skill dirs copied from v1)
# - src/channels/*.ts (untracked adapters copied from channels branch)
# - setup/groups.ts (untracked, copied by channel install scripts)
#
# What it restores:
# - groups/global/CLAUDE.md and groups/main/CLAUDE.md from git
# What it restores from git:
# - groups/ (CLAUDE.md files etc.)
# - container/skills/ (tracked container skills)
# - src/channels/ (tracked bridge / registry code)
# - setup/whatsapp-auth.ts (channel installs may overwrite)
# - setup/pair-telegram.ts (channel installs may overwrite)
# - setup/index.ts (channel installs append entries)
# - package.json + pnpm-lock.yaml (channel installs add deps)
#
# What it does NOT touch:
# - node_modules/ (expensive to reinstall, keep it)
# - The v1 install (read-only, never modified)
# - node_modules/ (expensive to reinstall, kept on purpose)
# - setup/migrate-v2/* (the migration scripts themselves, plus user WIP)
# - The v1 install (read-only, never modified)
set -euo pipefail
@@ -63,7 +73,26 @@ printf '%s Restored %s\n' "$(green '✓')" "container/skills/ from git"
# Restore channel code (src/channels/) to git state
git checkout -- src/channels/ 2>/dev/null || true
# Remove any untracked channel adapters copied in by install-*.sh
for f in src/channels/*.ts; do
[ -f "$f" ] || continue
if ! git ls-files --error-unmatch "$f" >/dev/null 2>&1; then
rm -f "$f"
fi
done
printf '%s Restored %s\n' "$(green '✓')" "src/channels/ from git"
# Restore tracked setup helpers that channel installs overwrite, and
# remove the untracked ones they create. Don't blanket-clean setup/
# because user WIP (setup/migrate-v2/*) lives there too.
git checkout -- setup/whatsapp-auth.ts setup/pair-telegram.ts setup/index.ts 2>/dev/null || true
rm -f setup/groups.ts
printf '%s Restored %s\n' "$(green '✓')" "setup/ install helpers"
# Restore package.json + lockfile (channel installs add deps like
# @whiskeysockets/baileys). node_modules/ is intentionally kept.
git checkout -- package.json pnpm-lock.yaml 2>/dev/null || true
printf '%s Restored %s\n' "$(green '✓')" "package.json + pnpm-lock.yaml"
echo
printf '%s\n\n' "$(dim 'Clean. Run: bash migrate-v2.sh')"
+36 -23
View File
@@ -29,14 +29,25 @@ SERVICE_SWITCHED=false
SELECTED_CHANNELS=()
ABORTED_AT=""
# Per-step status tracking. Parallel indexed arrays so this works on
# bash 3.2 (macOS default) which has no associative arrays.
STEP_NAMES=()
STEP_STATUSES=()
record_step() {
STEP_NAMES+=("$1")
STEP_STATUSES+=("$2")
}
# Write handoff.json on any exit so the skill can always read it
write_handoff() {
local handoff_dir="$LOGS_DIR/setup-migration"
mkdir -p "$handoff_dir"
local has_failures=false
for step_name in "${!STEP_RESULTS[@]}"; do
[ "${STEP_RESULTS[$step_name]}" = "failed" ] && has_failures=true
local i
for ((i=0; i<${#STEP_NAMES[@]}; i++)); do
[ "${STEP_STATUSES[$i]}" = "failed" ] && has_failures=true
done
local overall="success"
@@ -44,8 +55,10 @@ write_handoff() {
[ -n "$ABORTED_AT" ] && overall="failed"
local steps_json="{"
for step_name in "${!STEP_RESULTS[@]}"; do
steps_json="${steps_json}\"${step_name}\": {\"status\": \"${STEP_RESULTS[$step_name]}\", \"log\": \"logs/migrate-steps/${step_name}.log\"},"
for ((i=0; i<${#STEP_NAMES[@]}; i++)); do
local n="${STEP_NAMES[$i]}"
local s="${STEP_STATUSES[$i]}"
steps_json="${steps_json}\"${n}\": {\"status\": \"${s}\", \"log\": \"logs/migrate-steps/${n}.log\"},"
done
steps_json="${steps_json%,}}"
@@ -245,8 +258,8 @@ export NANOCLAW_V2_PATH="$PROJECT_ROOT"
# ─── run_step helper ─────────────────────────────────────────────────────
# Runs a TypeScript migration step, captures output, reports success/failure.
# Track step outcomes for handoff.json
declare -A STEP_RESULTS
# Step outcomes are tracked via record_step() into STEP_NAMES/STEP_STATUSES
# (defined above, near write_handoff).
run_step() {
local name=$1 label=$2 script=$3
@@ -258,7 +271,7 @@ run_step() {
result=$(grep '^OK:' "$raw" | head -1 || true)
step_ok "$label $(dim "$result")"
log "$name: $result"
STEP_RESULTS[$name]="success"
record_step "$name" "success"
# Surface partial errors (rows skipped due to parse/lookup failures)
# even when the step exited successfully — they're easy to miss in the
# raw log and have caused silent migrations before.
@@ -276,7 +289,7 @@ run_step() {
reason=$(grep '^SKIPPED:' "$raw" | head -1 | sed 's/^SKIPPED://')
step_skip "$label $(dim "($reason)")"
log "$name: skipped ($reason)"
STEP_RESULTS[$name]="skipped"
record_step "$name" "skipped"
else
step_fail "$label"
echo
@@ -285,7 +298,7 @@ run_step() {
done
echo
log "$name: FAILED (see $raw)"
STEP_RESULTS[$name]="failed"
record_step "$name" "failed"
fi
}
@@ -359,10 +372,10 @@ else
STATUS_LINE=$(grep '^STATUS:' "$STEP_LOG" | head -1 | sed 's/^STATUS: *//')
if [ "$STATUS_LINE" = "already-installed" ]; then
step_skip "Install $ch $(dim "(already installed)")"
STEP_RESULTS[$STEP_NAME]="skipped"
record_step "$STEP_NAME" "skipped"
else
step_ok "Install $ch"
STEP_RESULTS[$STEP_NAME]="success"
record_step "$STEP_NAME" "success"
fi
log "install-$ch: $STATUS_LINE"
else
@@ -371,12 +384,12 @@ else
echo " $(dim "$line")"
done
log "install-$ch: FAILED (see $STEP_LOG)"
STEP_RESULTS[$STEP_NAME]="failed"
record_step "$STEP_NAME" "failed"
fi
else
step_skip "Install $ch $(dim "(no install script)")"
log "install-$ch: no install script"
STEP_RESULTS[$STEP_NAME]="failed"
record_step "$STEP_NAME" "failed"
fi
done
fi
@@ -401,11 +414,11 @@ else
if bash setup/install-docker.sh > "$DOCKER_LOG" 2>&1; then
hash -r 2>/dev/null || true
step_ok "Docker installed"
STEP_RESULTS["3a-docker"]="success"
record_step "3a-docker" "success"
log "Docker: installed"
else
step_fail "Docker install failed $(dim "(see $DOCKER_LOG)")"
STEP_RESULTS["3a-docker"]="failed"
record_step "3a-docker" "failed"
log "Docker: FAILED"
fi
fi
@@ -426,16 +439,16 @@ elif command -v docker >/dev/null 2>&1; then
if pnpm exec tsx setup/index.ts --step onecli > "$ONECLI_LOG" 2>"$ONECLI_ERR"; then
step_ok "OneCLI ready"
ONECLI_OK=true
STEP_RESULTS["3b-onecli"]="success"
record_step "3b-onecli" "success"
log "OneCLI: installed/configured"
else
step_fail "OneCLI setup failed $(dim "(see $ONECLI_LOG)")"
STEP_RESULTS["3b-onecli"]="failed"
record_step "3b-onecli" "failed"
log "OneCLI: FAILED"
fi
else
step_fail "OneCLI needs Docker $(dim "(install Docker first)")"
STEP_RESULTS["3b-onecli"]="failed"
record_step "3b-onecli" "failed"
log "OneCLI: skipped (no Docker)"
fi
@@ -449,11 +462,11 @@ elif [ "$ONECLI_OK" = "true" ]; then
AUTH_ERR="$STEPS_DIR/3c-auth.err"
if pnpm exec tsx setup/index.ts --step auth > "$AUTH_LOG" 2>"$AUTH_ERR"; then
step_ok "Anthropic credential registered"
STEP_RESULTS["3c-auth"]="success"
record_step "3c-auth" "success"
log "Anthropic credential: registered via auth step"
else
step_fail "Auth setup failed $(dim "(see $AUTH_LOG)")"
STEP_RESULTS["3c-auth"]="failed"
record_step "3c-auth" "failed"
log "Anthropic credential: FAILED"
fi
else
@@ -494,11 +507,11 @@ if command -v docker >/dev/null 2>&1; then
BUILD_LOG="$STEPS_DIR/3e-container-build.log"
if bash container/build.sh > "$BUILD_LOG" 2>&1; then
step_ok "Container image built"
STEP_RESULTS["3e-build"]="success"
record_step "3e-build" "success"
log "Container build: success"
else
step_fail "Container build failed"
STEP_RESULTS["3e-build"]="failed"
record_step "3e-build" "failed"
tail -10 "$BUILD_LOG" 2>/dev/null | while IFS= read -r line; do
echo " $(dim "$line")"
done
@@ -506,7 +519,7 @@ if command -v docker >/dev/null 2>&1; then
fi
else
step_fail "Docker not available — cannot build container"
STEP_RESULTS["3e-build"]="failed"
record_step "3e-build" "failed"
log "Container build: skipped (no Docker)"
fi