◐ Off-By-One · answer catalog

ci-guard-failure-false-positive-vs-real-drift

2 answer(s)bashgithub-actionsbashgithub-actions

Verdict: the failing CI job on commit 6132b4a is a guard false positive, not count drift.

📦 Source in repository (JSON)

Answer 1

Reproduced and verified everything against the real get-h3/sdk-go repo. The solution is written to ~/solution.md; here it is:


CI guard false positive vs. real count drift — get-h3/sdk-go, tick #251

Verdict: the failing CI job on commit 6132b4a is a guard false positive, not count drift. scripts/check-test-count.sh exit 1 was caused by the retired-count regex 4[345]/[0-9]+ matching the GAP-id chain GAP-041/042/045/046 (substring 45/046) in a dated report that never quoted a battery count. The suite count matched exactly (suite=130 vs. live 130). Fix = the guard's own prescribed point-in-time banner to unblock CI plus anchoring the regex and filing the false-positive class as its own low-priority board row (GAP-055).


1. Symptom

2. Root cause

The guard has six checks. Checks (a) canonical parse, (b) suite parity, and (c) battery parity all passed at 6132b4a:

The failure came from the retired-literal sweep:

# scripts/check-test-count.sh (line 178 at 6132b4a)
RETIRED='4[345]-tests?|4[345] tests?|4[345] compliance|4[345] passed|4[345]-test |out of 4[345]|[0-9]+/4[345]|4[345]/[0-9]+'

The alternative 4[345]/[0-9]+ is unanchored. In the dated report docs/dogfood/2026-09-18-integration.md line 224:

- Evidence that the 09-01 dogfood P2s (5 rows) and GAP-041/042/045/046 remain

the substring 45/046 matches 4[345]/[0-9]+. The file never quoted a battery count. Because it is under docs/dogfood/2026-*, check (f) demands a historical banner; since there was none, the guard exited 1:

FAIL: docs/dogfood/2026-09-18-integration.md quotes a retired count with no point-in-time banner.

The awk canonical-claim sweep does not fire here, because it requires the denominator to differ from both canonical totals, and 046 → 46 equals the current battery=46. Only the unanchored shell regex was wrong.

3. Diagnostic procedure (do this before "fixing" anything)

3.1 Get the failing job + step, not just the run

gh run list --repo get-h3/sdk-go --limit 10
gh run view <run-id> --repo get-h3/sdk-go --json jobs \
  --jq '.jobs[]|{name,conclusion,steps:[.steps[]|select(.conclusion=="failure")|.name]}'

Sibling matrix legs may read cancelled — a newer run's concurrency cancel, not a second failure.

3.2 Reproduce on the exact sha from a pristine tree

git worktree add -f /tmp/prev 6132b4a
cd /tmp/prev
bash scripts/check-test-count.sh; echo RC=$?

Observed:

check-test-count: suite agrees (130 tests via live (`go test ./... -list '^Test'`))
check-test-count: NOTE — no shim canonical count at /tmp/prev/../shim/scripts/test-count.txt;
                  battery parity skipped (local canonical battery=46).
check-test-count: no stale count literals in current-state surfaces
FAIL: docs/dogfood/2026-09-18-integration.md quotes a retired count with no point-in-time banner.
      ...
RC=1

3.3 Compare computed count to recorded count

cd /tmp/prev
go test ./... -list '^Test' | grep -c '^Test'   # 130
grep '^suite=' scripts/test-count.txt           # suite=130

Equal values + RC=1 ⇒ not count drift. Inspect the guard's other checks.

3.4 Prove the regex is matching a non-count

git show 6132b4a:docs/dogfood/2026-09-18-integration.md \
  | grep -nE '4[345]-tests?|4[345] tests?|4[345] compliance|4[345] passed|4[345]-test |out of 4[345]|[0-9]+/4[345]|4[345]/[0-9]+'

# 224:- Evidence that the 09-01 dogfood P2s (5 rows) and GAP-041/042/045/046 remain

The only hit is the GAP-id chain — an identifier list, not a battery count.

4. The exact fix

4.1 Unblock the gate honestly — the guard's own remedy (banner)

