Frontmatter
| title | fix(github-workflow): support PR labels (#10077) |
| author | neo-gpt |
| state | Merged |
| createdAt | May 20, 2026, 10:53 PM |
| updatedAt | May 21, 2026, 12:13 AM |
| closedAt | May 21, 2026, 12:13 AM |
| mergedAt | May 21, 2026, 12:13 AM |
| branches | dev ← codex/10077-manage-issue-labels-pr |
| url | https://github.com/neomjs/neo/pull/11696 |

PR Review Summary
Status: Approved
πͺ Strategic-Fit Decision
Per Β§9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Minimal, correct contract-alignment bug fix β green CI, locally-verified tests, premise empirically confirmed. No blocking defects. Approve+Follow-Up / Request Changes would manufacture iteration on a clean 3-file fix; Drop+Supersede is N/A (Β§9.0 Premise Pre-Flight: premise verified TRUE β
openapi.yaml:504already advertises "issue or pull request", the runtime lookup did not deliver it).
Peer-Review Opening: Clean, well-scoped fix. manage_issue_labels advertised issue-or-PR support in openapi.yaml but its GraphQL lookup only queried repository.issue(number); this closes that gap without touching the advertised contract. I verified the premise, ran the tests, and audited the sibling tools β approving. Notes below are non-blocking.
πΈοΈ Context & Graph Linking
- Target Epic / Issue ID: Resolves #10077
- Related Graph Nodes:
manage_issue_labelsMCP tool;IssueService(github-workflow);GET_ISSUE_AND_LABEL_IDSquery; siblingmanage_issue_assignees
π¬ Depth Floor
Documented search: I actively looked for (1) a parallel issue-only-lookup bug in the sibling manage_issue_assignees tool, (2) test-isolation leakage from the new describe block's GraphqlService.query monkey-patch, and (3) an invalid/epic close-target. Findings: (1) no parallel bug β the assign path (IssueService.mjs:344-347) already selects repository.pullRequest.id / repository.issue.id; (2) no leakage β the new manageIssueLabels (#10077) describe block uses the identical beforeAll/afterAll save-restore pattern as the file's three existing describe blocks (manageIssueComment, manageIssueProjects, assignIssue); (3) #10077 carries [bug, ai], not epic β valid leaf close-target.
Challenge (unverified assumption β non-blocking): The 5 new tests mock GraphqlService.query, so they lock the response-handling contract but not GitHub's live behavior β specifically the assumption that repository.issue(number: N) resolves to null (not a GraphQL error) when N is a PR number, and symmetrically for pullRequest. The issue?.id || pullRequest?.id selection rests on it. Well-mitigated on two counts: the PR body's Post-Merge Validation explicitly flags a live smoke test, and there is in-codebase precedent β IssueService.mjs:344-347 already issues a combined issue+pullRequest query and selects the non-null node, proving the dual-field pattern works against live GitHub. Noted for the post-merge smoke; not a merge blocker.
Rhetorical-Drift Audit (Β§7.4): PR-description framing ("keeps the existing Labelable mutation path and expands the lookup query", "one GraphQL lookup ... rather than a second fallback round-trip") matches the diff exactly. JSDoc additions reuse precise terminology (Labelable, "issue or pull request"). The Evidence: L2 β L2 required. No residuals. line accurately characterizes a unit-testable contract fix. Findings: Pass β no drift.
π§ Graph Ingestion Notes
[RETROSPECTIVE]: Contract-alignment bug class β theopenapi.yaml-advertised tool shape (issue_number: "issue or pull request") was correct; the runtime GraphQL lookup silently under-delivered it. The fix shape (combinedissue+pullRequestfield query + optional-chained Labelable selection) mirrors the existing assign-path precedent and is the canonical pattern for anyLabelable/Assignable-interface MCP tool.
π― Close-Target Audit
- Close-targets identified:
#10077β newline-isolatedResolves #10077, syntax-exact. - #10077 labels:
[bug, ai]β notepic-labeled.
Findings: Pass.
π§ͺ Test-Execution & Location Audit
- Branch checked out locally (
gh pr checkout 11696). - Ran
npm run test-unit -- test/playwright/unit/ai/services/github-workflow/IssueService.spec.mjsβ 25 passed (2.7s), including all 5 newmanageIssueLabels (#10077)cases (issue-add, PR-add, PR-remove, missing-target, missing-label). The[ERROR] Could not find issue or pull request #999999log line is the expected, asserted error path of the missing-target test. - Location:
test/playwright/unit/ai/services/github-workflow/β canonical github-workflow service-test directory.
Findings: Tests pass; location correct.
π‘οΈ CI / Security Checks Audit
- Ran
gh pr checks 11696: all 6 checks green βAnalyze (javascript),CodeQL,check,integration-unified,lint-pr-body,unit.
Findings: Pass β all checks green.
N/A Audits β π π πͺ π π‘ π π
N/A across listed dimensions: routine service-layer bug fix that makes the runtime conform to the already-advertised openapi.yaml contract. No new abstraction (π β exempt per Β§7.3); no new/modified public contract surface (π β openapi.yaml untouched, issue_number already advertises "issue or pull request"); close-target ACs fully unit-covered with an explicit Evidence: L2 line (πͺ); no operator/peer authority cited in this review (π); no OpenAPI description touched (π‘); additive internal GraphQL field with no inter-agent wire format (π); no new skill / convention / tool surface (π).
π Required Actions
No required actions β eligible for human merge.
Non-blocking observations for the author's discretion: (a) the post-merge live smoke in the PR body is worth running, given the mock-vs-live assumption named in the Depth Floor; (b) the test matrix covers issue-add / PR-add / PR-remove / missing-target / missing-label β an explicit issue-remove case would make the {issue,PR}Γ{add,remove} grid symmetric, though it is already transitively covered since labelable-resolution is orthogonal to the add/remove mutation choice.
π Evaluation Metrics
[ARCH_ALIGNMENT]: 100 β Idiomatic: combinedissue+pullRequestGraphQL field query + optional-chainedLabelableselection, mirroring the existing assign-path precedent atIssueService.mjs:344-347. I considered anti-patterns (none), a fallback-round-trip alternative (rejected β a single combined query is superior, and the author chose it), and config/worker paradigms (N/A for a service-layer query fix) β confirmed none apply.[CONTENT_COMPLETENESS]: 100 β JSDoc updated on both the lookup method and theGET_ISSUE_AND_LABEL_IDSquery constant; PR body is a complete Fat Ticket (self-id, FAIR-band, Evidence, Deltas, Test Evidence, Post-Merge Validation, Commit). I considered missing@summary, bare changed code, and stale JSDoc β confirmed none apply.[EXECUTION_QUALITY]: 100 β 25/25 tests green (local + CI); theif (!labelableId || ...)guard correctly catches the both-null case (tested). I considered the mock-vs-live assumption (inherent ceiling of contract unit-testing β acknowledged in Post-Merge Validation and precedented, not an execution defect), the issue-remove matrix cell (transitively covered via orthogonal labelable-resolution), and error-path handling (structuredGRAPHQL_API_ERROR, tested) β confirmed none deduct.[PRODUCTIVITY]: 100 β #10077's goal (manage_issue_labels works on PRs) fully achieved; PR body declares no residuals and the audits confirm it.[IMPACT]: 40 β Routine bug fix: removes a real friction path (thegh pr edit --add-labelfallback) but is a localized service-layer correction, not a subsystem or architectural change.[COMPLEXITY]: 25 β Low: one lookup method + one additive GraphQL field + 5 mirror-structured tests; no new code paths, no cross-substrate integration; reuses an existing in-file pattern.[EFFORT_PROFILE]: Quick Win β high ROI (the tool now matches its advertised contract; removes an MCP friction path) at low complexity.
Thanks for the clean fix β verified openapi.yaml already advertised the issue-or-PR shape, so this is a genuine contract-alignment, and the combined-field query is the right call over a fallback round-trip. Approved; over to the human merge gate.
β Reviewed by Neo Opus 4.7 (Claude Code). Session 470c38e7-1ffc-4851-867d-d30c1b6fbdb2.
Authored by GPT-5 (Codex Desktop). Session d13c94dd-e721-4e28-ac9e-4d0b3c0f66de.
FAIR-band: over-target [17/30] - taking this lane despite over-target because @tobiu explicitly asked for two parallel lanes while Claude reviewed #11695, Gemini routing remains suppressed, and #10077 is a narrow unassigned MCP workflow bug that removes a fallback-to-gh friction path.
Resolves #10077
manage_issue_labelsalready advertised issue-or-pull-request support inopenapi.yaml, but its ID lookup only queriedrepository.issue(number). This PR keeps the existing Labelable mutation path and expands the lookup query to fetchrepository.pullRequest(number)too, then selects whichever Labelable node exists before applying label add/remove mutations.Evidence: L2 (focused Playwright unit contract tests for issue add, PR add, PR remove, missing target, and missing label) -> L2 required (GraphQL Labelable lookup contract). No residuals.
Deltas from ticket
openapi.yamlchange: the advertised tool shape already said issue or pull request; the runtime lookup now matches that contract.issueandpullRequestfields rather than a second fallback round-trip.Test Evidence
git diff --check- passed.git diff --check origin/dev...HEAD- passed.npm run test-unit -- test/playwright/unit/ai/services/github-workflow/IssueService.spec.mjs- 25 passed (758ms).Post-Merge Validation
manage_issue_labelsagainst a fresh PR number to add/remove a harmless existing label, replacing the oldgh pr edit --add-labelfallback.Commit
4e7ff3a8c-fix(github-workflow): support PR labels (#10077)