Fix paginated repository-label resolution in gitea_set_issue_labels #627

Closed
opened 2026-07-10 11:45:39 -05:00 by jcwalker3 · 1 comment
Owner

Problem

gitea_set_issue_labels performs full-set label replacement but validates requested label names against an incomplete repository-label listing.

In repositories with more labels than Gitea returns on a single page, valid labels that appear only on later pages are classified as nonexistent. That blocks safe preservation of the existing label set and blocks reconciliation workflows.

Reproduction (Issue #601 / PR #625)

Observed during post-merge reconciliation of:

Field Value
Repository Scaled-Tech-Consulting/Gitea-Tools
Issue #601 (closed; lease lifecycle landed via PR #625)
PR #625 (merged)
Reconciliation ledger comment 9455

Intended mutation: remove stale status:pr-open while preserving valid labels including type:feature and workflow-hardening.

Sanctioned tool error:

The following labels do not exist on the repository:
['type:feature', 'workflow-hardening']

Those labels do exist on the repository (confirmed via gitea_list_labels, which uses api_get_all). They appear among later-named labels and can fall beyond the first page when the repo label inventory exceeds Gitea's effective per-page maximum.

Live residual state: #601 remains closed but still carries status:pr-open because full-set replacement could not complete safely. Do not treat incomplete #601 label cleanup as ownership of this defect — this issue owns the systemic tool fix; a later reconciler session unblocks #601 label hygiene after this fix is merged and the MCP runtime is refreshed.

Code diagnosis (verified from repository source)

In gitea_mcp_server.py, gitea_set_issue_labels resolves names → IDs with a single-page fetch:

existing = api_request("GET", f"{base}/labels?limit=100", auth)
name_to_id = {lb["name"]: lb["id"] for lb in existing}

Related single-page label fetches also appear in other paths (for example other labels?limit=100 call sites and CLI helpers). By contrast:

  • gitea_list_labels already uses api_get_all(f"{base}/labels", auth).
  • gitea_auth.api_get_all documents that Gitea caps per-page results at 50 and force-clamps page_size > 50 to 50, then walks pages until a short page.

Therefore requesting limit=100 on a single GET does not guarantee a complete label inventory. Labels beyond the first returned page are treated as missing and the full-set replacement aborts before mutation.

Impact

Any operation that replaces a complete label set can fail when one or more requested names are not present on the first API page.

Affected workflows may include:

  • post-merge reconciliation;
  • canonical issue-state transitions;
  • controller closure;
  • label hygiene;
  • workflow-state updates;
  • bulk or exact label replacement.

Unsafe workaround (forbidden): callers must not submit only first-page-visible labels. Because the operation replaces the entire set, that could silently drop valid metadata.

Required implementation investigation

Inspect and fix as needed:

  • gitea_set_issue_labels
  • repository-label lookup helpers and other labels?limit=100 call sites
  • gitea_auth.api_get_all pagination conventions
  • label existence / name-to-ID resolution
  • post-mutation response verification
  • related task capability and mutation guards (set_issue_labels, preflight purity)

Likely direction: replace single-page lookup with a canonical paginated helper such as api_get_all(f"{base}/labels", auth). Prefer the repository's sanctioned helper; do not invent a one-off pagination loop if api_get_all already is the standard.

Also consider duplicate label names (this repo currently has duplicate type:feature and workflow-hardening label IDs) so name→id mapping remains deterministic and fail-closed where ambiguous.

Acceptance criteria

  1. Repository-label resolution retrieves every page.
  2. Valid labels beyond the first page are accepted.
  3. Invalid label names are still rejected before mutation.
  4. Full-set replacement preserves requested labels regardless of page position.
  5. Existing labels are not silently dropped.
  6. The operation continues to use sanctioned authentication and API helpers only.
  7. Capability, namespace, runtime, and mutation guards remain enforced.
  8. Pagination failures fail closed with an actionable error.
  9. Post-mutation verification confirms the final issue-label set.
  10. Tests cover:
    • repository with fewer than one page of labels;
    • repository with exactly one full page;
    • repository with more than one page;
    • requested labels split across pages;
    • missing requested label;
    • duplicate label names or IDs if relevant;
    • API failure on a later page;
    • complete-set replacement preserving later-page labels.
  11. A regression test reproduces the Issue #601 failure mode (type:feature / workflow-hardening style later-page names rejected under single-page inventory).
  12. After the fix is merged and the MCP runtime is refreshed, Issue #601 reconciliation can remove status:pr-open while retaining all valid labels.

Linkage

  • Issue #601 — closed feature work; post-merge label reconciliation remains incomplete pending this fix.
  • PR #625 — merged implementation of #601 that exposed the reconciler path.
  • Comment 9455 — reconciliation ledger recording the sanctioned-tool failure.

Ownership boundary:

  • #601 is already closed; do not reopen it for this tool defect.
  • This issue owns the systemic gitea_set_issue_labels pagination correction.
  • Completing this issue unblocks a later reconciler session for residual #601 label hygiene.

Non-goals

  • Do not implement ad-hoc label edits on #601 from this issue's author session without the fixed tool.
  • Do not weaken full-set replacement semantics.
  • Do not bypass MCP with direct tokens/scripts for production label mutations.
  • Diagnostic-bypass hardening for reconciler scratch scripts is out of primary scope (see closed related process issues #558 / #539); track residual process gaps separately if needed.

Suggested labels

bug, type:guardrail, status:ready, labels, mcp, workflow-hardening

## Problem `gitea_set_issue_labels` performs **full-set label replacement** but validates requested label names against an **incomplete repository-label listing**. In repositories with more labels than Gitea returns on a single page, valid labels that appear only on later pages are classified as nonexistent. That blocks safe preservation of the existing label set and blocks reconciliation workflows. ## Reproduction (Issue #601 / PR #625) Observed during **post-merge reconciliation** of: | Field | Value | |-------|-------| | Repository | `Scaled-Tech-Consulting/Gitea-Tools` | | Issue | **#601** (closed; lease lifecycle landed via PR **#625**) | | PR | **#625** (merged) | | Reconciliation ledger comment | **9455** | Intended mutation: remove stale `status:pr-open` while preserving valid labels including `type:feature` and `workflow-hardening`. Sanctioned tool error: ```text The following labels do not exist on the repository: ['type:feature', 'workflow-hardening'] ``` Those labels **do exist** on the repository (confirmed via `gitea_list_labels`, which uses `api_get_all`). They appear among later-named labels and can fall beyond the first page when the repo label inventory exceeds Gitea's effective per-page maximum. Live residual state: #601 remains **closed** but still carries `status:pr-open` because full-set replacement could not complete safely. **Do not treat incomplete #601 label cleanup as ownership of this defect** — this issue owns the systemic tool fix; a later reconciler session unblocks #601 label hygiene after this fix is merged and the MCP runtime is refreshed. ## Code diagnosis (verified from repository source) In `gitea_mcp_server.py`, `gitea_set_issue_labels` resolves names → IDs with a **single-page** fetch: ```python existing = api_request("GET", f"{base}/labels?limit=100", auth) name_to_id = {lb["name"]: lb["id"] for lb in existing} ``` Related single-page label fetches also appear in other paths (for example other `labels?limit=100` call sites and CLI helpers). By contrast: * `gitea_list_labels` already uses `api_get_all(f"{base}/labels", auth)`. * `gitea_auth.api_get_all` documents that **Gitea caps per-page results at 50** and force-clamps `page_size > 50` to 50, then walks pages until a short page. Therefore requesting `limit=100` on a single GET does **not** guarantee a complete label inventory. Labels beyond the first returned page are treated as missing and the full-set replacement aborts before mutation. ## Impact Any operation that replaces a complete label set can fail when one or more requested names are not present on the first API page. Affected workflows may include: * post-merge reconciliation; * canonical issue-state transitions; * controller closure; * label hygiene; * workflow-state updates; * bulk or exact label replacement. **Unsafe workaround (forbidden):** callers must **not** submit only first-page-visible labels. Because the operation replaces the entire set, that could silently drop valid metadata. ## Required implementation investigation Inspect and fix as needed: * `gitea_set_issue_labels` * repository-label lookup helpers and other `labels?limit=100` call sites * `gitea_auth.api_get_all` pagination conventions * label existence / name-to-ID resolution * post-mutation response verification * related task capability and mutation guards (`set_issue_labels`, preflight purity) Likely direction: replace single-page lookup with a canonical paginated helper such as `api_get_all(f"{base}/labels", auth)`. Prefer the repository's sanctioned helper; do not invent a one-off pagination loop if `api_get_all` already is the standard. Also consider duplicate label names (this repo currently has duplicate `type:feature` and `workflow-hardening` label IDs) so name→id mapping remains deterministic and fail-closed where ambiguous. ## Acceptance criteria 1. Repository-label resolution retrieves **every** page. 2. Valid labels beyond the first page are accepted. 3. Invalid label names are still rejected **before** mutation. 4. Full-set replacement preserves requested labels regardless of page position. 5. Existing labels are not silently dropped. 6. The operation continues to use sanctioned authentication and API helpers only. 7. Capability, namespace, runtime, and mutation guards remain enforced. 8. Pagination failures fail closed with an actionable error. 9. Post-mutation verification confirms the final issue-label set. 10. Tests cover: * repository with fewer than one page of labels; * repository with exactly one full page; * repository with more than one page; * requested labels split across pages; * missing requested label; * duplicate label names or IDs if relevant; * API failure on a later page; * complete-set replacement preserving later-page labels. 11. A regression test reproduces the Issue #601 failure mode (`type:feature` / `workflow-hardening` style later-page names rejected under single-page inventory). 12. After the fix is merged and the MCP runtime is refreshed, Issue #601 reconciliation can remove `status:pr-open` while retaining all valid labels. ## Linkage * Issue **#601** — closed feature work; post-merge label reconciliation remains incomplete pending this fix. * PR **#625** — merged implementation of #601 that exposed the reconciler path. * Comment **9455** — reconciliation ledger recording the sanctioned-tool failure. **Ownership boundary:** * #601 is already closed; do not reopen it for this tool defect. * This issue owns the systemic `gitea_set_issue_labels` pagination correction. * Completing this issue unblocks a later reconciler session for residual #601 label hygiene. ## Non-goals * Do not implement ad-hoc label edits on #601 from this issue's author session without the fixed tool. * Do not weaken full-set replacement semantics. * Do not bypass MCP with direct tokens/scripts for production label mutations. * Diagnostic-bypass hardening for reconciler scratch scripts is **out of primary scope** (see closed related process issues #558 / #539); track residual process gaps separately if needed. ## Suggested labels `bug`, `type:guardrail`, `status:ready`, `labels`, `mcp`, `workflow-hardening`
jcwalker3 added the bugmcplabelstype:guardrailstatus:readyworkflow-hardening labels 2026-07-10 11:45:39 -05:00
Author
Owner

Canonical Issue State

STATE:
needs-author

WHO_IS_NEXT:
author

NEXT_ACTION:
Implement and test paginated label resolution for gitea_set_issue_labels

NEXT_PROMPT:

You are the AUTHOR session for Scaled-Tech-Consulting/Gitea-Tools on remote prgs.

Task: Implement Issue #627 — Fix paginated repository-label resolution in gitea_set_issue_labels.

Do not self-select another issue.
Do not reopen or modify labels on Issue #601.
Do not implement unrelated feature work.
Do not use direct API scripts, resolved tokens, internal MCP imports, or raw credential access.

## Scope

Issue: #627
Title: Fix paginated repository-label resolution in gitea_set_issue_labels

Root defect: gitea_set_issue_labels validates requested names against a single-page GET of `{base}/labels?limit=100`. Gitea effectively caps pages at 50 (see gitea_auth.api_get_all). Later-page labels (e.g. type:feature, workflow-hardening) are falsely rejected as nonexistent, blocking full-set replacement during post-merge reconciliation of closed #601 (PR #625; ledger comment 9455).

## Isolation

1. Activate prgs-author.
2. Confirm identity jcwalker3 / prgs-author and runtime freshness (restart_required=false, stop_required=false, stale=false).
3. Work only under branches/ in an author-owned worktree, e.g. branches/feat-issue-627-label-pagination.
4. Branch suggestion: fix/issue-627-set-issue-labels-pagination
5. Do not use reviewer/reconciler worktrees (including branches/review-pr-625).

## Files to inspect

* gitea_mcp_server.py — gitea_set_issue_labels and other labels?limit=100 call sites
* gitea_auth.py — api_get_all (page_size clamp to 50)
* gitea_list_labels (already uses api_get_all — pattern to follow)
* mark_issue.py / manage_labels.py if they share the incomplete inventory path
* tests for set_issue_labels / issue write gates / label helpers
* task_capability_map.py only if capability wiring must change (prefer no guard weakening)

## Required behavior

1. Repository-label resolution retrieves every page via sanctioned pagination (prefer api_get_all).
2. Valid later-page labels are accepted.
3. Invalid names still fail closed before mutation.
4. Full-set replacement preserves the requested set regardless of page position.
5. No silent drop of existing labels.
6. Keep capability, namespace, runtime, and mutation guards.
7. Pagination failures fail closed with actionable errors.
8. Post-mutation verification confirms final issue-label set.
9. Handle duplicate label names/IDs if present (repo currently has duplicate type:feature and workflow-hardening IDs).

## Tests required

* fewer than one page of repo labels
* exactly one full page
* more than one page
* requested labels split across pages
* missing requested label still rejected
* duplicate label names/IDs if relevant
* API failure on a later page fails closed
* complete-set replacement preserves later-page labels
* regression reproducing #601 failure mode (later-page names falsely missing under single-page inventory)

## Prohibitions

* No direct API/token/keychain bypasses
* No scratch scripts for production mutations
* No weakening full-set semantics to “partial apply”
* No changes to #601 labels in this implementation session

## PR requirements

1. Open one PR against master for #627 only.
2. Title/body reference Closes #627 and link #601 / #625 / comment 9455 as context only.
3. Include validation commands and results.
4. Post CTH: Author Handoff + Canonical Issue/PR State + Thread State Ledger as required by workflow.
5. Leave a ready-to-paste reviewer prompt pinned to the new head SHA.
6. Do not self-review or merge.

## Handoff

After PR open: status needs-review; next owner reviewer; note that #601 residual status:pr-open cleanup remains a reconciler follow-up after this fix merges and MCP runtime refreshes.

WHAT_HAPPENED:
Issue #601 reconciliation exposed valid later-page labels being rejected as nonexistent by gitea_set_issue_labels; durable issue #627 created for the systemic fix. #601 was not reopened and its labels were not modified in this intake session.

WHY:
Source inspection shows gitea_set_issue_labels uses a single-page labels?limit=100 lookup while api_get_all documents a Gitea per-page cap of 50 and paginates correctly; gitea_list_labels already uses api_get_all. Incomplete inventory falsely rejects later-page names and blocks full-set replacement.

RELATED_DISCUSSION:
none

RELATED_ISSUES:

  • #627 (this issue)
  • #601 (closed; residual post-merge label hygiene blocked)
  • #558 / #539 (closed process issues covering direct API / import bypass — not primary scope here)

RELATED_PRS:

  • #625 (merged #601 implementation that exposed reconciler path)
  • none yet for this defect

BRANCH:
none yet

HEAD_SHA:
none

VALIDATION:
Duplicate search completed across open (limit=100) and closed (limit=100) inventories; no exact pagination issue found. Code path verified in gitea_mcp_server.py and gitea_auth.api_get_all. gitea_list_labels confirms type:feature and workflow-hardening exist. Live #601 remains closed with status:pr-open still present.

BLOCKERS:
Issue #601 final label reconciliation waits for #627 to be fixed, reviewed, merged, and MCP runtime refreshed.

LAST_UPDATED_BY:
jcwalker3 / prgs-author / author / 2026-07-10


CTH: Author Handoff

Status: issue_created_ready_for_author_implementation
Next owner: author
Current blocker: none for starting implementation of #627
Decision: created durable Issue #627; no implementation in intake session
Proof: issue number 627; labels applied; #601 state/labels not modified
Next action: author implements pagination fix under branches/ worktree and opens PR
Ready-to-paste prompt: see NEXT_PROMPT above

## Canonical Issue State STATE: needs-author WHO_IS_NEXT: author NEXT_ACTION: Implement and test paginated label resolution for gitea_set_issue_labels NEXT_PROMPT: ```text You are the AUTHOR session for Scaled-Tech-Consulting/Gitea-Tools on remote prgs. Task: Implement Issue #627 — Fix paginated repository-label resolution in gitea_set_issue_labels. Do not self-select another issue. Do not reopen or modify labels on Issue #601. Do not implement unrelated feature work. Do not use direct API scripts, resolved tokens, internal MCP imports, or raw credential access. ## Scope Issue: #627 Title: Fix paginated repository-label resolution in gitea_set_issue_labels Root defect: gitea_set_issue_labels validates requested names against a single-page GET of `{base}/labels?limit=100`. Gitea effectively caps pages at 50 (see gitea_auth.api_get_all). Later-page labels (e.g. type:feature, workflow-hardening) are falsely rejected as nonexistent, blocking full-set replacement during post-merge reconciliation of closed #601 (PR #625; ledger comment 9455). ## Isolation 1. Activate prgs-author. 2. Confirm identity jcwalker3 / prgs-author and runtime freshness (restart_required=false, stop_required=false, stale=false). 3. Work only under branches/ in an author-owned worktree, e.g. branches/feat-issue-627-label-pagination. 4. Branch suggestion: fix/issue-627-set-issue-labels-pagination 5. Do not use reviewer/reconciler worktrees (including branches/review-pr-625). ## Files to inspect * gitea_mcp_server.py — gitea_set_issue_labels and other labels?limit=100 call sites * gitea_auth.py — api_get_all (page_size clamp to 50) * gitea_list_labels (already uses api_get_all — pattern to follow) * mark_issue.py / manage_labels.py if they share the incomplete inventory path * tests for set_issue_labels / issue write gates / label helpers * task_capability_map.py only if capability wiring must change (prefer no guard weakening) ## Required behavior 1. Repository-label resolution retrieves every page via sanctioned pagination (prefer api_get_all). 2. Valid later-page labels are accepted. 3. Invalid names still fail closed before mutation. 4. Full-set replacement preserves the requested set regardless of page position. 5. No silent drop of existing labels. 6. Keep capability, namespace, runtime, and mutation guards. 7. Pagination failures fail closed with actionable errors. 8. Post-mutation verification confirms final issue-label set. 9. Handle duplicate label names/IDs if present (repo currently has duplicate type:feature and workflow-hardening IDs). ## Tests required * fewer than one page of repo labels * exactly one full page * more than one page * requested labels split across pages * missing requested label still rejected * duplicate label names/IDs if relevant * API failure on a later page fails closed * complete-set replacement preserves later-page labels * regression reproducing #601 failure mode (later-page names falsely missing under single-page inventory) ## Prohibitions * No direct API/token/keychain bypasses * No scratch scripts for production mutations * No weakening full-set semantics to “partial apply” * No changes to #601 labels in this implementation session ## PR requirements 1. Open one PR against master for #627 only. 2. Title/body reference Closes #627 and link #601 / #625 / comment 9455 as context only. 3. Include validation commands and results. 4. Post CTH: Author Handoff + Canonical Issue/PR State + Thread State Ledger as required by workflow. 5. Leave a ready-to-paste reviewer prompt pinned to the new head SHA. 6. Do not self-review or merge. ## Handoff After PR open: status needs-review; next owner reviewer; note that #601 residual status:pr-open cleanup remains a reconciler follow-up after this fix merges and MCP runtime refreshes. ``` WHAT_HAPPENED: Issue #601 reconciliation exposed valid later-page labels being rejected as nonexistent by gitea_set_issue_labels; durable issue #627 created for the systemic fix. #601 was not reopened and its labels were not modified in this intake session. WHY: Source inspection shows gitea_set_issue_labels uses a single-page labels?limit=100 lookup while api_get_all documents a Gitea per-page cap of 50 and paginates correctly; gitea_list_labels already uses api_get_all. Incomplete inventory falsely rejects later-page names and blocks full-set replacement. RELATED_DISCUSSION: none RELATED_ISSUES: - #627 (this issue) - #601 (closed; residual post-merge label hygiene blocked) - #558 / #539 (closed process issues covering direct API / import bypass — not primary scope here) RELATED_PRS: - #625 (merged #601 implementation that exposed reconciler path) - none yet for this defect BRANCH: none yet HEAD_SHA: none VALIDATION: Duplicate search completed across open (limit=100) and closed (limit=100) inventories; no exact pagination issue found. Code path verified in gitea_mcp_server.py and gitea_auth.api_get_all. gitea_list_labels confirms type:feature and workflow-hardening exist. Live #601 remains closed with status:pr-open still present. BLOCKERS: Issue #601 final label reconciliation waits for #627 to be fixed, reviewed, merged, and MCP runtime refreshed. LAST_UPDATED_BY: jcwalker3 / prgs-author / author / 2026-07-10 --- ## CTH: Author Handoff Status: issue_created_ready_for_author_implementation Next owner: author Current blocker: none for starting implementation of #627 Decision: created durable Issue #627; no implementation in intake session Proof: issue number 627; labels applied; #601 state/labels not modified Next action: author implements pagination fix under branches/ worktree and opens PR Ready-to-paste prompt: see NEXT_PROMPT above
jcwalker3 added status:pr-open and removed status:ready labels 2026-07-10 12:07:18 -05:00
sysadmin added workflow-hardening and removed status:pr-openworkflow-hardening labels 2026-07-10 13:36:34 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

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