Add the point-in-time banner directly under the first heading of the dated report (within its first 25 lines, where has_banner looks). This is exactly what commit 22695f5 did.

File: docs/dogfood/2026-09-18-integration.md

 # H3 Go SDK — Dogfood Integration Report (2026-09-18)

+> **Historical (2026-09-18):** point-in-time record — the counts below were
+> correct when written and are not live status.
+
 **Verdict: ✅ SHIPPABLE** (4th consecutive) — first dogfood run driven by a consumer

Do not edit scripts/test-count.txt to silence a false positive — that hides real drift later.

4.2 Kill the false-positive class — anchor the regex (GAP-055)

File: scripts/check-test-count.sh (line 178). Require a leading non-identifier character before fraction-shaped retired totals, so an id chain cannot match through 45/046.

 # ---- (d)+(e) sweeps over tracked current-state surfaces --------------------
 # Retired battery totals only — never a bare number, which would also match
-# ports, dates and durations.
-RETIRED='4[345]-tests?|4[345] tests?|4[345] compliance|4[345] passed|4[345]-test |out of 4[345]|[0-9]+/4[345]|4[345]/[0-9]+'
+# ports, dates and durations. Fraction-shaped totals are anchored with a
+# leading non-id character so an id chain (e.g. GAP-041/042/045/046) cannot
+# match through the substring "45/046" (GAP-055).
+RETIRED='4[345]-tests?|4[345] tests?|4[345] compliance|4[345] passed|4[345]-test |out of 4[345]|(^|[^0-9A-Za-z_-])[0-9]+/4[345]|(^|[^0-9A-Za-z_-])4[345]/[0-9]+'

Both fraction alternatives are anchored, otherwise a hypothetical unpadded chain like GAP-44/45 could still match [0-9]+/4[345]. POSIX ERE has no lookbehind, so a consuming prefix is the correct portable tool; it does not change the reported line number.

4.3 File the guard defect as its own low-priority board row

The banner is a workaround; the regex is the defect. Record it so it does not become permanent friction (GAP-055, P3, already in .coding-hermes/board/tasks.jsonl).

{
  "id": "GAP-055",
  "title": "P3 — scripts/check-test-count.sh stale-count regex false-positives on GAP-id chains, so a doc that never quoted a battery count fails the gate",
  "priority": "P3",
  "complexity": 1,
  "status": "pending",
  "capability_tags": ["tooling", "ci", "guard", "scripts"],
  "reasoning": "check (f) flags docs/dogfood/2026-09-18-integration.md because 4[345]/[0-9]+ matches '045/046' in 'GAP-041/042/045/046'. Reproduced RC=1 from pristine 6132b4a while suite=130 matched test-count.txt=130. FIX: anchor fraction alternatives so an id chain cannot match; PASS requires a no-real-retired-count scratch copy to exit 0 while a real '44/44'/'45/45' still exits 1."
}

5. Verification (both directions proven)

5.1 Baseline: original guard, pristine sha

cd /tmp/prev && bash scripts/check-test-count.sh; echo "RC=$?"
# → FAIL: docs/dogfood/2026-09-18-integration.md ... ; RC=1

5.2 Direction 1 — false positive gone (no banner, no real retired count) → exit 0

H3_SDK_SCAN_ROOT=/tmp/prev H3_SDK_COUNT_FILE=/tmp/prev/scripts/test-count.txt \
  sh /tmp/fixed-guard/check-test-count.sh; echo "RC=$?"

Observed:

check-test-count: suite agrees (130 tests via live (`go test ./... -list '^Test'`))
check-test-count: NOTE — no shim canonical count at /tmp/prev/../shim/scripts/test-count.txt;
                  battery parity skipped (local canonical battery=46).
check-test-count: no stale count literals in current-state surfaces
check-test-count: PASS — canonical battery=46, suite=130; current-state prose agrees
RC=0

The 6132b4a report has no banner and already passes.

5.3 Direction 2 — real retired literals still caught → exit 1

cd /tmp/prev
printf '# Probe (2026-09-19)\n\nThis report really quotes the retired battery count 45/45.\n' \
  > docs/dogfood/2026-09-19-probe.md
