fix(mcp): add sanctioned gitea_edit_issue and a doc/registry drift guard (Closes #781) #783
Labels
Clear labels
allocator
anti-stomp
architecture
bug
chore
codex
concurrency
contamination
control-plane
dashboard
database
design
documentation
enhancement
gitea
glitchtip
important
incident
incident-bridge
integration
jenkins
labels
leases
mcp
mcp-health
mcp-menu
multi-project
mutating
nice-to-have
observability
portability
preflight
protected-branch
queue
read-only
reconnect
recovery
refactor
release
reliability
resumable-review
reviewer
roadmap
safety
security
self-hosted
sentry
stale-runtime
status:blocked
status:in-progress
status:pr-open
status:ready
terminal-lock
testing
tracker
type:bug
type:feature
type:feature
type:guardrail
visibility
workflow
workflow-hardening
workflow-hardening
Controller-owned work allocator
Prevent concurrent LLM session stomping
Architecture / structural design
OpenAI Codex client / workflow session surface
Concurrent session safety
Workflow or session contamination incident
MCP control-plane coordination and allocation authority
MCP operational dashboard/queue view
Internal coordination storage (SQLite/Postgres)
Design / investigation, no implementation
Docs / runbooks
New feature or improvement
Gitea MCP workflow
GlitchTip integration
Operational or process incident requiring durable audit trail
Sentry-to-Gitea incident bridging
Integration testing
Jenkins integration
Label taxonomy management
Lease adopt/release/expire lifecycle
MCP server / tooling
MCP namespace and runtime health
MCP menu surface
Work spanning multiple monitoring projects or Gitea repos
Mutating action; requires gating
Observability, metrics, traces, error reporting
Cross-platform / portability
Shared preflight gates before mutation
Protected branch / stable-branch policy concern
Work queue visibility and allocation
Read-only, no mutation
MCP client reconnect/reload recovery path
Recovery paths for stale/foreign leases
Code refactor / restructure
Release / versioning
Reliability / failure handling
Persist and resume prepared review verdicts across sessions
Reviewer workflow tooling
Roadmap / umbrella issue
Safety rails and fail-closed mutation guards
Security / trust boundary
Self-hosted infrastructure integration
Sentry error monitoring integration
Stale backend daemon / runtime-vs-master parity failures
Issue is blocked
Issue is being worked on
Issue has an open pull request
Issue is ready for work
Terminal review lock (#332) path
Tests / test coverage
Issue tracker hygiene / meta
Bug or defect
Feature or enhancement
Feature or enhancement
Safety gate or guardrail
Workflow state visibility for LLMs/operators
Cross-tool workflow
LLM workflow coordination hardening
LLM workflow coordination hardening
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#783
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #781
Head:
a002864a06c52a5557de5d0b0a4552c790319318Base:
master@8e149e6cfa3557e4642a3ac72c971460adbc3037Problem
The workflow documented a
gitea_edit_issuetool that no namespace had everregistered, so there was no sanctioned MCP path to change an issue title or
body. The only edit tool,
gitea_edit_pr, PATCHes the pull-request endpointand cannot target an issue. An authorized body correction on #780 had to be
recorded as a discussion comment instead.
What this adds
edit_issue.pyholds the rule;gitea_edit_issueis the tool built on it.milestone cannot be overwritten from a stale read.
/issues/{n}collection too, so without that check the issue path wouldquietly become a second, ungated PR edit path.
gitea_edit_prstayspull-request-only and gains no issue awareness.
A request that would change nothing is reported as an explicit no-op with a
next action, never as a silent success.
assignees, and milestone did not move. Transport failures on the pre-read,
the PATCH, and the read-back are each reported (redacted) with the correct
performed/verifiedstate.Gates match every other issue mutation: profile permission through the shared
capability map (resolver task
edit_issue,gitea.issue.comment), preflightpurity,
branches/worktree validation, anti-stomp inventory membership, andaudited mutation.
Drift guard
docs/mcp-tool-inventory.mdis now the canonical registered-tool list.mcp_tool_inventory.pycompares it to the live registry in both directions andchecks that every tool named under
skills/is registered, so documentation cannever again name a tool an actor cannot reach.
The guard immediately found a second instance of that defect:
gitea_record_pre_review_commandhad lost its@mcp.tool()decorator while thecanonical review workflow still instructs reviewers to call it. Its registration
is restored — one line, required for the new guard to pass.
Files
edit_issue.py(new) — validation, PR-target refusal, planning, verificationmcp_tool_inventory.py(new) — the drift ruledocs/mcp-tool-inventory.md(new) — canonical inventorytests/test_issue_781_edit_issue_tool.py(new) — 50 testsgitea_mcp_server.py—gitea_edit_issue; restored decoratortask_capability_map.py,anti_stomp_preflight.py— gate wiringskills/gitea-workflow/SKILL.md,skills/llm-project-workflow/SKILL.md— docsValidation
PASSED:
venv/bin/python -m pytest tests/test_issue_781_edit_issue_tool.py -s -q— 50 passed.
FAILED:
venv/bin/python -m pytest -s -q— 4095 passed, 11 failed, 6 skipped.The identical 11 tests fail on clean master
8e149e6with no changes applied(254 passed, 11 failed across those five files), so the failures are
pre-existing and proven by a baseline run rather than asserted:
Reviewer notes
The new tool is not live in any running daemon until the checkout advances and
the namespaces restart; the running server keeps its startup code in memory.
Controller Handoff
fix/issue-781-mcp-edit-issue-toolauthor_issue_work, claimantjcwalker3/prgs-authora002864a06c52a5557de5d0b0a4552c790319318repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #783
issue: #781
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 74231-4ae8546a5e84
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-783
phase: claimed
candidate_head:
a002864a06target_branch: master
target_branch_sha:
8e149e6cfalast_activity: 2026-07-21T20:54:25Z
expires_at: 2026-07-21T21:04:25Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #783
issue: #781
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 74231-6357d9464958
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-783
phase: claimed
candidate_head:
a002864a06target_branch: master
target_branch_sha:
8e149e6cfalast_activity: 2026-07-21T21:08:43Z
expires_at: 2026-07-21T21:18:43Z
blocker: none
Independent review of PR #783 at the exact published head
a002864a06c52a5557de5d0b0a4552c790319318, basemaster@8e149e6cfa3557e4642a3ac72c971460adbc3037.Reviewer
sysadmin/prgs-reviewer; PR authorjcwalker3/prgs-author. Distinct identities, self-review not possible.Validation
Working directory: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-783
Command path: venv/bin/python from the repository virtualenv
PASSED:
venv/bin/python -m pytest tests/test_issue_781_edit_issue_tool.py -s -qata002864— 50 passed, exit status 0.FAILED:
venv/bin/python -m pytest -s -qata002864— 4095 passed, 11 failed, 6 skipped, exit status 1.Pre-merge baseline proof
The eleven failures are pre-existing. I proved that on the PR's pre-merge base commit, in a separate detached worktree, not by narrative.
Pre-merge base commit:
8e149e6cfaTested commit:
8e149e6cfaWorking directory: /Users/jasonwalker/Development/Gitea-Tools/branches/review-baseline-783
Command: venv/bin/python -m pytest -s -q tests/test_commit_payloads.py tests/test_issue_702_review_findings_f1_f6.py tests/test_mcp_server.py tests/test_post_merge_moot_lease.py tests/test_reconciler_supersession_close.py
Exit status: 1
Failure signature: 11 failed, 254 passed — the identical eleven test ids listed below, with no PR change applied
The failure set at the PR head is unchanged from the pre-merge base, so this PR introduces no new failure.
Stale-head proof
Reviewed head SHA:
a002864a06Live head SHA before approval:
a002864a06Pushes occurred during validation: no
What I verified
Scope: 9 files, 1473 insertions, 0 deletions, one commit. Every file traces to an acceptance criterion in #781. No unrelated file is touched, and
gitea_edit_pris not modified anywhere in the diff.gitea_edit_issuebuilds its PATCH payload only from fields the caller named, so an unspecified field is never sent; labels, state, assignees, and milestone cannot be clobbered by a stale pre-image read./issues/{n}, so without that refusal this path would have become a second, ungated PR-edit route. The refusal happens before the PATCH.no_opwith a next action and issues no PATCH.performed/verifiedstate.Gate wiring: the tool gates on
task_capability_map.required_permission("edit_issue"), runsverify_preflight_purity(task="edit_issue"), andedit_issueis declared inanti_stomp_preflight.MUTATION_TASKS. The existing inventory-versus-wiring and resolver-alignment tests cover the new entries and pass.Audit path, exercised with auditing genuinely on: the unit tests patch
audit_enabledoff, so the audited branch was untested at the sink. I drove the tool directly with an audit sink configured and confirmed one record is emitted with"action": "edit_issue","result": "succeeded","request_metadata": {"fields": ["title"]}. No token, header, or credential material appears in the record.Restored registration:
gitea_record_pre_review_commandwas defined without its@mcp.tool()decorator while the canonical review workflow instructs reviewers to call it. Restoring it is one line, the body is unchanged, and it is read-only with respect to the Gitea API.Drift guard:
mcp_tool_inventorycomparesdocs/mcp-tool-inventory.mdto the live registry in both directions and checks every tool named underskills/. The documented list matches the 111 registered tools, and the guard fails on a seeded mismatch in either direction.Non-blocking observations
edit_issue.pysits at the repository root alongsideclose_issue.pyandmark_issue.py, which are argparse CLI scripts, but it is a pure-logic module with no entry point. Cosmetic only:reviewer_fallback.LOCAL_GITEA_SCRIPT_NAMESis a fixed tuple that does not include the new file, so nothing misclassifies it as a shell fallback.edit_issueis absent fromnative_mcp_preference.GITEA_MUTATION_TASKS,role_session_router.AUTHOR_TASKS, andsubagent_gate. This matches the precedent set bycleanup_terminal_pr_labelswhen #780 merged, so it is not a regression introduced here. Those registries deserve their own issue rather than scope growth on this one.Prior-landing gate. This is a validator defect, not a property of this PR.None of these change behaviour or weaken a gate, so none blocks this verdict.
Canonical PR State
STATE:
approved-awaiting-merge
WHO_IS_NEXT:
merger
NEXT_ACTION:
Merge PR #783 into master only after re-pinning the live head and confirming it still equals
a002864a06and that this approval is the current review.NEXT_PROMPT:
WHAT_HAPPENED:
Independent review of the exact published head. Scope, gate wiring, read-after-write behaviour, audit emission, and the pre-existing-failure baseline were each verified by command evidence rather than narrative.
WHY:
The change satisfies every acceptance criterion in #781, introduces no new test failure against a pre-merge base proof, and adds no capability beyond issue title/body editing.
ISSUE:
781
HEAD_SHA:
a002864a06REVIEW_STATUS:
approved review posted to gitea
MERGE_READY:
yes
BLOCKERS:
none
VALIDATION:
50 passed for the new suite; 4095 passed / 11 failed / 6 skipped for the full suite, with the identical 11 failures proven on the pre-merge base commit
8e149e6cfaNATIVE_REVIEW_PROOF: transport=native_mcp; entrypoint=mcp_server; namespace=gitea-reviewer; profile=prgs-reviewer; identity=sysadmin; reviewer_lease_comment=13682; expected_head=a002864a06c52a5557de5d0b0a4552c790319318
LAST_UPDATED_BY:
sysadmin / prgs-reviewer
[THREAD STATE LEDGER]
What is true now
server-side decision state: approved review posted to gitea
local verdict/state: approve verdict prepared locally, then posted
PR #783 is open at head
a002864a06, mergeable against master8e149e6cfa.merge not performed.
What changed
server-side mutation ledger: reviewer PR lease comment 13680 on PR #783, which reached its ten-minute TTL during validation and was released through the sanctioned path; replacement reviewer PR lease comment 13682 on PR #783; this approving review verdict on PR #783. server-side state changed. No file, worktree, index, or git ref was mutated by the reviewer.
What is blocked
blocker classification: no blocker
Nothing prevents the merge. The new tool is not reachable from any running daemon until the checkout advances past this merge and the namespaces restart, which is an expected operator step, not a defect in this change.
Who/what acts next
next actor: merger
required action: merge PR #783 into master after re-pinning the live head to
a002864a06and confirming this approval is the current review.Do not do: do not merge a different head; do not amend, rebase, squash, or force-push the branch; do not close issue #781 as part of the merge, because controller closure is a separate step after the merge lands; do not re-review or post a second verdict on this PR.
Canonical Handoff
Controller Handoff
8e149e6cfa, mutation gate enforceda002864a06a002864a06a002864a068e149e6cfaa002864a06is absent from master history, so the normal review path appliesa002864a06a002864a06and confirming this approval is the current reviewa002864a06, base master8e149e6cfa, approved by sysadmin / prgs-reviewer on that exact head. Use the gitea-merger namespace, verify identity, profile, repository binding, namespace health, runtime parity, and merge eligibility, re-read the PR immediately before merging, and stop if the live head differs.a002864a06and confirming this approval is the current reviewadopted_at: 2026-07-21T21:14:42Z
adopted_by_identity: sysadmin
adopted_by_profile: prgs-merger
adopted_from_session_id: 74231-6357d9464958
adopted_from_profile: prgs-reviewer
adopted_from_reviewer_identity: sysadmin
adopted_from_comment_id: 13682
adoption_reason: merger-handoff-approved-head
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #783
issue: #781
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 74238-35888d9c0524
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr-783
phase: adopted
candidate_head:
a002864a06target_branch: master
target_branch_sha:
8e149e6cfalast_activity: 2026-07-21T21:14:42Z
expires_at: 2026-07-21T21:24:42Z
blocker: none
Stale #332 review-decision lock cleanup (#594)
Status: APPLIED
sysadminprgs-merger2026-07-21T21:15:05.203148+00:00approveon PR fix(mcp): add sanctioned gitea_edit_issue and a doc/registry drift guard (Closes #781) (#783)closed(merged=True)300e8acd13cca2482d24dc83c4f24341fe3e127e1prgs-reviewerManual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #783
issue: #781
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 74238-35888d9c0524
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/merge-pr-783
phase: released
candidate_head:
a002864a06target_branch: master
target_branch_sha:
8e149e6cfalast_activity: 2026-07-21T21:16:19Z
expires_at: 2026-07-21T21:26:19Z
blocker: post-merge-moot