Make branches-only mutation guard agree with runtime_context worktree resolution #460

Closed
opened 2026-07-07 15:06:35 -05:00 by jcwalker3 · 1 comment
Owner

Problem

The #274 branches-only mutation guard can reject valid write attempts even when runtime_context has already resolved a compliant branches/ worktree.

Observed behavior

A session created and verified a clean worktree under:

/Users/jasonwalker/Development/Gitea-Tools/branches/create-issue-lock-defect

But the mutation guard still evaluated the stable checkout path:

/Users/jasonwalker/Development/Gitea-Tools

and rejected the write as if the session were operating from the stable control checkout.

This means the read/runtime path and the write/guard path can disagree about the active workspace. In practice the guard emits:

Branches-only mutation guard (#274): worktree '.../branches/<name>'
does not belong to the target repository '/Users/.../Gitea-Tools' (fail closed)

while gitea_get_runtime_context concurrently reports preflight_ready: true with inspected_git_root pointing at the same valid branches/ worktree. The divergence tracks with a stale/duplicate mcp_server.py process whose PROJECT_ROOT realpath differs from the read path's resolution (verify_preflight_purity in gitea_mcp_server.py).

Expected behavior

  • The branches-only mutation guard should use the same canonical workspace resolution as runtime_context, or both systems should share one authoritative workspace resolver.
  • The guard should not be sensitive only to PROJECT_ROOT realpath when a valid task worktree has already been resolved.

Acceptance criteria

  1. Runtime workspace resolution and mutation-guard workspace resolution use the same canonical source of truth.
  2. If runtime_context resolves a valid worktree inside branches/, the mutation guard evaluates that worktree, not the stable control checkout.
  3. Stable checkout mutations remain blocked.
  4. A valid branches/ worktree is accepted only when it:
    • is inside the repo's branches/ directory
    • belongs to the target repository
    • exists on disk
    • is clean where cleanliness is required
  5. Invalid worktrees fail closed with a clear error.
  6. Error output distinguishes:
    • stable checkout rejected
    • no active worktree resolved
    • resolved worktree outside branches/
    • runtime_context/guard workspace mismatch
  7. Tests cover:
    • stable checkout mutation rejected
    • valid runtime_context branches/ worktree accepted
    • mismatch between runtime_context and guard path detected
    • non-branches/ worktree rejected
    • deleted/missing worktree rejected
    • symlink/realpath edge cases do not bypass the guard

Why this matters

Without this fix, agents can correctly create and enter a valid branches/ worktree but still be blocked because the mutation guard evaluates a different path than runtime_context. That creates false "blocked" reports and prevents valid guarded MCP mutations.

Relation

Follow-up to the #274 branches-only worktree guard. Related failure mode observed while filing #458 (guard false-fail resolved only by /mcp reconnect).

## Problem The #274 branches-only mutation guard can reject valid write attempts even when `runtime_context` has already resolved a compliant `branches/` worktree. ## Observed behavior A session created and verified a clean worktree under: `/Users/jasonwalker/Development/Gitea-Tools/branches/create-issue-lock-defect` But the mutation guard still evaluated the stable checkout path: `/Users/jasonwalker/Development/Gitea-Tools` and rejected the write as if the session were operating from the stable control checkout. This means the read/runtime path and the write/guard path can disagree about the active workspace. In practice the guard emits: ``` Branches-only mutation guard (#274): worktree '.../branches/<name>' does not belong to the target repository '/Users/.../Gitea-Tools' (fail closed) ``` while `gitea_get_runtime_context` concurrently reports `preflight_ready: true` with `inspected_git_root` pointing at the same valid `branches/` worktree. The divergence tracks with a stale/duplicate `mcp_server.py` process whose `PROJECT_ROOT` realpath differs from the read path's resolution (`verify_preflight_purity` in `gitea_mcp_server.py`). ## Expected behavior - The branches-only mutation guard should use the same canonical workspace resolution as `runtime_context`, or both systems should share one authoritative workspace resolver. - The guard should not be sensitive only to `PROJECT_ROOT` realpath when a valid task worktree has already been resolved. ## Acceptance criteria 1. Runtime workspace resolution and mutation-guard workspace resolution use the same canonical source of truth. 2. If `runtime_context` resolves a valid worktree inside `branches/`, the mutation guard evaluates that worktree, not the stable control checkout. 3. Stable checkout mutations remain blocked. 4. A valid `branches/` worktree is accepted only when it: - is inside the repo's `branches/` directory - belongs to the target repository - exists on disk - is clean where cleanliness is required 5. Invalid worktrees fail closed with a clear error. 6. Error output distinguishes: - stable checkout rejected - no active worktree resolved - resolved worktree outside `branches/` - runtime_context/guard workspace mismatch 7. Tests cover: - stable checkout mutation rejected - valid `runtime_context` `branches/` worktree accepted - mismatch between `runtime_context` and guard path detected - non-`branches/` worktree rejected - deleted/missing worktree rejected - symlink/realpath edge cases do not bypass the guard ## Why this matters Without this fix, agents can correctly create and enter a valid `branches/` worktree but still be blocked because the mutation guard evaluates a different path than `runtime_context`. That creates false "blocked" reports and prevents valid guarded MCP mutations. ## Relation Follow-up to the #274 branches-only worktree guard. Related failure mode observed while filing #458 (guard false-fail resolved only by `/mcp reconnect`).
jcwalker3 added the status:in-progress label 2026-07-07 15:08:23 -05:00
Author
Owner

Issue claim heartbeat

<!-- gitea-issue-claim-heartbeat:v1 --> **Issue claim heartbeat** - kind: claim - issue: #460 - branch: pending - phase: claimed - profile: prgs-author - pr: none - blocker: none - next_action: create worktree and begin implementation
sysadmin removed the status:in-progress label 2026-07-07 16:53:09 -05:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

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