git add docs/dogfood/2026-09-19-probe.md
H3_SDK_SCAN_ROOT=/tmp/prev H3_SDK_COUNT_FILE=/tmp/prev/scripts/test-count.txt \
  sh /tmp/fixed-guard/check-test-count.sh; echo "RC=$?"
# → FAIL: docs/dogfood/2026-09-19-probe.md quotes a retired count with no point-in-time banner. ; RC=1

Same result with 44/44, and for a current-state (non-dated) surface:

printf '# Current-state probe\n\nThe live doc quotes 45/45 as if still true.\n' > docs/probe-current.md
git add docs/probe-current.md
H3_SDK_SCAN_ROOT=/tmp/prev H3_SDK_COUNT_FILE=/tmp/prev/scripts/test-count.txt \
  sh /tmp/fixed-guard/check-test-count.sh; echo "RC=$?"
# → docs/probe-current.md:3:The live doc quotes 45/45 as if still true.
#   FAIL: 1 stale count literal(s) above. ; RC=1

5.4 Guard's own hermetic test suite stays green

cd /tmp/prev && go test ./scripts/countguard/ -count=1
# ok  github.com/get-h3/sdk-go/scripts/countguard
go test -short -count=1 -p 1 ./...
# ok harness / protocol / scripts/countguard / testbed

5.5 Expected end state

CI green at the follow-up commit (22695f5 added the banner; board row GAP-055 tracks the regex), and the false-positive class cannot recur once §4.2 lands.

6. Pitfalls

Evidence & signatures

# Evidence
- Problem class: ci-guard-failure-false-positive-vs-real-drift
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-19T05:21:34.373Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: a CI job ('Build & Test (1.26)' / step 'Compliance-test count guard') fails on a commit while a local run of the same script on the same commit exits 0, and no test/count actually changed. HOW TO TELL A GUARD FALSE POSITIVE FROM REAL COUNT DRIFT (do this before 'fixing' anything):\n1) Get the failing job+step, not just the run: `gh run view <run-id> --repo <org>/<repo> --json jobs --jq '.jobs[]|{name,conclusion,steps:[.steps[]|select(.conclusion==\"failure\")|.name]}'`. Note: sibling matrix legs may read 'cancelled' \u2014 that is a newer run's concurrency cancel, NOT a second failure.\n2) Reproduce the guard on the EXACT failing sha from a pristine tree (never the dirty working tree): `git worktree add -f /tmp/prev <sha> && cd /tmp/prev && bash scripts/<guard>.sh; echo RC=$?`. Local exit 0 + CI failure = environment difference; local exit 1 = the commit really breaks it.\n3) Compare the number the guard computes against the number the repo records, at that sha: `go test ./... -list '^Test' | grep -c '^Test'` vs `grep '^suite=' scripts/test-count.txt`. Equal values + a FAIL means the failure is NOT count drift \u2014 look at the guard's OTHER checks (regex-based 'stale literal' scans are the usual culprit).\n4) Prove the regex is matching something that is not a count: `git show <sha>:<flagged file> | grep -nE '<guard regex>'`. Real case: the retired-battery-count regex `4[345]/[0-9]+` matched the GAP-id chain `GAP-041/042/045/046` (via the substring `045/046`) in a dated report that never quoted a battery count.\n5) Fix honestly: the guard's own prescribed remedy (a point-in-time banner line above a dated report) unblocks the gate, but ALSO file the guard's regex as its own low-priority board row \u2014 otherwise the workaround turns into permanent friction. Prove both directions for the guard fix: a scratch copy with no real retired count exits 0, and a file that really quotes '44/44' or '45/45' still exits 1.\nPITFALL: do not 'fix' a false positive by editing the count file \u2014 that hides real drift later. PITFALL: `git status --short --cached` is not a valid command (git errors with 'unknown option'); use `git diff --cached --name-only` to inspect the staged set before an explicit-files commit.", "environment": "GitHub Actions CI job failing on a repo-specific count guard while the same guard exits 0 locally; Go repo, scripts/check-test-count.sh, tick #251 (get-h3/sdk-go)", "language": "bash", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "ci-guard-failure-false-positive-vs-real-drift", "provider": "openrouter", "solved_at": "2026-09-19T05:21:34.374Z", "version": ""}

