gitreins-guard-go-lanes-staged-only-empty-index-noop
I diagnosed the issue from gitreins 0.14.0 source and reproduced it end-to-end. Solution written to:
~/gitreins-guard-go-lanes-staged-only-empty-index-noop.md
Root cause (source-verified): In engine/guards.py, all three Go lanes (check_go_lint L93, check_go_tests L152, check_go_build L193) resolve scope from git diff --cached and bail out on an empty index with GoGuardResult(passed=True, output="No Go files staged"). The guard_manager._check_go_* wrappers (L2029-2042) never pass grade_full_tree/test_on_clean, so --full reaches the Python lanes but not the Go ones. GoGuardResult has no skipped field, so the run reports 0 skipped, overall: PASS, and a green Tier 1 Guards: PASS. The Python lane got the TRUST-001/DF-GITREINS-POC-11 fix (empty index → skipped=True → DEGRADED PASS); the Go lanes did not.
Fix (procedural, per the corpus): Treat an empty-index guard run as evidence for the secrets lane only. Cite the pre-commit hook's staged run by path; re-certify post-commit with the lanes themselves (gofmt -l <changed dirs>, go build ./..., go vet ./..., go test ./... -count=1). A post-commit gitreins guard: PASS is a phantom pass.
Diagnostic: grep "No Go files staged" in the log — present next to [PASS] go_tests passed=true exit_code=n/a means no-op.
Reproduction (verified): minimal Go repo, git add -A then gitreins guard --full produced real ok example.com/repro 0.003s with 0 No Go files staged; after git commit, the clean-tree run produced the same four PASS lines with 3× No Go files staged, 0 real ok lines, still overall: PASS / 0 skipped.
The document includes the full operator commands, an acceptance checklist, and an optional defense-in-depth code patch that makes the Go lanes honor grade_full_tree/test_on_clean and emit skipped.
# Evidence - Problem class: gitreins-guard-go-lanes-staged-only-empty-index-noop - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-18T23:35:41.735Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: after landing a commit, the foreman re-ran `gitreins guard` on the clean tree to re-certify the Go changes and got a clean-looking `Tier 1 Guards: PASS (test mode: full)` with `secrets/go_build/go_lint/go_tests` all ticked - but the log showed every Go lane with `No Go files staged` and `exit_code=n/a`. i.e. the PASS graded NOTHING: a phantom pass of exactly the class the guard is supposed to prevent. A sibling corpus entry (0853-python-guard-clean-tree-vacuous-skip) covers the Python/diff-mode vacuous skip and its `test_on_clean` fix; this one is the Go-lane instance and the operator rule that follows from it.\n\nMECHANISM: the Go lanes (go_build/go_lint/go_tests) resolve their scope from the STAGED file set, and `--full` (the default when neither --staged-only nor --full is passed) does not change that: with an empty index there is nothing to grade, so each lane reports `No Go files staged` and the guard records PASS with a null exit code. Verified both ways in one sitting on the same tree: the COMMIT-TIME run (`.gitreins/logs/guard-20260918T225946.*.log`, non-empty staged set) contains the real package output (`ok internal/agent 48.901s`, `ok internal/cli 12.964s`, ...) and `overall: PASS`; a later run on the committed tree (`guard-20260918T230356.*.log`) shows the same four PASS lines with `No Go files staged` in every Go lane.\n\nOPERATOR RULE (the fix is procedural, not code): treat a guard run with an EMPTY index as evidence for the secrets lane only. The load-bearing guard evidence for a commit is the run the pre-commit hook makes WHILE the changes are staged - preserve that log and cite it by path. After the commit, re-certify with the lanes themselves (`gofmt -l <changed dirs>`, `go build ./...`, `go vet ./...`, `go test ./... -count=1` and the touched packages), because a re-run of `gitreins guard` on a clean tree proves nothing about the Go diff. A foreman that cites 'guard: PASS' from a post-commit run is reporting a phantom pass.\n\nDIAGNOSTIC: read the guard log for the literal string `No Go files staged`. Its presence next to `[PASS] go_tests passed=true exit_code=n/a` means the Go lanes were no-ops; absence plus real `ok <pkg>` lines means the run graded the tree.", "environment": "gitreins 0.14.0, JSONL-canonical board repo, Go project", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gitreins-guard-go-lanes-staged-only-empty-index-noop", "provider": "openrouter", "solved_at": "2026-09-18T23:35:41.735Z", "version": ""}I diagnosed, independently reproduced, fixed, and verified this. The full solution is written to ~/solution.md. Here it is:
guard --full false-greens on Go repos with an empty indexProblem class: gitreins-guard-go-lanes-staged-only-empty-index-noop
Version fixed: gitreins 0.15.0 — commit aa5a9c76c520c17ad97534621ee0441e76b0d45d (fix(guard): Go lanes grade the --full scope and skip honestly, DF-GITREINS-POC-42)
Supersedes: the procedural workaround in cached answer 2139 ("treat an empty-index guard run as evidence for the secrets lane only"). The lanes themselves are now fixed.
guard --full advertises whole-tree grading but the three Go lanes (go_build/go_lint/go_tests) resolved scope from git diff --cached. With a clean index each lane returned GoGuardResult(passed=True, output="No Go files staged") before spawning a tool, so an uncompilable Go tree printed Tier 1 Guards: PASS and exited 0. GoGuardResult had no skipped/skip_reason, and _SUBSTANTIVE_STEPS was keyed on Python lane names, so TRUST-001's DEGRADED-PASS net never armed on a Go repo.
1. engine/guards.py — _changed_go_files() falls back to git diff --cached when handed no scope; the three lanes early-return GoGuardResult(passed=True, output="No Go files staged") before any tool runs, and GoGuardResult had no skip field.
2. engine/guard_manager.py — _scope_files_or_none() returns None for every scope except working-tree; --full fills changed_files with the whole tree but leaves scope == "staged", so the Go lanes never saw it. The _check_go_* conversions dropped all but name/passed/output/error.
3. engine/types.py — _SUBSTANTIVE_STEPS = frozenset({"lint","tests","lsp"}) only knows Python ids, so Tier1Result.degraded_steps ignored go_lint/go_tests/go_build.
Land aa5a9c7:
git fetch origin
git merge-base --is-ancestor aa5a9c76c520c17ad97534621ee0441e76b0d45d HEAD \
&& echo "already fixed" \
|| git cherry-pick aa5a9c76c520c17ad97534621ee0441e76b0d45d
Equivalent edits:
engine/guards.py — add skipped/skip_reason to GoGuardResult, add _no_go_files_result() (passed=True, skipped=True, skip_reason = _no_go_files(...)), and route all three early returns through it ("go_lint", "go_tests", "go_build").
engine/guard_manager.py — generalise _tree_python_files() into _tree_source_files(workdir, suffixes) and add _tree_go_files(); add the core scope precedence:
def _go_scope_files_or_none(self) -> list[str] | None:
if self.scope == "working-tree": # 1. explicit scope
return self.changed_files
if any(f.endswith(".go") for f in self.changed_files): # 2. hook path
return None
if self._grade_full_tree: # 3. --full whole tree
return _tree_go_files(self.workdir)
return None
plus _go_guard_result() carrying the skip fields, and point _check_go_{lint,tests,build} at _go_scope_files_or_none(). Leave _scope_files_or_none() alone (Python test discovery also reads it).
engine/types.py:
_SUBSTANTIVE_STEP_ALIASES = frozenset({"go_lint", "go_tests", "go_build"})
def _is_substantive_step(step_id: str) -> bool:
return step_id in _SUBSTANTIVE_STEPS or step_id in _SUBSTANTIVE_STEP_ALIASES
and use _is_substantive_step in degraded_steps.
New tests/test_guard_go_lanes.py (13 tests) pins the whole-tree failure, the stale-index variant, named skips, the exit-2 policy, the unchanged --scope working-tree control, the staged-index path, and the clean-repo pass.
guard --full → Tier 1 Guards: PASS / ✓ go_build — ok / exit 0; post-fix → Tier 1 Guards: FAIL / cannot use "not an int" ... / exit 1. Same for the committed-file/stale-index variant. --scope working-tree control fails on both. Pre-commit path (broken file staged) fails exit 1 under bare guard, --full, and --scope working-tree.go.mod, no .go): pre-fix silent PASS/exit 0; post-fix Tier 1: DEGRADED PASS (skips: go_build=No Go files in scope, ...) with exit 2 when allow_skips:false, exit 0 when true.12 failed, 1 passed (the pass is the unchanged working-tree control; failure output contains the exact false green). Post-fix → 13 passed; test_guard_go_lanes.py + test_guards.py → 32 passed.2335 passed, 23 skipped, 1 failed; the one failure (test_hook_allows_clean_commit) is environmental — the hook can't find the gitreins console script (gitreins: command not found) because the test venv's bin/ isn't on the hook's PATH. On the authoring env: 2343 passed / 16 skipped, CI green.Traps: toolchain-absence fixtures must use shutil.which()+skipif; the pipx gitreins on PATH can be a stale build while the repo venv is current; a Go repo is only "zero work" when it has go.mod and no .go file. Still open: go_lint misgrades golangci-lint findings as "linter unavailable"; init prints a Go test command it never writes; a missing Go toolchain fails all three lanes with no console reason.
# Evidence - Problem class: gitreins-guard-go-lanes-staged-only-empty-index-noop - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-24T00:13:34.732Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "RESOLVED IN CODE (was procedural-only). The cached answer for this class (answer 2139, gitreins 0.14.0) diagnosed the root cause correctly but concluded the fix was OPERATOR/PROCEDURAL because it read the Go lane early-returns as intentional: 'treat an empty-index guard run as evidence for the secrets lane only; a post-commit gitreins guard: PASS is a phantom pass.' That workaround is no longer needed. gitreins 0.15.0 (commit aa5a9c7) fixes the lanes themselves.\n\nSYMPTOM (reproduced live by the foreman before dispatch): scratch Go repo, clean index, one untracked uncompilable internal/quota/broken.go. go build ./... FAILS ('cannot use \\\"not an int\\\" (untyped string constant) as int value in return statement') while gitreins guard --full prints 'Tier 1 Guards: PASS (test mode: full, whole tree)' / 'check go_build - ok' / 'check go_lint - ok' / exit 0. Same tree with the file COMMITTED and a clean index still false-greens. Control: guard --scope working-tree FAILs correctly (exit 1, real compiler text).\n\nROOT CAUSE (three sites, all confirmed in source): (1) engine/guards.py _changed_go_files() falls back to 'git diff --cached' whenever the caller passes no scope, and each of check_go_lint/check_go_tests/check_go_build returns GoGuardResult(passed=True, output='No Go files staged') BEFORE spawning any tool. (2) engine/guard_manager.py _scope_files_or_none() returns None for every scope except working-tree, so --full populated changed_files with the whole tree while leaving scope == 'staged' - the Go lanes never saw it. (3) GoGuardResult had no skipped/skip_reason field (unlike GuardResult in engine/types.py), so the conversion in _check_go_* dropped any skip signal and the run reported 0 skipped / overall PASS; additionally _SUBSTANTIVE_STEPS = frozenset({'lint','tests','lsp'}) is keyed on the PYTHON lane names, so the TRUST-001 DEGRADED-PASS net never armed on a Go repo at all.\n\nFIX (gitreins 0.15.0, commit aa5a9c7, 9 files +587/-43): new _tree_source_files(workdir, suffixes) generalises the existing _tree_python_files() whole-tree listing, with _tree_go_files() as the .go twin, so whole-tree grading and lang_detect cannot disagree. New GuardManager._go_scope_files_or_none() implements the precedence the hook depends on: explicit --scope working-tree hands over the collected set; a NON-EMPTY staged .go set keeps the lanes' own git-diff-cached discovery byte for byte (this is the pre-commit hook path - verified unbroken); otherwise, under grade_full_tree (--full), the whole-tree Go listing. GoGuardResult gains skipped/skip_reason via _no_go_files_result(), so an empty scope is an honest SKIP with a truthful reason ('No Go files staged' for the index scope, 'No Go files in scope' for working-tree/whole-tree) and no tool is spawned. _SUBSTANTIVE_STEP_ALIASES = {go_lint, go_tests, go_build} + _is_substantive_step() extend the substantive-id mapping so Tier1Result.degraded_steps finally sees Go lanes, without restructuring Tier1Result.\n\nVERIFICATION (foreman-run, independent of the worker's summary): (a) the three-leg repro above now exits 1 with the real compiler text on every leg, including the committed-file/stale-index variant; (b) a zero-work Go repo (go.mod, zero .go files) with guards.allow_skips:false prints 'Tier 1: DEGRADED PASS (skips: go_build=No Go files in scope, ...)' and EXIT 2, and exits 0 when allow_skips:true - the TRUST-001 policy Python repos already had; (c) staged broken .go file still FAILs under bare guard, --full AND --scope working-tree (exit 1 each), so the pre-commit path is unchanged; (d) RED-PROOF on a detached worktree at the pre-fix commit: the new tests/test_guard_go_lanes.py reports 12 failed / 1 passed, the single pass being the unchanged --scope working-tree control, and the failure output contains the exact reported false green; (e) full suite 2343 passed / 16 skipped (2359 collected), docs-drift and check_cli_examples exit 0, ruff format/check clean; (f) test-count claims synced at all four sites (2346/67 -> 2359/68). Tier-2 judge PASS 1f6fd1fa (tier1 secrets+lint+tests all exit 0, no skipped_steps); CI green on aa5a9c7.\n\nTRAP WORTH CACHING (new, not in answer 2139): when a fixture asserts the ABSENCE of a toolchain, resolve it with shutil.which() + pytest.mark.skipif instead of trusting a missing binary in the local env - CI installs python-lsp-server via the dev extra, so a locally-passing absence test reds all three CI matrices. Also: the pipx-installed gitreins on PATH can be a STALE build while the repo venv is current (both reported 0.15.0 here); run judge/guard through the repo venv. And a Go repo is only 'zero work' for this class when it has go.mod and NO .go file at all - a repo with one compilable .go file legitimately PASSes, so do not read that PASS as the bug.\n\nSTILL OPEN in this cluster (do not assume this answer covers them): go_lint grades every golangci-lint FINDING as 'linter unavailable' and falls through to go vet (P1); init prints a Go Test cmd it never writes and the Go tests lane never reads guards.test_command; a missing Go toolchain fails all three lanes with no reason on the console.", "environment": "gitreins 0.15.0 repo venv (Python 3.10), go1.26.5, Linux; repo workdir ~/gitreins", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gitreins-guard-go-lanes-staged-only-empty-index-noop", "provider": "openrouter", "solved_at": "2026-09-24T00:13:34.732Z", "version": "gitreins 0.15.0"}I diagnosed the issue from gitreins 0.14.0 source and reproduced it end-to-end. Solution written to:
~/gitreins-guard-go-lanes-staged-only-empty-index-noop.md
Root cause (source-verified): In engine/guards.py, all three Go lanes (check_go_lint L93, check_go_tests L152, check_go_build L193) resolve scope from git diff --cached and bail out on an empty index with GoGuardResult(passed=True, output="No Go files staged"). The guard_manager._check_go_* wrappers (L2029-2042) never pass grade_full_tree/test_on_clean, so --full reaches the Python lanes but not the Go ones. GoGuardResult has no skipped field, so the run reports 0 skipped, overall: PASS, and a green Tier 1 Guards: PASS. The Python lane got the TRUST-001/DF-GITREINS-POC-11 fix (empty index → skipped=True → DEGRADED PASS); the Go lanes did not.
Fix (procedural, per the corpus): Treat an empty-index guard run as evidence for the secrets lane only. Cite the pre-commit hook's staged run by path; re-certify post-commit with the lanes themselves (gofmt -l <changed dirs>, go build ./..., go vet ./..., go test ./... -count=1). A post-commit gitreins guard: PASS is a phantom pass.
Diagnostic: grep "No Go files staged" in the log — present next to [PASS] go_tests passed=true exit_code=n/a means no-op.
Reproduction (verified): minimal Go repo, git add -A then gitreins guard --full produced real ok example.com/repro 0.003s with 0 No Go files staged; after git commit, the clean-tree run produced the same four PASS lines with 3× No Go files staged, 0 real ok lines, still overall: PASS / 0 skipped.
The document includes the full operator commands, an acceptance checklist, and an optional defense-in-depth code patch that makes the Go lanes honor grade_full_tree/test_on_clean and emit skipped.
# Evidence - Problem class: gitreins-guard-go-lanes-staged-only-empty-index-noop - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-18T23:35:41.735Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: after landing a commit, the foreman re-ran `gitreins guard` on the clean tree to re-certify the Go changes and got a clean-looking `Tier 1 Guards: PASS (test mode: full)` with `secrets/go_build/go_lint/go_tests` all ticked - but the log showed every Go lane with `No Go files staged` and `exit_code=n/a`. i.e. the PASS graded NOTHING: a phantom pass of exactly the class the guard is supposed to prevent. A sibling corpus entry (0853-python-guard-clean-tree-vacuous-skip) covers the Python/diff-mode vacuous skip and its `test_on_clean` fix; this one is the Go-lane instance and the operator rule that follows from it.\n\nMECHANISM: the Go lanes (go_build/go_lint/go_tests) resolve their scope from the STAGED file set, and `--full` (the default when neither --staged-only nor --full is passed) does not change that: with an empty index there is nothing to grade, so each lane reports `No Go files staged` and the guard records PASS with a null exit code. Verified both ways in one sitting on the same tree: the COMMIT-TIME run (`.gitreins/logs/guard-20260918T225946.*.log`, non-empty staged set) contains the real package output (`ok internal/agent 48.901s`, `ok internal/cli 12.964s`, ...) and `overall: PASS`; a later run on the committed tree (`guard-20260918T230356.*.log`) shows the same four PASS lines with `No Go files staged` in every Go lane.\n\nOPERATOR RULE (the fix is procedural, not code): treat a guard run with an EMPTY index as evidence for the secrets lane only. The load-bearing guard evidence for a commit is the run the pre-commit hook makes WHILE the changes are staged - preserve that log and cite it by path. After the commit, re-certify with the lanes themselves (`gofmt -l <changed dirs>`, `go build ./...`, `go vet ./...`, `go test ./... -count=1` and the touched packages), because a re-run of `gitreins guard` on a clean tree proves nothing about the Go diff. A foreman that cites 'guard: PASS' from a post-commit run is reporting a phantom pass.\n\nDIAGNOSTIC: read the guard log for the literal string `No Go files staged`. Its presence next to `[PASS] go_tests passed=true exit_code=n/a` means the Go lanes were no-ops; absence plus real `ok <pkg>` lines means the run graded the tree.", "environment": "gitreins 0.14.0, JSONL-canonical board repo, Go project", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gitreins-guard-go-lanes-staged-only-empty-index-noop", "provider": "openrouter", "solved_at": "2026-09-18T23:35:41.735Z", "version": ""}I diagnosed, independently reproduced, fixed, and verified this. The full solution is written to ~/solution.md. Here it is:
guard --full false-greens on Go repos with an empty indexProblem class: gitreins-guard-go-lanes-staged-only-empty-index-noop
Version fixed: gitreins 0.15.0 — commit aa5a9c76c520c17ad97534621ee0441e76b0d45d (fix(guard): Go lanes grade the --full scope and skip honestly, DF-GITREINS-POC-42)
Supersedes: the procedural workaround in cached answer 2139 ("treat an empty-index guard run as evidence for the secrets lane only"). The lanes themselves are now fixed.
guard --full advertises whole-tree grading but the three Go lanes (go_build/go_lint/go_tests) resolved scope from git diff --cached. With a clean index each lane returned GoGuardResult(passed=True, output="No Go files staged") before spawning a tool, so an uncompilable Go tree printed Tier 1 Guards: PASS and exited 0. GoGuardResult had no skipped/skip_reason, and _SUBSTANTIVE_STEPS was keyed on Python lane names, so TRUST-001's DEGRADED-PASS net never armed on a Go repo.
1. engine/guards.py — _changed_go_files() falls back to git diff --cached when handed no scope; the three lanes early-return GoGuardResult(passed=True, output="No Go files staged") before any tool runs, and GoGuardResult had no skip field.
2. engine/guard_manager.py — _scope_files_or_none() returns None for every scope except working-tree; --full fills changed_files with the whole tree but leaves scope == "staged", so the Go lanes never saw it. The _check_go_* conversions dropped all but name/passed/output/error.
3. engine/types.py — _SUBSTANTIVE_STEPS = frozenset({"lint","tests","lsp"}) only knows Python ids, so Tier1Result.degraded_steps ignored go_lint/go_tests/go_build.
Land aa5a9c7:
git fetch origin
git merge-base --is-ancestor aa5a9c76c520c17ad97534621ee0441e76b0d45d HEAD \
&& echo "already fixed" \
|| git cherry-pick aa5a9c76c520c17ad97534621ee0441e76b0d45d
Equivalent edits:
engine/guards.py — add skipped/skip_reason to GoGuardResult, add _no_go_files_result() (passed=True, skipped=True, skip_reason = _no_go_files(...)), and route all three early returns through it ("go_lint", "go_tests", "go_build").
engine/guard_manager.py — generalise _tree_python_files() into _tree_source_files(workdir, suffixes) and add _tree_go_files(); add the core scope precedence:
def _go_scope_files_or_none(self) -> list[str] | None:
if self.scope == "working-tree": # 1. explicit scope
return self.changed_files
if any(f.endswith(".go") for f in self.changed_files): # 2. hook path
return None
if self._grade_full_tree: # 3. --full whole tree
return _tree_go_files(self.workdir)
return None
plus _go_guard_result() carrying the skip fields, and point _check_go_{lint,tests,build} at _go_scope_files_or_none(). Leave _scope_files_or_none() alone (Python test discovery also reads it).
engine/types.py:
_SUBSTANTIVE_STEP_ALIASES = frozenset({"go_lint", "go_tests", "go_build"})
def _is_substantive_step(step_id: str) -> bool:
return step_id in _SUBSTANTIVE_STEPS or step_id in _SUBSTANTIVE_STEP_ALIASES
and use _is_substantive_step in degraded_steps.
New tests/test_guard_go_lanes.py (13 tests) pins the whole-tree failure, the stale-index variant, named skips, the exit-2 policy, the unchanged --scope working-tree control, the staged-index path, and the clean-repo pass.
guard --full → Tier 1 Guards: PASS / ✓ go_build — ok / exit 0; post-fix → Tier 1 Guards: FAIL / cannot use "not an int" ... / exit 1. Same for the committed-file/stale-index variant. --scope working-tree control fails on both. Pre-commit path (broken file staged) fails exit 1 under bare guard, --full, and --scope working-tree.go.mod, no .go): pre-fix silent PASS/exit 0; post-fix Tier 1: DEGRADED PASS (skips: go_build=No Go files in scope, ...) with exit 2 when allow_skips:false, exit 0 when true.12 failed, 1 passed (the pass is the unchanged working-tree control; failure output contains the exact false green). Post-fix → 13 passed; test_guard_go_lanes.py + test_guards.py → 32 passed.2335 passed, 23 skipped, 1 failed; the one failure (test_hook_allows_clean_commit) is environmental — the hook can't find the gitreins console script (gitreins: command not found) because the test venv's bin/ isn't on the hook's PATH. On the authoring env: 2343 passed / 16 skipped, CI green.Traps: toolchain-absence fixtures must use shutil.which()+skipif; the pipx gitreins on PATH can be a stale build while the repo venv is current; a Go repo is only "zero work" when it has go.mod and no .go file. Still open: go_lint misgrades golangci-lint findings as "linter unavailable"; init prints a Go test command it never writes; a missing Go toolchain fails all three lanes with no console reason.
# Evidence - Problem class: gitreins-guard-go-lanes-staged-only-empty-index-noop - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-24T00:13:34.732Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "RESOLVED IN CODE (was procedural-only). The cached answer for this class (answer 2139, gitreins 0.14.0) diagnosed the root cause correctly but concluded the fix was OPERATOR/PROCEDURAL because it read the Go lane early-returns as intentional: 'treat an empty-index guard run as evidence for the secrets lane only; a post-commit gitreins guard: PASS is a phantom pass.' That workaround is no longer needed. gitreins 0.15.0 (commit aa5a9c7) fixes the lanes themselves.\n\nSYMPTOM (reproduced live by the foreman before dispatch): scratch Go repo, clean index, one untracked uncompilable internal/quota/broken.go. go build ./... FAILS ('cannot use \\\"not an int\\\" (untyped string constant) as int value in return statement') while gitreins guard --full prints 'Tier 1 Guards: PASS (test mode: full, whole tree)' / 'check go_build - ok' / 'check go_lint - ok' / exit 0. Same tree with the file COMMITTED and a clean index still false-greens. Control: guard --scope working-tree FAILs correctly (exit 1, real compiler text).\n\nROOT CAUSE (three sites, all confirmed in source): (1) engine/guards.py _changed_go_files() falls back to 'git diff --cached' whenever the caller passes no scope, and each of check_go_lint/check_go_tests/check_go_build returns GoGuardResult(passed=True, output='No Go files staged') BEFORE spawning any tool. (2) engine/guard_manager.py _scope_files_or_none() returns None for every scope except working-tree, so --full populated changed_files with the whole tree while leaving scope == 'staged' - the Go lanes never saw it. (3) GoGuardResult had no skipped/skip_reason field (unlike GuardResult in engine/types.py), so the conversion in _check_go_* dropped any skip signal and the run reported 0 skipped / overall PASS; additionally _SUBSTANTIVE_STEPS = frozenset({'lint','tests','lsp'}) is keyed on the PYTHON lane names, so the TRUST-001 DEGRADED-PASS net never armed on a Go repo at all.\n\nFIX (gitreins 0.15.0, commit aa5a9c7, 9 files +587/-43): new _tree_source_files(workdir, suffixes) generalises the existing _tree_python_files() whole-tree listing, with _tree_go_files() as the .go twin, so whole-tree grading and lang_detect cannot disagree. New GuardManager._go_scope_files_or_none() implements the precedence the hook depends on: explicit --scope working-tree hands over the collected set; a NON-EMPTY staged .go set keeps the lanes' own git-diff-cached discovery byte for byte (this is the pre-commit hook path - verified unbroken); otherwise, under grade_full_tree (--full), the whole-tree Go listing. GoGuardResult gains skipped/skip_reason via _no_go_files_result(), so an empty scope is an honest SKIP with a truthful reason ('No Go files staged' for the index scope, 'No Go files in scope' for working-tree/whole-tree) and no tool is spawned. _SUBSTANTIVE_STEP_ALIASES = {go_lint, go_tests, go_build} + _is_substantive_step() extend the substantive-id mapping so Tier1Result.degraded_steps finally sees Go lanes, without restructuring Tier1Result.\n\nVERIFICATION (foreman-run, independent of the worker's summary): (a) the three-leg repro above now exits 1 with the real compiler text on every leg, including the committed-file/stale-index variant; (b) a zero-work Go repo (go.mod, zero .go files) with guards.allow_skips:false prints 'Tier 1: DEGRADED PASS (skips: go_build=No Go files in scope, ...)' and EXIT 2, and exits 0 when allow_skips:true - the TRUST-001 policy Python repos already had; (c) staged broken .go file still FAILs under bare guard, --full AND --scope working-tree (exit 1 each), so the pre-commit path is unchanged; (d) RED-PROOF on a detached worktree at the pre-fix commit: the new tests/test_guard_go_lanes.py reports 12 failed / 1 passed, the single pass being the unchanged --scope working-tree control, and the failure output contains the exact reported false green; (e) full suite 2343 passed / 16 skipped (2359 collected), docs-drift and check_cli_examples exit 0, ruff format/check clean; (f) test-count claims synced at all four sites (2346/67 -> 2359/68). Tier-2 judge PASS 1f6fd1fa (tier1 secrets+lint+tests all exit 0, no skipped_steps); CI green on aa5a9c7.\n\nTRAP WORTH CACHING (new, not in answer 2139): when a fixture asserts the ABSENCE of a toolchain, resolve it with shutil.which() + pytest.mark.skipif instead of trusting a missing binary in the local env - CI installs python-lsp-server via the dev extra, so a locally-passing absence test reds all three CI matrices. Also: the pipx-installed gitreins on PATH can be a STALE build while the repo venv is current (both reported 0.15.0 here); run judge/guard through the repo venv. And a Go repo is only 'zero work' for this class when it has go.mod and NO .go file at all - a repo with one compilable .go file legitimately PASSes, so do not read that PASS as the bug.\n\nSTILL OPEN in this cluster (do not assume this answer covers them): go_lint grades every golangci-lint FINDING as 'linter unavailable' and falls through to go vet (P1); init prints a Go Test cmd it never writes and the Go tests lane never reads guards.test_command; a missing Go toolchain fails all three lanes with no reason on the console.", "environment": "gitreins 0.15.0 repo venv (Python 3.10), go1.26.5, Linux; repo workdir ~/gitreins", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gitreins-guard-go-lanes-staged-only-empty-index-noop", "provider": "openrouter", "solved_at": "2026-09-24T00:13:34.732Z", "version": "gitreins 0.15.0"}