fix: guard remote/repo mismatch vs local git remote (Closes #530) #555

Merged
sysadmin merged 1 commits from fix/issue-530-remote-repo-mismatch into master 2026-07-08 21:07:03 -05:00
Owner

Summary

Closes #530.

Bare remote=prgs resolved to the hardcoded REMOTES default Scaled-Tech-Consulting/Timesheet instead of Scaled-Tech-Consulting/Gitea-Tools, which is what the local git remote points at. That silent divergence caused false 404s (e.g. PR #527 / issue #515 lookups) and risked mutating the wrong repository.

Root cause

gitea_auth.py REMOTES["prgs"]["repo"] = "Timesheet". When no profiles.json overlay supplies default_repo, _resolve("prgs", ...) with no explicit org/repo yields Scaled-Tech-Consulting/Timesheet. The shared default is intentionally left unchanged (the prgs instance also hosts Timesheet); instead the fix fails closed on a proven mismatch.

Fix

  • New remote_repo_guard.py — pure assess_remote_repo_match(...) + format_remote_repo_guard_error(...), mirroring the root_checkout_guard.py shape.
  • Wired into gitea_mcp_server._resolve via _enforce_remote_repo_guard, so every read/lookup/mutation tool that resolves a target is covered at one chokepoint.
  • Fails closed with a diagnostic message (showing resolved org/repo and the local remote URL) when the resolved org/repo slug is absent from the local git remote URL and the caller did not pass explicit org/repo.
  • Conservative by design: skips when both org and repo are explicit, skips when the local remote URL is unavailable (best-effort), and is bypassed under pytest unless GITEA_FORCE_REMOTE_REPO_CHECK=1 so the unit suite (which calls tools with bare remotes against mocked APIs) is unaffected.

Acceptance criteria

  • Regression coverage for remote=prgs resolving to the wrong repo
  • Guard detecting mismatch between local git remote URL and MCP-resolved org/repo
  • Test proving PR/issue lookup cannot silently query a different repository (gitea_view_issue fails closed before any API call)
  • Author/reviewer/merger handoff templates updated to require explicit remote/org/repo

Files changed

  • remote_repo_guard.py (new)
  • gitea_mcp_server.py — import + _resolve hook + _enforce_remote_repo_guard
  • tests/test_remote_repo_guard.py (new) — pure-function cases + server-wiring cases
  • skills/llm-project-workflow/templates/{start-issue,review-pr,merge-pr}.md

Validation

venv/bin/python -m pytest tests/test_remote_repo_guard.py -q   # 11 passed
venv/bin/python -m pytest tests/ -q                            # 1957 passed, 6 skipped, 2 failed
git diff --check                                               # clean
venv/bin/python -m py_compile remote_repo_guard.py gitea_mcp_server.py tests/test_remote_repo_guard.py  # ok

The 2 full-suite failures (test_verify_preflight_blocks_control_checkout_with_test_porcelain, test_pr487_style_merge_binds_clean_merger_workspace) are pre-existing on clean master — verified by running both against the root master checkout, where they fail identically at gitea_mcp_server.py:715. Neither touches remote/repo resolution.

LLM Handoff Metadata

  • LLM-Role: implementer
  • Authenticated-Gitea-User: jcwalker3
  • MCP-Profile: prgs-author
  • Branch: fix/issue-530-remote-repo-mismatch
  • Worktree: branches/issue-530-remote-repo-mismatch
  • Self-review allowed: no
## Summary Closes #530. Bare `remote=prgs` resolved to the hardcoded `REMOTES` default `Scaled-Tech-Consulting/Timesheet` instead of `Scaled-Tech-Consulting/Gitea-Tools`, which is what the local git remote points at. That silent divergence caused false 404s (e.g. PR #527 / issue #515 lookups) and risked mutating the wrong repository. ## Root cause `gitea_auth.py` `REMOTES["prgs"]["repo"] = "Timesheet"`. When no `profiles.json` overlay supplies `default_repo`, `_resolve("prgs", ...)` with no explicit `org`/`repo` yields `Scaled-Tech-Consulting/Timesheet`. The shared default is intentionally left unchanged (the `prgs` instance also hosts `Timesheet`); instead the fix fails closed on a proven mismatch. ## Fix - New `remote_repo_guard.py` — pure `assess_remote_repo_match(...)` + `format_remote_repo_guard_error(...)`, mirroring the `root_checkout_guard.py` shape. - Wired into `gitea_mcp_server._resolve` via `_enforce_remote_repo_guard`, so every read/lookup/mutation tool that resolves a target is covered at one chokepoint. - Fails closed with a diagnostic message (showing resolved org/repo and the local remote URL) when the resolved `org/repo` slug is absent from the local git remote URL **and** the caller did not pass explicit `org`/`repo`. - Conservative by design: skips when both `org` and `repo` are explicit, skips when the local remote URL is unavailable (best-effort), and is bypassed under pytest unless `GITEA_FORCE_REMOTE_REPO_CHECK=1` so the unit suite (which calls tools with bare remotes against mocked APIs) is unaffected. ## Acceptance criteria - [x] Regression coverage for `remote=prgs` resolving to the wrong repo - [x] Guard detecting mismatch between local git remote URL and MCP-resolved org/repo - [x] Test proving PR/issue lookup cannot silently query a different repository (`gitea_view_issue` fails closed before any API call) - [x] Author/reviewer/merger handoff templates updated to require explicit `remote`/`org`/`repo` ## Files changed - `remote_repo_guard.py` (new) - `gitea_mcp_server.py` — import + `_resolve` hook + `_enforce_remote_repo_guard` - `tests/test_remote_repo_guard.py` (new) — pure-function cases + server-wiring cases - `skills/llm-project-workflow/templates/{start-issue,review-pr,merge-pr}.md` ## Validation ``` venv/bin/python -m pytest tests/test_remote_repo_guard.py -q # 11 passed venv/bin/python -m pytest tests/ -q # 1957 passed, 6 skipped, 2 failed git diff --check # clean venv/bin/python -m py_compile remote_repo_guard.py gitea_mcp_server.py tests/test_remote_repo_guard.py # ok ``` The 2 full-suite failures (`test_verify_preflight_blocks_control_checkout_with_test_porcelain`, `test_pr487_style_merge_binds_clean_merger_workspace`) are pre-existing on clean `master` — verified by running both against the root master checkout, where they fail identically at `gitea_mcp_server.py:715`. Neither touches remote/repo resolution. ## LLM Handoff Metadata - LLM-Role: implementer - Authenticated-Gitea-User: jcwalker3 - MCP-Profile: prgs-author - Branch: fix/issue-530-remote-repo-mismatch - Worktree: branches/issue-530-remote-repo-mismatch - Self-review allowed: no
jcwalker3 added 1 commit 2026-07-08 14:50:30 -05:00
Bare remote=prgs resolved to the hardcoded REMOTES default
Scaled-Tech-Consulting/Timesheet instead of the Gitea-Tools repository
the local git remote points at, causing false 404s and risking mutation
of a different repository.

Add remote_repo_guard.assess_remote_repo_match (pure) + formatter, wired
into gitea_mcp_server._resolve so every read/lookup/mutation tool fails
closed when the MCP-resolved org/repo disagrees with the local git remote
URL and the caller did not pass explicit org/repo. The guard is
best-effort: it skips when both org and repo are explicit, when the local
remote URL is unavailable, and is bypassed under pytest unless
GITEA_FORCE_REMOTE_REPO_CHECK is set.

Add tests/test_remote_repo_guard.py (pure-function cases + server wiring
proving a lookup tool cannot silently query a different repository), and
update author/reviewer/merger handoff templates to require explicit
remote/org/repo.

Closes #530

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #555
issue: #530
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 34818-d3f66410a126
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr555
phase: claimed
candidate_head: fd2bc018ff
target_branch: master
target_branch_sha: 18e7d7b6e1
last_activity: 2026-07-09T02:00:54Z
expires_at: 2026-07-09T04:00:54Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #555 issue: #530 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 34818-d3f66410a126 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr555 phase: claimed candidate_head: fd2bc018ffabcdcd7d6c55af1463687d7d18328b target_branch: master target_branch_sha: 18e7d7b6e18cfb8d3941f3fd780601e5b87f854b last_activity: 2026-07-09T02:00:54Z expires_at: 2026-07-09T04:00:54Z blocker: none
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #555
issue: #530
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 34818-d3f66410a126
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr555
phase: released
candidate_head: fd2bc018ff
target_branch: master
target_branch_sha: 18e7d7b6e1
last_activity: 2026-07-09T02:01:05Z
expires_at: 2026-07-09T04:01:05Z
blocker: manual-release

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #555 issue: #530 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 34818-d3f66410a126 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr555 phase: released candidate_head: fd2bc018ffabcdcd7d6c55af1463687d7d18328b target_branch: master target_branch_sha: 18e7d7b6e18cfb8d3941f3fd780601e5b87f854b last_activity: 2026-07-09T02:01:05Z expires_at: 2026-07-09T04:01:05Z blocker: manual-release
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #555
issue: #530
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 34818-d3f66410a126
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr555-fresh2
phase: claimed
candidate_head: fd2bc018ff
target_branch: master
target_branch_sha: 18e7d7b6e1
last_activity: 2026-07-09T02:04:51Z
expires_at: 2026-07-09T04:04:51Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #555 issue: #530 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 34818-d3f66410a126 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr555-fresh2 phase: claimed candidate_head: fd2bc018ffabcdcd7d6c55af1463687d7d18328b target_branch: master target_branch_sha: 18e7d7b6e18cfb8d3941f3fd780601e5b87f854b last_activity: 2026-07-09T02:04:51Z expires_at: 2026-07-09T04:04:51Z blocker: none
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #555
issue: #530
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 34818-d3f66410a126
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr555-fresh2
phase: claimed
candidate_head: fd2bc018ff
target_branch: master
target_branch_sha: 18e7d7b6e1
last_activity: 2026-07-09T02:05:33Z
expires_at: 2026-07-09T04:05:33Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #555 issue: #530 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 34818-d3f66410a126 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr555-fresh2 phase: claimed candidate_head: fd2bc018ffabcdcd7d6c55af1463687d7d18328b target_branch: master target_branch_sha: 18e7d7b6e18cfb8d3941f3fd780601e5b87f854b last_activity: 2026-07-09T02:05:33Z expires_at: 2026-07-09T04:05:33Z blocker: none
sysadmin approved these changes 2026-07-08 21:05:45 -05:00
sysadmin left a comment
Owner

Fresh independent review — PR #555 / Issue #530

Verdict: APPROVE

Live re-verification

  • Head SHA (Gitea + remote branch): fd2bc018ffabcdcd7d6c55af1463687d7d18328b
  • Base master: 18e7d7b6e18cfb8d3941f3fd780601e5b87f854b
  • Mergeable: true
  • Author: jcwalker3 — reviewer: sysadmin / prgs-reviewer (not self-review)
  • Worktree: branches/review-pr555-fresh2

Scope

  • New remote_repo_guard.py pure assessment
  • Wired into gitea_mcp_server._resolve via _enforce_remote_repo_guard
  • Fail-closed when bare remote resolves to org/repo missing from local git remote URL
  • Skip when both org+repo explicit, or local URL unavailable; pytest bypass unless GITEA_FORCE_REMOTE_REPO_CHECK
  • Template notes for explicit remote/org/repo
  • Tests: tests/test_remote_repo_guard.py

Validation (this session)

  • git diff --check prgs/master...HEAD: clean
  • Targeted: 11 passed
  • Full suite: 1957 passed, 6 skipped, 2 failed (pre-existing master baseline failures; not introduced by this PR)

Correctness

  • Single chokepoint on _resolve covers all tools
  • Does not change hardcoded Timesheet default (shared instance); fails closed on mismatch instead
  • Explicit org/repo remains authoritative

No REQUEST_CHANGES findings.

Next: merger may merge at head fd2bc018ffabcdcd7d6c55af1463687d7d18328b after formal APPROVED is visible.

## Fresh independent review — PR #555 / Issue #530 **Verdict: APPROVE** ### Live re-verification - Head SHA (Gitea + remote branch): `fd2bc018ffabcdcd7d6c55af1463687d7d18328b` - Base master: `18e7d7b6e18cfb8d3941f3fd780601e5b87f854b` - Mergeable: true - Author: `jcwalker3` — reviewer: `sysadmin` / `prgs-reviewer` (not self-review) - Worktree: `branches/review-pr555-fresh2` ### Scope - New `remote_repo_guard.py` pure assessment - Wired into `gitea_mcp_server._resolve` via `_enforce_remote_repo_guard` - Fail-closed when bare remote resolves to org/repo missing from local git remote URL - Skip when both org+repo explicit, or local URL unavailable; pytest bypass unless `GITEA_FORCE_REMOTE_REPO_CHECK` - Template notes for explicit remote/org/repo - Tests: `tests/test_remote_repo_guard.py` ### Validation (this session) - `git diff --check prgs/master...HEAD`: clean - Targeted: **11 passed** - Full suite: **1957 passed**, 6 skipped, **2 failed** (pre-existing master baseline failures; not introduced by this PR) ### Correctness - Single chokepoint on `_resolve` covers all tools - Does not change hardcoded Timesheet default (shared instance); fails closed on mismatch instead - Explicit org/repo remains authoritative No REQUEST_CHANGES findings. **Next:** merger may merge at head `fd2bc018ffabcdcd7d6c55af1463687d7d18328b` after formal APPROVED is visible.
Owner

adopted_at: 2026-07-09T02:06:52Z
adopted_by_identity: sysadmin
adopted_by_profile: prgs-merger
adopted_from_session_id: 34818-d3f66410a126
adopted_from_profile: prgs-reviewer
adopted_from_reviewer_identity: sysadmin
adopted_from_comment_id: 7343
adoption_reason: merger-handoff-approved-head

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #555
issue: #530
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 34822-0282f7356d08
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr555
phase: adopted
candidate_head: fd2bc018ff
target_branch: master
target_branch_sha: 18e7d7b6e1
last_activity: 2026-07-09T02:06:52Z
expires_at: 2026-07-09T04:06:52Z
blocker: none

<!-- mcp-review-lease-adoption:v1 --> adopted_at: 2026-07-09T02:06:52Z adopted_by_identity: sysadmin adopted_by_profile: prgs-merger adopted_from_session_id: 34818-d3f66410a126 adopted_from_profile: prgs-reviewer adopted_from_reviewer_identity: sysadmin adopted_from_comment_id: 7343 adoption_reason: merger-handoff-approved-head <!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #555 issue: #530 reviewer_identity: sysadmin profile: prgs-merger session_id: 34822-0282f7356d08 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr555 phase: adopted candidate_head: fd2bc018ffabcdcd7d6c55af1463687d7d18328b target_branch: master target_branch_sha: 18e7d7b6e18cfb8d3941f3fd780601e5b87f854b last_activity: 2026-07-09T02:06:52Z expires_at: 2026-07-09T04:06:52Z blocker: none
sysadmin merged commit 6e589e73ea into master 2026-07-08 21:07:03 -05:00
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #555
issue: #530
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 34822-0282f7356d08
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr555
phase: released
candidate_head: fd2bc018ff
target_branch: master
target_branch_sha: 18e7d7b6e1
last_activity: 2026-07-09T02:07:54Z
expires_at: 2026-07-09T04:07:54Z
blocker: post-merge-moot

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #555 issue: #530 reviewer_identity: sysadmin profile: prgs-merger session_id: 34822-0282f7356d08 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr555 phase: released candidate_head: fd2bc018ffabcdcd7d6c55af1463687d7d18328b target_branch: master target_branch_sha: 18e7d7b6e18cfb8d3941f3fd780601e5b87f854b last_activity: 2026-07-09T02:07:54Z expires_at: 2026-07-09T04:07:54Z blocker: post-merge-moot
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#555