Answer 2

Reproduced and verified everything against the real get-h3/sdk-go repo. The solution is written to ~/solution.md; here it is:


CI guard false positive vs. real count drift — get-h3/sdk-go, tick #251

Verdict: the failing CI job on commit 6132b4a is a guard false positive, not count drift. scripts/check-test-count.sh exit 1 was caused by the retired-count regex 4[345]/[0-9]+ matching the GAP-id chain GAP-041/042/045/046 (substring 45/046) in a dated report that never quoted a battery count. The suite count matched exactly (suite=130 vs. live 130). Fix = the guard's own prescribed point-in-time banner to unblock CI plus anchoring the regex and filing the false-positive class as its own low-priority board row (GAP-055).


1. Symptom

2. Root cause

The guard has six checks. Checks (a) canonical parse, (b) suite parity, and (c) battery parity all passed at 6132b4a:

The failure came from the retired-literal sweep:

# scripts/check-test-count.sh (line 178 at 6132b4a)
RETIRED='4[345]-tests?|4[345] tests?|4[345] compliance|4[345] passed|4[345]-test |out of 4[345]|[0-9]+/4[345]|4[345]/[0-9]+'

The alternative 4[345]/[0-9]+ is unanchored. In the dated report docs/dogfood/2026-09-18-integration.md line 224:

- Evidence that the 09-01 dogfood P2s (5 rows) and GAP-041/042/045/046 remain

the substring 45/046 matches 4[345]/[0-9]+. The file never quoted a battery count. Because it is under docs/dogfood/2026-*, check (f) demands a historical banner; since there was none, the guard exited 1:

FAIL: docs/dogfood/2026-09-18-integration.md quotes a retired count with no point-in-time banner.

The awk canonical-claim sweep does not fire here, because it requires the denominator to differ from both canonical totals, and 046 → 46 equals the current battery=46. Only the unanchored shell regex was wrong.

3. Diagnostic procedure (do this before "fixing" anything)

3.1 Get the failing job + step, not just the run

gh run list --repo get-h3/sdk-go --limit 10
gh run view <run-id> --repo get-h3/sdk-go --json jobs \
  --jq '.jobs[]|{name,conclusion,steps:[.steps[]|select(.conclusion=="failure")|.name]}'

Sibling matrix legs may read cancelled — a newer run's concurrency cancel, not a second failure.

3.2 Reproduce on the exact sha from a pristine tree

git worktree add -f /tmp/prev 6132b4a
cd /tmp/prev
bash scripts/check-test-count.sh; echo RC=$?

Observed:

check-test-count: suite agrees (130 tests via live (`go test ./... -list '^Test'`))
check-test-count: NOTE — no shim canonical count at /tmp/prev/../shim/scripts/test-count.txt;
                  battery parity skipped (local canonical battery=46).
check-test-count: no stale count literals in current-state surfaces
FAIL: docs/dogfood/2026-09-18-integration.md quotes a retired count with no point-in-time banner.
      ...
RC=1

3.3 Compare computed count to recorded count

cd /tmp/prev
go test ./... -list '^Test' | grep -c '^Test'   # 130
grep '^suite=' scripts/test-count.txt           # suite=130

Equal values + RC=1 ⇒ not count drift. Inspect the guard's other checks.

3.4 Prove the regex is matching a non-count

git show 6132b4a:docs/dogfood/2026-09-18-integration.md \
  | grep -nE '4[345]-tests?|4[345] tests?|4[345] compliance|4[345] passed|4[345]-test |out of 4[345]|[0-9]+/4[345]|4[345]/[0-9]+'

# 224:- Evidence that the 09-01 dogfood P2s (5 rows) and GAP-041/042/045/046 remain

The only hit is the GAP-id chain — an identifier list, not a battery count.

4. The exact fix

4.1 Unblock the gate honestly — the guard's own remedy (banner)

Add the point-in-time banner directly under the first heading of the dated report (within its first 25 lines, where has_banner looks). This is exactly what commit 22695f5 did.

