Repository: totalwindupflightsystems/gitreins
Diagnosis complete and verified against the real repository. Solution written to ~/GR-GAP-054-solution.md.
guard.run false green on config-less repositoriesRepository: totalwindupflightsystems/gitreins
Fix commit: 11f8a25 — fix(mcp): reject guard.run without config. Addresses GR-GAP-054.
Component: gitreins_mcp/server.py → GitReinsMCPServer._guard_run
A freshly git init'd repo (no .gitreins/config.yaml) could be guarded through MCP and receive a green result: guard.run returned passed: true with guards reporting "nothing staged", i.e. a false green. This is the same class of false positive the CLI already blocks (GR-GAP-051, gitreins/cli.py::_require_guard_config), but the MCP surface had no gate.
The vulnerable _guard_run treated a missing config as an empty config and still constructed GuardManager:
wd = os.path.abspath(workdir) if workdir else self.workdir
config: dict[str, object] = {}
config_path = os.path.join(wd, ".gitreins", "config.yaml")
if os.path.isfile(config_path):
try:
with open(config_path, "r") as f:
config = yaml.safe_load(f) or {}
except Exception:
pass
gm = GuardManager(wd, config=config) # config == {} when file is missing
result = gm.run_all(force_dead_code=dead_code)
return {"passed": result.passed, ...} # -> True
Failure chain matches the reported stack trace: _guard_run → load_config (missing file silently becomes {}) → GuardManager.run_all → serialize_guard_result (passed=true). GuardManager is intentionally permissive with config=None/{}/ for library callers and test fixtures, so the MCP layer must enforce the config requirement itself. Two constraints: gate at the MCP surface (not inside run_all()), and gate the effective workdir (optional workdir arg wins).
gitreins_mcp/server.py::_guard_run — check the effective workdir for .gitreins/config.yaml before constructing GuardManager:
def _guard_run(self, workdir: str = None, dead_code: bool = False) -> dict:
"""Run Tier 1 static guards. Accepts optional workdir for cross-repo use
- and dead_code boolean for on-demand dead-code detection."""
+ and dead_code boolean for on-demand dead-code detection.
+
+ Refuses to run when the target repo has no .gitreins/config.yaml
+ (GR-GAP-054). ... This gate lives HERE, not in
+ ``GuardManager.run_all()``: library callers and unit-test fixtures
+ construct ``GuardManager`` directly with ``config=None`` and must
+ keep working.
+ """
import yaml
wd = os.path.abspath(workdir) if workdir else self.workdir
# Load config from .gitreins/config.yaml (same pattern as CLI)
- config: dict[str, object] = {}
config_path = os.path.join(wd, ".gitreins", "config.yaml")
- if os.path.isfile(config_path):
- try:
- with open(config_path, "r") as f:
- config = yaml.safe_load(f) or {}
- except Exception:
- pass
+ if not os.path.isfile(config_path):
+ return {
+ "error": (
+ f"no .gitreins/config.yaml in {wd} — run `gitreins init` first. "
+ "Refusing to run Tier 1 guards: without a config every guard "
+ "falls back to built-in defaults and reports a false green."
+ ),
+ "workdir": wd,
+ }
+ config: dict[str, object] = {}
+ try:
+ with open(config_path, "r") as f:
+ config = yaml.safe_load(f) or {}
+ except Exception:
+ pass
gm = GuardManager(wd, config=config)
result = gm.run_all(force_dead_code=dead_code)
return {
The error payload is serialized normally and contains no passed/results, so the call is success=false. Behavior matrix:
| Scenario | Result |
|---|---|
No config, no workdir arg |
{"error": "...gitreins init...", "workdir": <server wd>} |
No config on server, target workdir has config |
passed/workdir/results preserved |
Config on server, target workdir has none |
{"error": ..., "workdir": <target>} |
| Target has config | {"passed, "workdir", "results"} unchanged |
test_guard_run_returns_passed_and_results — configured repo keeps shape.test_guard_run_without_config_returns_error_not_pass — errors naming .gitreins/config.yaml + gitreins init, and GuardManager.run_all is never invoked (monkeypatched to raise).test_guard_run_target_workdir_with_config_allows / ..._without_config_errors — gate follows the requested workdir.test_guard_run_over_stdio / test_guard_run_without_config_over_stdio — same contract over real stdio.tests/test_mcp_integration.py::test_guard_run_without_config_returns_error — fresh git init, no config, real stdio process.Before (11f8a25^), bare repo:
{"passed": true, "workdir": "/tmp/bare-repo-...",
"results": [{"name":"secrets","passed":true,...},
{"name":"lint","passed":true,...},
{"name":"tests","passed":true,...}]}
FALSE GREEN
After (HEAD), same bare repo:
{"error": "no .gitreins/config.yaml in /tmp/bare-repo-... — run `gitreins init` first. Refusing to run Tier 1 guards: without a config every guard falls back to built-in defaults and reports a false green.",
"workdir": "/tmp/bare-repo-..."}
rejected (error)
Configured target via optional workdir (server itself unconfigured): passed: True | guards: ['secrets'].
Focused suite:
uv run --frozen pytest tests/test_mcp_server.py tests/test_mcp_integration.py \
-k "guard_run" -q -o addopts=""
# 8 passed, 77 deselected
Reported evidence: 84 passed / 1 skipped focused MCP tests; full GitReins guard PASS 4/4; Tier 2 judge PASS a03695ab; GitHub CI run 34630190475 green on Python 3.10/3.11/3.12.
# Evidence - Problem class: gitreins-mcp-guard-configless-false-green - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-11T17:58:53.107Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "GitReinsMCPServer._guard_run loaded missing configuration as an empty guard set and serialized success=true, causing MCP guard.run to report a false green for uninitialized repositories. The verified fix validates that the effective workdir contains .gitreins/config.yaml before constructing GuardManager; if absent it returns success=false with an actionable message naming gitreins init. Preserve configured repositories and optional target-workdir behavior. Regression coverage should exercise direct _guard_run and the public MCP tools/call path for missing default and explicit workdirs, plus configured success paths.", "environment": "GitReins Python MCP server; git repository with .git directory but no .gitreins/config.yaml", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gitreins-mcp-guard-configless-false-green", "provider": "openrouter", "solved_at": "2026-09-11T17:58:53.107Z", "version": "gitreins commit 11f8a25"}Diagnosis complete and verified against the real repository. Solution written to ~/GR-GAP-054-solution.md.
guard.run false green on config-less repositoriesRepository: totalwindupflightsystems/gitreins
Fix commit: 11f8a25 — fix(mcp): reject guard.run without config. Addresses GR-GAP-054.
Component: gitreins_mcp/server.py → GitReinsMCPServer._guard_run
A freshly git init'd repo (no .gitreins/config.yaml) could be guarded through MCP and receive a green result: guard.run returned passed: true with guards reporting "nothing staged", i.e. a false green. This is the same class of false positive the CLI already blocks (GR-GAP-051, gitreins/cli.py::_require_guard_config), but the MCP surface had no gate.
The vulnerable _guard_run treated a missing config as an empty config and still constructed GuardManager:
wd = os.path.abspath(workdir) if workdir else self.workdir
config: dict[str, object] = {}
config_path = os.path.join(wd, ".gitreins", "config.yaml")
if os.path.isfile(config_path):
try:
with open(config_path, "r") as f:
config = yaml.safe_load(f) or {}
except Exception:
pass
gm = GuardManager(wd, config=config) # config == {} when file is missing
result = gm.run_all(force_dead_code=dead_code)
return {"passed": result.passed, ...} # -> True
Failure chain matches the reported stack trace: _guard_run → load_config (missing file silently becomes {}) → GuardManager.run_all → serialize_guard_result (passed=true). GuardManager is intentionally permissive with config=None/{}/ for library callers and test fixtures, so the MCP layer must enforce the config requirement itself. Two constraints: gate at the MCP surface (not inside run_all()), and gate the effective workdir (optional workdir arg wins).
gitreins_mcp/server.py::_guard_run — check the effective workdir for .gitreins/config.yaml before constructing GuardManager:
def _guard_run(self, workdir: str = None, dead_code: bool = False) -> dict:
"""Run Tier 1 static guards. Accepts optional workdir for cross-repo use
- and dead_code boolean for on-demand dead-code detection."""
+ and dead_code boolean for on-demand dead-code detection.
+
+ Refuses to run when the target repo has no .gitreins/config.yaml
+ (GR-GAP-054). ... This gate lives HERE, not in
+ ``GuardManager.run_all()``: library callers and unit-test fixtures
+ construct ``GuardManager`` directly with ``config=None`` and must
+ keep working.
+ """
import yaml
wd = os.path.abspath(workdir) if workdir else self.workdir
# Load config from .gitreins/config.yaml (same pattern as CLI)
- config: dict[str, object] = {}
config_path = os.path.join(wd, ".gitreins", "config.yaml")
- if os.path.isfile(config_path):
- try:
- with open(config_path, "r") as f:
- config = yaml.safe_load(f) or {}
- except Exception:
- pass
+ if not os.path.isfile(config_path):
+ return {
+ "error": (
+ f"no .gitreins/config.yaml in {wd} — run `gitreins init` first. "
+ "Refusing to run Tier 1 guards: without a config every guard "
+ "falls back to built-in defaults and reports a false green."
+ ),
+ "workdir": wd,
+ }
+ config: dict[str, object] = {}
+ try:
+ with open(config_path, "r") as f:
+ config = yaml.safe_load(f) or {}
+ except Exception:
+ pass
gm = GuardManager(wd, config=config)
result = gm.run_all(force_dead_code=dead_code)
return {
The error payload is serialized normally and contains no passed/results, so the call is success=false. Behavior matrix:
| Scenario | Result |
|---|---|
No config, no workdir arg |
{"error": "...gitreins init...", "workdir": <server wd>} |
No config on server, target workdir has config |
passed/workdir/results preserved |
Config on server, target workdir has none |
{"error": ..., "workdir": <target>} |
| Target has config | {"passed, "workdir", "results"} unchanged |
test_guard_run_returns_passed_and_results — configured repo keeps shape.test_guard_run_without_config_returns_error_not_pass — errors naming .gitreins/config.yaml + gitreins init, and GuardManager.run_all is never invoked (monkeypatched to raise).test_guard_run_target_workdir_with_config_allows / ..._without_config_errors — gate follows the requested workdir.test_guard_run_over_stdio / test_guard_run_without_config_over_stdio — same contract over real stdio.tests/test_mcp_integration.py::test_guard_run_without_config_returns_error — fresh git init, no config, real stdio process.Before (11f8a25^), bare repo:
{"passed": true, "workdir": "/tmp/bare-repo-...",
"results": [{"name":"secrets","passed":true,...},
{"name":"lint","passed":true,...},
{"name":"tests","passed":true,...}]}
FALSE GREEN
After (HEAD), same bare repo:
{"error": "no .gitreins/config.yaml in /tmp/bare-repo-... — run `gitreins init` first. Refusing to run Tier 1 guards: without a config every guard falls back to built-in defaults and reports a false green.",
"workdir": "/tmp/bare-repo-..."}
rejected (error)
Configured target via optional workdir (server itself unconfigured): passed: True | guards: ['secrets'].
Focused suite:
uv run --frozen pytest tests/test_mcp_server.py tests/test_mcp_integration.py \
-k "guard_run" -q -o addopts=""
# 8 passed, 77 deselected
Reported evidence: 84 passed / 1 skipped focused MCP tests; full GitReins guard PASS 4/4; Tier 2 judge PASS a03695ab; GitHub CI run 34630190475 green on Python 3.10/3.11/3.12.
# Evidence - Problem class: gitreins-mcp-guard-configless-false-green - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-11T17:58:53.107Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "GitReinsMCPServer._guard_run loaded missing configuration as an empty guard set and serialized success=true, causing MCP guard.run to report a false green for uninitialized repositories. The verified fix validates that the effective workdir contains .gitreins/config.yaml before constructing GuardManager; if absent it returns success=false with an actionable message naming gitreins init. Preserve configured repositories and optional target-workdir behavior. Regression coverage should exercise direct _guard_run and the public MCP tools/call path for missing default and explicit workdirs, plus configured success paths.", "environment": "GitReins Python MCP server; git repository with .git directory but no .gitreins/config.yaml", "language": "python", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "gitreins-mcp-guard-configless-false-green", "provider": "openrouter", "solved_at": "2026-09-11T17:58:53.107Z", "version": "gitreins commit 11f8a25"}