diff --git a/.claude/verify.sh b/.claude/verify.sh
index 9351158..cd7c9b1 100755
--- a/.claude/verify.sh
+++ b/.claude/verify.sh
@@ -4440,7 +4440,35 @@ c61_mkrepo(){ #
chmod +x "$1/.claude/verify.sh"
}
c61_store(){ printf '%s' "$1/home/.config/agents/verify-trust"; }
-c61_trust(){ HOME="$1/home" ./bin/vstack trust "$1" --yes >/dev/null 2>&1; }
+# Keeps stderr and the exit code instead of discarding them. This check went red on Alpine in CI
+# and nowhere else, and all three lanes reported only "vstack trust failed" because the command
+# was run with `>/dev/null 2>&1` -- a check that knows something is wrong and has thrown away the
+# only sentence saying what. Two reproduction attempts against a byte-identical image failed to
+# make it fail, which is exactly the case where the swallowed stderr is the whole investigation.
+# VSTACK_DIR is set for the same reason HOME is overridden, and the two interact. HOME is
+# redirected so each lane gets its own trust store instead of writing the operator's. That also
+# hides ~/.vstack and the global gitconfig, and bin/vstack resolves its repo from $VSTACK_DIR,
+# then ~/.vstack, then `git rev-parse --show-toplevel`. Under a foreign HOME the first two are
+# gone, and inside the Alpine CI container the third fails too -- actions/checkout writes its
+# safe.directory exemption into the runner HOME's gitconfig, which the override hides, so git
+# refuses the checkout as dubiously owned. bin/vstack then exits 1 before `trust` runs at all.
+#
+# PROVEN: the three lanes reported `no vstack repo found (checked $VSTACK_DIR, ~/.vstack, and
+# this script's git root)`, rc=1, on Alpine only. INFERRED: that the git-root lane failed
+# specifically on dubious ownership; the message does not say which of the three lost. Naming
+# $VSTACK_DIR settles it either way, and it is what the error text itself tells you to do.
+#
+# This is the check's own environment, not a defect in what it measures: a user does not run
+# vstack under a substituted HOME. It cost a red on one platform and three destroyed tags'
+# worth of noise because the invocation discarded stderr.
+c61_last=""
+c61_trust(){
+ c61_last=$(HOME="$1/home" VSTACK_DIR="$PWD" ./bin/vstack trust "$1" --yes 2>&1 >/dev/null)
+ c61_rc=$?
+ [ -n "$c61_last" ] || c61_last="(command printed nothing on stderr)"
+ c61_last="rc=$c61_rc, stderr: $(printf '%s' "$c61_last" | tr '\n' ' ')"
+ return "$c61_rc"
+}
# Lane 2 -- positive control. A repo whose gate runs ./scripts/ci.sh must get ci.sh recorded, and
# that path is in no hardcoded list anywhere: it can only come from reading verify.sh.
@@ -4450,7 +4478,7 @@ if c61_trust "$c61_a"; then
grep -q '/scripts/ci\.sh$' "$(c61_store "$c61_a")" \
|| c61_errs="$c61_errs\nvstack trust did not record scripts/ci.sh, a path only verify.sh names -- the scan is not reading the gate"
else
- c61_errs="$c61_errs\nvstack trust failed on a synthetic repo naming ./scripts/ci.sh"
+ c61_errs="$c61_errs\nvstack trust failed on a synthetic repo naming ./scripts/ci.sh -- $c61_last"
fi
# Lane 3 -- negative control. A gate that runs nothing else gets one entry. Without this, lane 2
@@ -4463,13 +4491,15 @@ if c61_trust "$c61_b"; then
[ "$c61_n" = 1 ] \
|| c61_errs="$c61_errs\nvstack trust recorded $c61_n entries for a gate that names no other script; it should record 1"
else
- c61_errs="$c61_errs\nvstack trust failed on a synthetic repo naming nothing"
+ c61_errs="$c61_errs\nvstack trust failed on a synthetic repo naming nothing -- $c61_last"
fi
# Lane 4 -- this repository. The two files the narrow writer missed, named because they are the
# ones verify.sh actually executes, not because they are a general category.
c61_c="$c61_d/c"; mkdir -p "$c61_c/home"
-if HOME="$c61_c/home" ./bin/vstack trust "$PWD" --yes >/dev/null 2>&1; then
+if c61_last=$(HOME="$c61_c/home" VSTACK_DIR="$PWD" ./bin/vstack trust "$PWD" --yes 2>&1 >/dev/null); c61_rc=$?; \
+ c61_last="rc=$c61_rc, stderr: $(printf '%s' "${c61_last:-(nothing on stderr)}" | tr '\n' ' ')"; \
+ [ "$c61_rc" -eq 0 ]; then
for c61_f in install.sh overlay.sh; do
grep -q "/$c61_f\$" "$(c61_store "$c61_c")" \
|| c61_errs="$c61_errs\nthe trust store for this repo does not record $c61_f, which .claude/verify.sh executes"
@@ -4477,7 +4507,7 @@ if HOME="$c61_c/home" ./bin/vstack trust "$PWD" --yes >/dev/null 2>&1; then
c61_tot=$(grep -c . "$(c61_store "$c61_c")" || true)
else
c61_tot=0
- c61_errs="$c61_errs\nvstack trust failed on this repository"
+ c61_errs="$c61_errs\nvstack trust failed on this repository -- $c61_last"
fi
rm -rf "$c61_d"
diff --git a/tests/container-matrix.sh b/tests/container-matrix.sh
index 1279677..f360e5e 100755
--- a/tests/container-matrix.sh
+++ b/tests/container-matrix.sh
@@ -81,8 +81,8 @@ fi
cat > "$ROOT/bootstrap-apk.sh" <<'EOF'
#!/bin/sh
set -e
-apk add --no-cache bash git jq curl ca-certificates shellcheck 2>&1 || \
- apk add --no-cache bash git jq curl ca-certificates 2>&1
+apk add --no-cache bash git jq curl ca-certificates shellcheck python3 2>&1 || \
+ apk add --no-cache bash git jq curl ca-certificates python3 2>&1
EOF
cat > "$ROOT/bootstrap-apt.sh" <<'EOF'
@@ -90,8 +90,8 @@ cat > "$ROOT/bootstrap-apt.sh" <<'EOF'
set -e
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq
-apt-get install -y -qq --no-install-recommends git jq curl ca-certificates shellcheck || \
- apt-get install -y -qq --no-install-recommends git jq curl ca-certificates
+apt-get install -y -qq --no-install-recommends git jq curl ca-certificates shellcheck python3 || \
+ apt-get install -y -qq --no-install-recommends git jq curl ca-certificates python3
EOF
# --- the in-container assertion runner ----------------------------------------------------------
@@ -193,10 +193,20 @@ if [ -x "$DOCTOR" ]; then
# of .claude/verify.sh fails if either stops reading it or if the list grows.
_pt_file="/work/repo/tests/pretag-findings.sh"
if [ -f "$_pt_file" ]; then . "$_pt_file"; else PRETAG_ALLOWED_FINDING=""; fi
- _dr_x=$(grep '✖' /tmp/doctor.out | grep -vF "$PRETAG_ALLOWED_FINDING")
+ # bin/doctor's closing line is `DRIFT ✖` for ANY non-zero FAIL count -- it is the summary
+ # banner, not a drift finding, and it name-collides with one. Left in the residual it made the
+ # carve-out unreachable by construction: with the allowed finding as the SOLE failure, _dr_x
+ # was still non-empty, holding a line with no diagnostic content, and every image reported
+ # `doctor exit=1; DRIFT ✖`. Dropping it does not soften anything -- each individual finding
+ # prints its own ✖ line, so a second, real failure still lands in the residual. What is added
+ # below is the guard that the banner was accidentally providing: the allowed finding must
+ # actually be present, or a doctor that failed while printing no findings at all would pass.
+ _dr_x=$(grep '✖' /tmp/doctor.out | grep -vF "$PRETAG_ALLOWED_FINDING" | grep -vxF 'DRIFT ✖')
if [ "$rc" -eq 0 ]; then res PASS "2-doctor-exit0" "doctor exit=$rc"
elif [ -z "$_pt_file" ] || [ ! -f "$_pt_file" ]; then
res FAIL "2-doctor-exit0" "doctor exit=$rc and $_pt_file is missing, so the pre-tag carve-out cannot be applied; $(grep '✖' /tmp/doctor.out | tr '\n' ';')"
+ elif ! grep -qF "$PRETAG_ALLOWED_FINDING" /tmp/doctor.out; then
+ res FAIL "2-doctor-exit0" "doctor exit=$rc but did not report '$PRETAG_ALLOWED_FINDING' at all, so the carve-out is excusing a failure it cannot see; $(grep '✖' /tmp/doctor.out | tr '\n' ';')"
elif [ -z "$_dr_x" ]; then
res PASS "2-doctor-exit0" "doctor exit=$rc, only finding is '$PRETAG_ALLOWED_FINDING', a note in a commit-triggered lane"
else res FAIL "2-doctor-exit0" "doctor exit=$rc; $(printf '%s' "$_dr_x" | tr '\n' ';')"; fi