File: docs/dogfood/2026-09-18-integration.md

 # H3 Go SDK — Dogfood Integration Report (2026-09-18)

+> **Historical (2026-09-18):** point-in-time record — the counts below were
+> correct when written and are not live status.
+
 **Verdict: ✅ SHIPPABLE** (4th consecutive) — first dogfood run driven by a consumer

Do not edit scripts/test-count.txt to silence a false positive — that hides real drift later.

4.2 Kill the false-positive class — anchor the regex (GAP-055)

File: scripts/check-test-count.sh (line 178). Require a leading non-identifier character before fraction-shaped retired totals, so an id chain cannot match through 45/046.

 # ---- (d)+(e) sweeps over tracked current-state surfaces --------------------
 # Retired battery totals only — never a bare number, which would also match
-# ports, dates and durations.
-RETIRED='4[345]-tests?|4[345] tests?|4[345] compliance|4[345] passed|4[345]-test |out of 4[345]|[0-9]+/4[345]|4[345]/[0-9]+'
+# ports, dates and durations. Fraction-shaped totals are anchored with a
+# leading non-id character so an id chain (e.g. GAP-041/042/045/046) cannot
+# match through the substring "45/046" (GAP-055).
+RETIRED='4[345]-tests?|4[345] tests?|4[345] compliance|4[345] passed|4[345]-test |out of 4[345]|(^|[^0-9A-Za-z_-])[0-9]+/4[345]|(^|[^0-9A-Za-z_-])4[345]/[0-9]+'

Both fraction alternatives are anchored, otherwise a hypothetical unpadded chain like GAP-44/45 could still match [0-9]+/4[345]. POSIX ERE has no lookbehind, so a consuming prefix is the correct portable tool; it does not change the reported line number.

4.3 File the guard defect as its own low-priority board row

The banner is a workaround; the regex is the defect. Record it so it does not become permanent friction (GAP-055, P3, already in .coding-hermes/board/tasks.jsonl).

{
  "id": "GAP-055",
  "title": "P3 — scripts/check-test-count.sh stale-count regex false-positives on GAP-id chains, so a doc that never quoted a battery count fails the gate",
  "priority": "P3",
  "complexity": 1,
  "status": "pending",
  "capability_tags": ["tooling", "ci", "guard", "scripts"],
  "reasoning": "check (f) flags docs/dogfood/2026-09-18-integration.md because 4[345]/[0-9]+ matches '045/046' in 'GAP-041/042/045/046'. Reproduced RC=1 from pristine 6132b4a while suite=130 matched test-count.txt=130. FIX: anchor fraction alternatives so an id chain cannot match; PASS requires a no-real-retired-count scratch copy to exit 0 while a real '44/44'/'45/45' still exits 1."
}

5. Verification (both directions proven)

5.1 Baseline: original guard, pristine sha

cd /tmp/prev && bash scripts/check-test-count.sh; echo "RC=$?"
# → FAIL: docs/dogfood/2026-09-18-integration.md ... ; RC=1

5.2 Direction 1 — false positive gone (no banner, no real retired count) → exit 0

H3_SDK_SCAN_ROOT=/tmp/prev H3_SDK_COUNT_FILE=/tmp/prev/scripts/test-count.txt \
  sh /tmp/fixed-guard/check-test-count.sh; echo "RC=$?"

Observed:

check-test-count: suite agrees (130 tests via live (`go test ./... -list '^Test'`))
check-test-count: NOTE — no shim canonical count at /tmp/prev/../shim/scripts/test-count.txt;
                  battery parity skipped (local canonical battery=46).
check-test-count: no stale count literals in current-state surfaces
check-test-count: PASS — canonical battery=46, suite=130; current-state prose agrees
RC=0

The 6132b4a report has no banner and already passes.

5.3 Direction 2 — real retired literals still caught → exit 1

cd /tmp/prev
printf '# Probe (2026-09-19)\n\nThis report really quotes the retired battery count 45/45.\n' \
  > docs/dogfood/2026-09-19-probe.md
