The gitea-workflow documentation named a gitea_edit_issue tool that no
namespace had ever registered. There was no sanctioned MCP path to change an
issue title or body at all: the only edit tool, gitea_edit_pr, PATCHes the
pull-request endpoint. An authorized body correction on issue #780 therefore
had to be recorded as a discussion comment instead.
Add edit_issue.py as the authoritative rule and gitea_edit_issue as the tool
built on it:
- Only the fields the caller names are sent, so labels, state, assignee, and
milestone cannot be overwritten from a stale read.
- A pull-request number is refused. Gitea serves pull requests from the same
/issues/{n} collection, so without that check the issue path would quietly
become a second, ungated PR edit path. gitea_edit_pr stays PR-only.
- Structurally invalid requests raise before any credential or network work;
a request that would change nothing is reported as an explicit no-op with a
next action rather than a silent success.
- Read-after-write proves the applied title/body and proves that state,
labels, 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/verified state.
Gates match every other issue mutation: profile permission via the shared
capability map (resolver task edit_issue, gitea.issue.comment), preflight
purity, branches worktree validation, anti-stomp inventory membership, and
audited mutation.
Fix the drift that hid this. docs/mcp-tool-inventory.md is now the canonical
registered-tool list, and mcp_tool_inventory.py compares it to the live
registry in both directions, plus checks that every tool named under skills/
is registered. The new guard immediately found a second instance of the same
defect: gitea_record_pre_review_command had lost its @mcp.tool() decorator
while the canonical review workflow still instructed reviewers to call it, so
that registration is restored.
Validation:
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 8e149e6 with no changes applied
(254 passed, 11 failed across those five files), so they are pre-existing
and proven by a baseline run rather than asserted.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
16 KiB
name, description
| name | description |
|---|---|
| llm-project-workflow | Router skill for safe LLM project work: identify task mode, load the matching canonical workflow file, enforce mode isolation, and emit the correct final report schema. Use at the start of any implementation, review, merge, reconciliation, or issue-filing task. |
Also known as:
gitea-workflow,git-pr-workflows(canonical multi-runtime names — see docs/workflow-skill-mount.md / #551).
LLM Project Workflow Skill
This skill is a router. Do not perform project work from this file alone.
Before any project mutation, identify the task mode and load the matching workflow file.
Workflow modes
| Task mode | Workflow | Final report schema |
|---|---|---|
| PR review / approval / merge | workflows/review-merge-pr.md |
schemas/review-merge-final-report.md |
| Reconcile already-landed open PRs | workflows/reconcile-landed-pr.md |
schemas/reconcile-landed-final-report.md |
| Create or update Gitea issues | workflows/create-issue.md |
schemas/create-issue-final-report.md |
| Work on an assigned issue / author code | workflows/work-issue.md |
schemas/work-issue-final-report.md |
| PR-only queue cleanup (one canonical review per PR) | workflows/pr-queue-cleanup.md |
schemas/pr-queue-cleanup-final-report.md |
Universal rules
- Prove identity, active profile, runtime context, and exact capability before mutation.
- A nearby capability does not count.
- Do not self-review or self-merge.
- Never push a stable branch directly. Worker sessions (author/reviewer/merger)
must never run
git push <remote> master— ormain/dev/other stable refs, including refspecs (HEAD:master),--force,--delete, or--dry-runno-op probes — and must never commit on the root/control checkout outside an issue feature branch. Stable-branch updates land ONLY through sanctioned Gitea merge tooling (gitea_merge_pr) or an explicitly authorized reconciler path. A detected attempt marks the session workflow-contaminated (#671) and fails closed on review/merge/close/completion until a reconciler audits and clears it.git fetch/git pull --ff-onlyand feature-branch pushes stay allowed. - Do not mix modes in one run.
- BLOCKED + DIAGNOSE default rule (required): If any required workflow step, skill, tool, capability, preflight, instruction, profile, worktree binding, or terminal/MCP operation cannot be performed or loaded (including the canonical ones listed in this skill and its loaded workflow), immediately enter
BLOCKED + DIAGNOSE. Stop before any git or Gitea mutation. Diagnose using the standard template intemplates/blocked-diagnose-report.md. Attempt only safe non-mutating recovery. Report using the template. Do not continue, use fallbacks, or treat the missing requirement as harmless. - If the required workflow cannot be loaded, stop and produce a recovery handoff only (see BLOCKED + DIAGNOSE rule above).
- Final report must use the schema for the loaded workflow.
- If a task requires a different mode, stop and produce a handoff for the correct workflow.
Covered blocker classes (BLOCKED + DIAGNOSE must trigger for these)
- missing required skill or workflow guide (e.g. gitea-workflow, llm-project-workflow)
- broken terminal/tool runner or shell spawn failure
- MCP capability failure, reset, or deadlock (e.g. preflight state cleared)
- wrong profile or role for the requested operation
- dirty or misbound worktree (root checkout or non-branches/ path)
- root checkout mutation risk
- stable-branch push contamination (#671): a direct
git push <remote> master(ormain/dev) equivalent, or a root/control-checkout commit not on an issue feature branch, marks the session workflow-contaminated; review/merge/close/ completion mutations then fail closed until a reconciler audits and clears it (gitea_audit_stable_branch_contamination action=clear, reconciler-only) - mutation guard failure (e.g. branches-only guard)
- missing required MCP tool/schema or operation
- stale or inconsistent runtime state (e.g. lease vs actual, dirty state disagreement)
- unavailable project instructions or checked-in guides
- any other failure of a step the current workflow or controller prompt declares "required"
Prohibited unless controller authorizes in writing for this instance: temp scripts, direct API fallback, MCP internals, direct imports, in-memory state restoration, manual bypasses, or any continuation that hides the blocker. All such cases must be reported as process/tooling defects.
Mode isolation
A run that starts in review-merge-pr mode may not create process issues,
implement fixes, or edit source files.
A run that starts in reconcile-landed-pr mode may not approve, request
changes, merge, implement fixes, or create normal issues.
A run that starts in create-issue mode may not review, approve, request
changes, merge, implement fixes, create branches, commit, push, or create PRs.
A run that starts in work-issue mode may not review, approve, request changes,
merge, close PRs, or act as reviewer.
A run that starts in pr-queue-cleanup mode may not claim issues, create
branches, edit implementation files, file new issues, or review a second PR
after any terminal review mutation.
If the task requires a different mode, stop and produce a handoff for the correct workflow.
Definitions
- Merged: Gitea PR metadata says
merged=true. - Landed: Equivalent content is present on remote
master, but PR metadata may not say merged. - Closed-not-merged: PR state is closed and
merged=false. - Reconciled: Verified whether closed-not-merged or already-landed content is present on the target branch; issue/label/tracker state repaired.
Work Selection Rule for LLMs
Before starting any issue or PR work, acquire or verify a work lease. Do not begin coding, reviewing, fixing, branching, committing, pushing, commenting, or creating a PR until you prove the target is not already being worked.
Required checks:
- List open PRs.
- Search for PRs linked to the target issue.
- Search local and remote branches for the issue number.
- Search registered worktrees for the issue branch.
- Check dirty worktrees.
- Check active leases or recent handoffs.
- Check whether the issue was already completed by a merged PR.
If another active session owns the lease, stop with "work already claimed" or produce a handoff.
For Gitea-Tools: gitea_lock_issue is the fail-closed lease gate before author
mutations; status:in-progress and claim comments are supporting lease signals.
Global LLM Worktree Rule
The main project checkout is a stable control checkout on master, main, or
dev. All LLM task work must happen inside the project's branches/ directory.
If cwd is not inside branches/, stop before any file edit, test write,
commit, merge, rebase, or cleanup. The main checkout is orchestration-only.
Stable Branch Push Protection (#671)
Worker sessions must never publish a stable branch directly. This is the
prevention hardening for the #670 incident (a bare direct-to-master commit
2fa97c26 and a PR #654 merger git push prgs master attempt).
Forbidden for author/reviewer/merger sessions:
git push <remote> master(andmain,dev,develop,development), including refspecs (HEAD:master,+refs/heads/x:refs/heads/master),--force,--delete/:master, and--dry-run/-nno-op probes (a dry-run still proves intent and contaminates the session).- Local commits on the root/control checkout that are not carried by an issue
feature branch under
branches/.
Allowed (never blocked):
git fetchandgit pull --ff-onlyof master into the control checkout when authorized for sync.- Feature-branch pushes to non-stable refs (
git push <remote> fix/issue-N-...). - Sanctioned merges via
gitea_merge_pr/ the Gitea API merge endpoint — the only way stable branches advance.
What happens on a detected attempt: the session is marked
workflow-contaminated (durable stable_branch_contamination marker, redacted
command summary + session id + remote + ref). While contaminated, all
review / merge / close / issue-completion mutations fail closed. comment_issue
and lock_issue remain allowed so the contaminated worker can post the durable
audit comment and hand off. Contamination cannot be self-cleared — only a
reconciler audit (gitea_audit_stable_branch_contamination action=clear) may
clear it.
Tooling: call gitea_record_stable_branch_push_attempt to classify/record a
proposed push before running it; gitea_audit_stable_branch_contamination to
inspect or (reconciler-only) clear the marker.
Shell Spawn Hard-Stop Rule
exit_code: -1 with empty stdout/stderr means the shell failed to spawn — not a
command failure. After two consecutive spawn failures, hard-stop shell use for
the session and emit a recovery report (#258).
Isolated worktree naming
Implementation: (fix|feat|docs|chore)/issue-<number>-<short-description>
Review: review/pr-<number>-<short-description>
Subagent Tool-Budget Guardrails
General-purpose subagents on single-step MCP tasks (for example
gitea_commit_files) must not expand into 100+ tool-call retry spirals with
WebFetch/Playwright/manual-encoding fallbacks (issue #259).
Default budgets (stop when exceeded):
- Single-step MCP mutation (
commit_files,create_pr,lock_issue): 15 tool calls, 5 minutes wall time. - Review / merge queue inspection: 40 tool calls, 15 minutes.
- Non-mutating exploration: 60 tool calls, 20 minutes.
Rules:
- When the main session has
gitea.repo.commit, callgitea_commit_filesdirectly — do not delegate commit to a subagent (#260). - After shell spawn failure (#258), attempt the native MCP tool once before any fallback; shell unavailability never authorizes WebFetch/Playwright/ manual base64.
- Never resume a failed subagent into a larger retry loop or spawn a second subagent for the same deterministic step — stop and report.
- When
gitea_commit_filesis available, forbid WebFetch, Playwright, manual encoding, and ad-hoc_encode_*/_emit_*helpers in the repo.
Worktree folder: branch with / replaced by - under branches/.
Helpers: scripts/worktree-start, scripts/worktree-review,
scripts/worktree-clean.
Identity and profile safety
- Author and reviewer identities must be distinct.
- Never place raw tokens in LLM/MCP config.
- Use
gitea_whoamiandgitea_resolve_task_capabilitybefore mutating.
Tool inventory
docs/mcp-tool-inventory.md is the canonical
list of registered tools, held equal to the live registry by a test. A tool that
is not listed there does not exist — do not scope work around it (#781).
Issue content is edited with gitea_edit_issue (title/body only, read-after-write
verified). gitea_edit_pr is pull-request-only and never accepts an issue number.
Controller Handoff
Every task must end with a section titled exactly Controller Handoff. Compact
format canonical field set per issue #182; mode-specific schemas in
schemas/*-final-report.md define required fields. Use the final report schema
for the loaded workflow mode — not the legacy compact block alone.
review_proofs.assess_controller_handoff() validates presence.
Canonical self-propagating handoff
Every workflow mode also carries the cross-role handoff block defined in
schemas/self-propagating-handoff.md
(#626). Each actor consumes exactly one canonical handoff, performs exactly one
authorized role, posts the result to the Gitea issue or PR thread, and emits the
next complete handoff — until the controller records final closure. The block
must be posted to Gitea, not returned in chat alone, and the next prompt is not
an optional prose section. self_propagating_handoff.py implements the schema;
final_report_validator.py enforces it as shared.self_propagating_handoff.
Prompt templates
Ready-to-copy task prompts live in templates/:
start-issue.md— author work (loadswork-issue.md)review-pr.md— review (loadsreview-merge-pr.md)pr-queue-cleanup.md— one PR per cleanup runmerge-pr.md— merge (loadsreview-merge-pr.md)recover-bad-state.mdreconcile-closed-not-merged-pr.mdworktree-cleanup.mdrelease-tag.mdcanonical-state-comments.md
Adapting to a project
| Placeholder | Example here |
|---|---|
<remote> |
prgs |
| default branch | master |
| profile env vars | GITEA_MCP_CONFIG, GITEA_MCP_PROFILE |
branches/ |
branches/ |
| helpers | scripts/worktree-start / -review / -clean |
Versioning And Tagging
Releases follow SemVer from remote master only, after full test suite passes.
See templates/release-tag.md and
scripts/release-tag.
Proof: missing required workflow steps stop before mutation
- The llm-project-workflow router (this file) and every loaded workflow (work-issue.md, review-merge-pr.md, create-issue.md, etc.) now declare at the top: if required step/skill/tool/capability/instruction/profile/worktree binding/preflight fails, STOP, state BLOCKED, use blocked-diagnose-report.md template, only non-mutating recovery.
- Controller prompts (start-issue.md, review-pr.md, merge-pr.md, recover-bad-state.md, etc.) and the runbooks (docs/llm-workflow-runbooks.md) explicitly require the same and prohibit unsafe fallbacks.
- MCP guards (branches-only mutation guard #274, worktree binding #510, preflight purity, role checks, lease gates, gitea_lock_issue, etc.) plus the "prove before mutation" rules ensure that a BLOCKED state prevents git/Gitea mutations.
- When a skill/guide/tool is missing (e.g. gitea-workflow not mounted for a runtime), the load step in the router/prompt fails the "required" check → BLOCKED + report before any gitea_* call or git command that mutates.
- Terminal/shell failures, capability deadlocks, wrong profile, dirty/misbound worktree, root risk, guard failures, missing schema, stale state, unavailable instructions all map to the covered blocker classes and trigger the same stop + report.
- No code path in the canonical workflows allows continuation past a declared required step without the BLOCKED report.
- See also: Global LLM Worktree Rule, Shell Spawn Hard-Stop Rule, Identity and profile safety, Subagent Tool-Budget Guardrails, and the explicit prohibition list in Universal rules.
Tests / proof docs updated in this change + runbooks. Full relevant test runs (see PR handoff) pass; git diff --check clean. Missing-step cases are now documented to fail closed before mutation.
Bootstrap Review Path (#557)
Self-hosted MCP workflow fixes can deadlock live review daemons. Do not bypass gates with raw API, direct imports, or root checkout edits.
If and only if a controller posts a durable BOOTSTRAP REVIEW AUTHORIZATION (#557) record, follow:
docs/bootstrap-review-path.md
Otherwise stop with BLOCKED + DIAGNOSE. Bootstrap never weakens normal PR gates.