LearnNewsExamplesServices
Frontmatter
id13262
titleFix manage_issue_labels for issue-only numbers
stateClosed
labels
bugdeveloper-experienceaitestingmodel-experience
assigneesneo-gpt
createdAtJun 14, 2026, 10:22 PM
updatedAtJun 15, 2026, 12:21 AM
githubUrlhttps://github.com/neomjs/neo/issues/13262
authorneo-gpt
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 15, 2026, 12:21 AM

Fix manage_issue_labels for issue-only numbers

Closed v13.1.0/archive-v13-1-0-chunk-2 bugdeveloper-experienceaitestingmodel-experience
neo-gpt
neo-gpt commented on Jun 14, 2026, 10:22 PM

Context

While triaging #13260, I attempted to use the GitHub Workflow MCP manage_issue_labels tool to add ai to an ordinary GitHub Issue. The tool failed before mutation:

Tool Error: GraphQL API request failed. Message: GitHub API error: Could not resolve to a PullRequest with the number of 13260.

I verified the authenticated GitHub CLI account as neo-gpt before the public write path, then used REST as a manual fallback to apply the label. The MCP tool failure is still reproducible against the same issue number.

Release classification: not release-blocking; agent-ops hardening. The REST fallback exists, but the MCP surface should not fail its primary issue-label use case.

Live latest-open sweep: checked the latest 20 open issues on 2026-06-14T20:21Z; no equivalent open issue found. A2A in-flight sweep: checked latest 30 messages; no active claim/intent for this scope found.

The Problem

manage_issue_labels is documented as a unified issue-or-pull-request label tool. The merged support from #10077 / PR #11696 covered the old PR-label failure class, but the current live failure is the mirror image: an issue-only number fails because GitHub GraphQL rejects the pullRequest(number:) field when no PR with that repository number exists.

The current unit tests mock the dual lookup as if GitHub returns { issue, pullRequest: null } or { issue: null, pullRequest }. Live GraphQL can instead fail the whole operation when one branch of the combined query cannot resolve, so the fallback expression never executes.

The Architectural Reality

Current origin/dev shape:

  • ai/mcp/server/github-workflow/toolService.mjs maps manage_issue_labels to IssueService.manageIssueLabels().
  • ai/services/github-workflow/IssueService.mjs#manageIssueLabels() delegates add/remove to addLabels() / removeLabels().
  • IssueService.#getIds() calls one combined GET_ISSUE_AND_LABEL_IDS query, then chooses data.repository.issue?.id || data.repository.pullRequest?.id.
  • ai/services/github-workflow/queries/issueQueries.mjs defines GET_ISSUE_AND_LABEL_IDS with both issue(number: $issueNumber) and pullRequest(number: $issueNumber) in the same query.
  • test/playwright/unit/ai/services/github-workflow/IssueService.spec.mjs has contract coverage for the intended dual behavior, but not for the live GraphQL partial-field rejection path.

The Fix

Make manage_issue_labels resolve the labelable target without relying on a single combined issue+PR query that can fail on the missing side.

Likely shape:

  1. Fetch the issue labelable ID first using an issue-only query and the repository label IDs.
  2. If the issue lookup reports the target does not exist, fetch the PR labelable ID using a PR-only query.
  3. Preserve successful issue labels, successful PR labels, missing-label errors, and missing-target structured errors.
  4. Add a unit test that simulates the live issue-number failure class: the PR lookup path must not run or must not poison an issue target when the issue exists.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
manage_issue_labels MCP tool Existing OpenAPI tool contract + this ticket Add/remove labels for either an Issue or Pull Request number. If neither target exists, return the existing structured GraphQL error; if labels are missing, return the existing missing-label error. No OpenAPI shape change. Unit tests for issue success, PR success, missing target, missing label, and live-style partial lookup failure.
IssueService.#getIds() labelable resolution IssueService.manageIssueLabels() implementation Resolve the correct Labelable ID without a combined issue+PR query that can be rejected by GitHub for one missing side. Issue lookup failure should only fall through to PR lookup when it means target missing; unrelated GraphQL/auth/rate errors should still surface. JSDoc or inline summary if helper split is non-obvious. Focused IssueService.spec.mjs tests with mocked GraphqlService.query() call order and error shapes.

Decision Record impact

None. This is a bug fix in an existing MCP service surface and does not amend an ADR.

Acceptance Criteria

  • manage_issue_labels succeeds for a real issue number even when no PR with the same number exists.
  • manage_issue_labels still succeeds for a PR number.
  • Missing target and missing label cases continue to return structured errors.
  • Unit coverage includes the live failure class that produced Could not resolve to a PullRequest with the number of 13260.
  • No OpenAPI request/response schema change is introduced.

Out of Scope

  • Creating a separate PR-only label tool.
  • Reworking label validation or pagination beyond what is needed for the existing maxRepoLabels contract.
  • Changing issue/PR comment, assignment, review, or project membership tools.

Related

Related: #10077, #11696

Retrieval Hint: manage_issue_labels issue-only number PullRequest resolution GraphQL partial field rejection