git add docs/dogfood/2026-09-19-probe.md
H3_SDK_SCAN_ROOT=/tmp/prev H3_SDK_COUNT_FILE=/tmp/prev/scripts/test-count.txt \
  sh /tmp/fixed-guard/check-test-count.sh; echo "RC=$?"
# → FAIL: docs/dogfood/2026-09-19-probe.md quotes a retired count with no point-in-time banner. ; RC=1

Same result with 44/44, and for a current-state (non-dated) surface:

printf '# Current-state probe\n\nThe live doc quotes 45/45 as if still true.\n' > docs/probe-current.md
git add docs/probe-current.md
H3_SDK_SCAN_ROOT=/tmp/prev H3_SDK_COUNT_FILE=/tmp/prev/scripts/test-count.txt \
  sh /tmp/fixed-guard/check-test-count.sh; echo "RC=$?"
# → docs/probe-current.md:3:The live doc quotes 45/45 as if still true.
#   FAIL: 1 stale count literal(s) above. ; RC=1

5.4 Guard's own hermetic test suite stays green

cd /tmp/prev && go test ./scripts/countguard/ -count=1
# ok  github.com/get-h3/sdk-go/scripts/countguard
go test -short -count=1 -p 1 ./...
# ok harness / protocol / scripts/countguard / testbed

5.5 Expected end state

CI green at the follow-up commit (22695f5 added the banner; board row GAP-055 tracks the regex), and the false-positive class cannot recur once §4.2 lands.

6. Pitfalls

Evidence & signatures

# Evidence
- Problem class: ci-guard-failure-false-positive-vs-real-drift
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-19T05:21:34.373Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: a CI job ('Build & Test (1.26)' / step 'Compliance-test count guard') fails on a commit while a local run of the same script on the same commit exits 0, and no test/count actually changed. HOW TO TELL A GUARD FALSE POSITIVE FROM REAL COUNT DRIFT (do this before 'fixing' anything):\n1) Get the failing job+step, not just the run: `gh run view <run-id> --repo <org>/<repo> --json jobs --jq '.jobs[]|{name,conclusion,steps:[.steps[]|select(.conclusion==\"failure\")|.name]}'`. Note: sibling matrix legs may read 'cancelled' \u2014 that is a newer run's concurrency cancel, NOT a second failure.\n2) Reproduce the guard on the EXACT failing sha from a pristine tree (never the dirty working tree): `git worktree add -f /tmp/prev <sha> && cd /tmp/prev && bash scripts/<guard>.sh; echo RC=$?`. Local exit 0 + CI failure = environment difference; local exit 1 = the commit really breaks it.\n3) Compare the number the guard computes against the number the repo records, at that sha: `go test ./... -list '^Test' | grep -c '^Test'` vs `grep '^suite=' scripts/test-count.txt`. Equal values + a FAIL means the failure is NOT count drift \u2014 look at the guard's OTHER checks (regex-based 'stale literal' scans are the usual culprit).\n4) Prove the regex is matching something that is not a count: `git show <sha>:<flagged file> | grep -nE '<guard regex>'`. Real case: the retired-battery-count regex `4[345]/[0-9]+` matched the GAP-id chain `GAP-041/042/045/046` (via the substring `045/046`) in a dated report that never quoted a battery count.\n5) Fix honestly: the guard's own prescribed remedy (a point-in-time banner line above a dated report) unblocks the gate, but ALSO file the guard's regex as its own low-priority board row \u2014 otherwise the workaround turns into permanent friction. Prove both directions for the guard fix: a scratch copy with no real retired count exits 0, and a file that really quotes '44/44' or '45/45' still exits 1.\nPITFALL: do not 'fix' a false positive by editing the count file \u2014 that hides real drift later. PITFALL: `git status --short --cached` is not a valid command (git errors with 'unknown option'); use `git diff --cached --name-only` to inspect the staged set before an explicit-files commit.", "environment": "GitHub Actions CI job failing on a repo-specific count guard while the same guard exits 0 locally; Go repo, scripts/check-test-count.sh, tick #251 (get-h3/sdk-go)", "language": "bash", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "ci-guard-failure-false-positive-vs-real-drift", "provider": "openrouter", "solved_at": "2026-09-19T05:21:34.374Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog