fix(mcp): forward worktree_path into publication preflight (Closes #815)

gitea_publish_unpublished_issue_branch accepted a required worktree_path
but resolved it only after verify_preflight_purity had already run, so the
#618 branches-only guard and every other workspace-resolution layer behind
preflight received None and fell back to the MCP process root. A daemon
rooted at the stable control checkout therefore refused a valid registered
issue worktree the caller had explicitly supplied, before the publication
assessor could use it — the sole verify_preflight_purity call site that
accepted a worktree argument and dropped it.

Resolve the workspace once, before preflight, and forward it. A blank or
absent path forwards None and keeps the ordinary #618 fail-closed fallback,
so guard strictness is unchanged for missing, empty, unregistered, foreign,
or control-checkout worktrees. Public tool contract, ownership, cleanliness,
hash, ancestry, and read-after-write protections from PR #814 are untouched.

Adds tests/test_issue_815_preflight_worktree_forwarding.py: a #735-style
capture proving the argument reaches verify_preflight_purity, a faithful
production reproduction (control-rooted daemon, no session lock, explicit
worktree) that clears #618 on the fixed source and is trapped at #618 on the
unpatched source, an end-to-end control-rooted publication in the real
topology (PROJECT_ROOT is the stable control checkout, the issue worktree is
a distinct registered path, production guards forced on), and negative
coverage keeping every #618 and assessor refusal intact. The prior #812
suite masked the defect by patching PROJECT_ROOT to equal the issue worktree.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-22 17:27:49 -05:00
co-authored by Claude Opus 4.8
parent 910b6edbdc
commit 95a5eb254f
2 changed files with 640 additions and 2 deletions
+18 -2
View File
@@ -9173,11 +9173,27 @@ def gitea_publish_unpublished_issue_branch(
if blocked:
return blocked
verify_preflight_purity(remote, task=task, org=org, repo=repo)
# #815: resolve the caller's worktree *before* preflight and forward it, so
# every workspace-resolution layer behind verify_preflight_purity — including
# the #618 branches-only guard — judges the registered issue worktree this
# publication actually operates on. Resolving it afterwards let preflight
# fall back to the MCP process root, so a daemon rooted at the stable control
# checkout refused a valid explicit worktree before the assessor ever ran.
# A caller supplying nothing usable forwards None and keeps the ordinary
# fail-closed fallback.
explicit_worktree = (worktree_path or "").strip()
workspace = os.path.realpath(os.path.abspath(explicit_worktree or "."))
verify_preflight_purity(
remote,
worktree_path=workspace if explicit_worktree else None,
task=task,
org=org,
repo=repo,
)
h, o, r = _resolve(remote, host, org, repo)
git_remote = (git_remote_name or remote or "").strip()
workspace = os.path.realpath(os.path.abspath((worktree_path or "").strip() or "."))
existing_lock = issue_lock_store.load_issue_lock(
remote=remote, org=o, repo=r, issue_number=int(issue_number)