Files
Gitea-Tools/docs/label-taxonomy.md
T
sysadminandClaude Opus 4.8 ed0e8c82de fix(workflow): retire status:pr-open on every terminal PR transition (Closes #780)
status:pr-open was applied by gitea_create_pr and never removed again. Every
terminal path finished without touching it, so a repository audit found 40
closed issues still advertising an open PR.

Add terminal_pr_label_cleanup.py as the single authoritative rule and route
every sanctioned terminal path through it, so the paths cannot drift:

- merge (gitea_merge_pr)
- close without merge (gitea_edit_pr)
- supersession/abandonment (gitea_reconcile_superseded_by_merged_pr)
- already-landed reconciliation (gitea_reconcile_already_landed_pr)
- controller closure (gitea_close_issue)
- retry/recovery (new gitea_cleanup_terminal_pr_labels)

The rule removes only status:pr-open, preserves every other label, allows an
empty resulting set, is a no-op when the label is absent (so retries are
safe), and confirms the outcome by read-after-write rather than assumption.

Controller closure runs the cleanup before the state change and fails closed
if it cannot be completed and verified; closing first would bake in the stale
label with no later step to catch it. Post-merge cleanup never blocks the
merge, which already happened, and reports failures with a safe next action.

Also:
- gitea_assess_terminal_label_hygiene: read-only terminal validation that
  reports residual status:pr-open, exempting issues with a genuinely open PR.
- _put_issue_label_names now accepts Gitea's empty response body when the
  requested set is empty, so clearing the last label works.
- test_audit's close_issue fixture keys on the request instead of call order,
  since closing now also reads labels for the cleanup and its read-back.

Docs: label-taxonomy terminal-transition section, runbook pointer, and the
review-merge / reconcile-landed final-report terminal-label requirements.

Suite: 4045 passed, 11 failed, 6 skipped. The same 11 failures reproduce on
clean master df31674 (4010 passed, 11 failed) and are pre-existing.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-21 15:41:24 -04:00

8.4 KiB

Label Taxonomy

This document defines the canonical issue labels used by MCP workflows.

Every issue should carry:

  • one type:* label
  • one status:* label

Discussion-only issues must carry type:discussion.

Issue Type Labels

Label Use
type:bug Bug or defect
type:feature Feature or enhancement
type:process Process or policy work
type:workflow Workflow automation or guidance
type:guardrail Safety gate or guardrail
type:docs Documentation work
type:test Tests or test infrastructure
type:discussion Discussion-only issue
type:umbrella Umbrella or tracker issue
type:cleanup Cleanup or hygiene work

Workflow Status Labels

Only one status:* label should be active on an issue at a time. When an issue moves forward, tooling must remove the old status:* label and apply the new one.

Label Use
status:triage Issue needs triage
status:ready Issue is ready for work
status:claimed Issue is claimed
status:in-progress Issue is being worked on
status:blocked Issue is blocked
status:needs-review Issue work needs review
status:pr-open A linked PR is open
status:changes-requested Reviewer requested changes on the linked PR
status:approved Linked PR is approved
status:merged Linked PR is merged
status:reconcile Issue needs reconciliation
status:done Issue workflow is complete
status:duplicate Issue is a duplicate
status:wontfix Issue will not be fixed

Role Ownership Labels (#603)

A single role:* label shows which workflow role currently owns the item. It is advisory visibility only — the control-plane lease (#601) is the source of truth for mutation authority. Only one role:* label is active at a time; tooling replaces it on handoff via transition_role_labels.

Label Use
role:author Author currently owns the item
role:reviewer Reviewer currently owns the item
role:merger Merger currently owns the item

Hazard Labels (#603)

Hazard labels are orthogonal warning flags. Unlike status:* and role:*, more than one hazard may be active at once, and a hazard never substitutes for a live lease / PR-state check. Add/remove with add_hazard_label / clear_hazard_label.

Label Use
hazard:stale-lease A stale or expired lease references this item
hazard:workflow-contaminated Session/workflow state is contaminated; do not mutate
hazard:conflicted Linked PR has merge conflicts
hazard:root-mutation Work was mutated in the project root checkout
hazard:manual-state Session or lease state was edited manually
hazard:terminal-blocker A terminal review/merge lock blocks progress (#332/#602)

Any item that carries status:blocked or any hazard:* flag must also have a blocking-reason / next-action comment (requires_blocking_reason).

state:* → canonical mapping (#603 migration)

Issue #603 proposed a parallel state:* vocabulary. To avoid a conflicting second lifecycle prefix, those requested states are folded into the existing canonical labels rather than introduced as state:*. state:* is not a supported prefix; use the canonical label on the right.

Requested state:* Canonical label
state:needs-triage status:triage
state:claimed status:claimed
state:authoring status:in-progress
state:needs-review status:needs-review
state:reviewing status:needs-review
state:changes-requested status:changes-requested
state:approved status:approved
state:merge-ready status:approved
state:merged status:merged
state:blocked status:blocked
state:terminal-blocker hazard:terminal-blocker
state:abandoned status:wontfix

The transition helpers accept these names as synonyms (e.g. canonical_status_label("authoring")status:in-progress), so callers may use the #603 wording while a single canonical status stays active.

Allocator Cross-Check (#603)

Labels are advisory queue hints. The work allocator (#600/#613) uses labels as one signal but cross-checks live leases and PR state and never trusts labels alone. Discussion issues (type:discussion) are excluded from implementation queues (is_implementation_candidate) unless a controller explicitly selects them.

Transition Rules

Suggested lifecycle:

  1. New issue created: status:triage or status:ready
  2. Issue selected by an author: status:claimed
  3. Author starts work: status:in-progress
  4. Work is blocked: status:blocked
  5. PR opened: status:pr-open
  6. PR approved: status:approved
  7. PR merged but issue still needs closure/reconciliation: status:reconcile
  8. Issue fully complete: status:done
  9. Duplicate issue: status:duplicate
  10. Won't-fix issue: status:wontfix

The helper module issue_workflow_labels.py is the source of truth for the canonical label specs and status transition replacement behavior.

Terminal PR transitions retire status:pr-open (#780)

status:pr-open states that a linked PR is currently open. The moment that stops being true the label must go, whatever ended the PR:

Terminal reason Raised by
merged gitea_merge_pr
closed_without_merge gitea_edit_pr closing the PR
superseded gitea_reconcile_superseded_by_merged_pr
already_landed gitea_reconcile_already_landed_pr
controller_closure gitea_close_issue
abandoned abandonment handling
retry_recovery gitea_cleanup_terminal_pr_labels after a partial failure

All of these route through one rule in terminal_pr_label_cleanup.py, so the paths cannot drift apart. The rule guarantees:

  • only status:pr-open is removed — every other label is preserved verbatim;
  • an empty resulting label set is valid (it was the issue's only label);
  • an issue that no longer carries the label is a no-op, so retries are safe;
  • the result is confirmed by a read-after-write re-read, not assumed.

Controller closure runs the cleanup before changing issue state and fails closed if it cannot be completed and verified — closing first would bake in the stale label with no later step to catch it. Post-merge cleanup never blocks the merge: the transition already happened, so failures are reported with a safe_next_action instead.

Use gitea_assess_terminal_label_hygiene as terminal validation before declaring a transition or cleanup batch complete. It enumerates issues plus the live open PRs and reports any issue still carrying status:pr-open without an open PR to justify it. Issues with a genuinely open PR are exempt, not residual.

Recovery from a partial failure is gitea_cleanup_terminal_pr_labels with terminal_reason='retry_recovery'.

Discussion Issues

Discussion issues must be labeled type:discussion.

A discussion issue should not be treated as implementation-ready unless it also has a clear implementation status and next action.

If a discussion produces implementation work, either:

  1. convert the discussion issue into an implementation issue by changing labels and adding acceptance criteria, or
  2. create child implementation issues and leave the discussion issue as type:discussion.

Tooling

  • manage_labels.py --create-labels creates the canonical type:* and status:* labels.
  • gitea_create_issue recommends type:* and status:* labels when missing and can apply supplied label names.
  • gitea_mark_issue(..., action="start") replaces old status:* labels with status:in-progress.
  • gitea_create_pr fails closed before PR creation if status:pr-open cannot be applied to the locked issue, then applies it after the PR is created.
  • gitea_set_issue_labels accepts an explicit worktree_path so author sessions can satisfy the branches-only mutation guard while changing labels.
  • gitea_cleanup_terminal_pr_labels retires status:pr-open after a terminal PR transition; it is idempotent, so it is also the retry/recovery path.
  • gitea_assess_terminal_label_hygiene is the read-only terminal validation for residual status:pr-open.

Existing Non-Workflow Labels

Existing non-workflow labels such as mcp, workflow, labels, tracker, jenkins, glitchtip, documentation, and testing remain valid topical labels. They do not replace the required type:* and status:* labels.