---
id: 11233
title: 'Extend create_issue MCP tool with ProjectV2 membership API support + retire release:v13 label hack'
state: CLOSED
labels:
  - enhancement
  - ai
  - architecture
  - model-experience
assignees:
  - neo-opus-ada
createdAt: '2026-05-11T21:49:04Z'
updatedAt: '2026-05-12T02:02:11Z'
githubUrl: 'https://github.com/neomjs/neo/issues/11233'
author: neo-opus-ada
commentsCount: 0
parentIssue: null
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-05-12T02:02:11Z'
---
# Extend create_issue MCP tool with ProjectV2 membership API support + retire release:v13 label hack

## Context

Operator-surfaced 2026-05-11 (this session) on PR #11227 chief-architect audit:

> *"v13 label are noise. this is NOT how github v2 projects work. tickets have labels and projects. direct api mapping. either we have a ticket to include projects into our create ticket tool, or we already did that. i seriously want to see this hacky label getting removed. not a script to better use the failed approach more."*

V-B-A'd: **no existing ticket** for ProjectV2-in-create_issue. The github-workflow MCP tool's `create_issue` schema has only `title`/`body`/`labels`/`assignees` — no `addToProjects` parameter. The `release:v13` label is a hacky workaround for missing project-membership API integration.

## The Problem

**Current shape (wrong)**:
- Agent creates ticket with `create_issue({labels: ["release:v13", ...]})`
- `release:v13` label is used as project-membership proxy
- A separate `ai/scripts/reconcileV13Project.mjs` script reconciles "labeled but not in project" + "in project but not labeled" drift
- Drift is structural — labels and projects are independent GitHub primitives; the proxy is always going to drift

**GitHub Projects v2 reality** (per V-B-A on GitHub API docs + the operator's framing):
- Tickets have LABELS and PROJECTS as independent first-class properties
- Direct API: `addProjectV2ItemById` mutation adds a ticket to a project
- Direct API: `removeProjectV2Item` mutation removes a ticket from a project
- No label-as-proxy needed; projects are not derived from labels

**Why the current approach was taken** (best-guess root cause): create_issue MCP tool was built before ProjectV2 mutation support existed; label was the simplest substrate to add for "v13 release tracking" without extending the tool.

**Why it's substrate-failure**:
- Drift between label and project IS structural (PR #11227 V-B-A showed 14 "labeled-not-in-project" + 196 "in-project-not-labeled" — 31% drift)
- Reconcile script (PR #11227's approach) treats the SYMPTOM (drift detection), not the CAUSE (wrong substrate)
- Label `release:v13` is noise on every ticket — pollutes label-as-categorization-tool semantics
- PR #11227's pagination fix doesn't fix the substrate; just makes the wrong-shape script more efficient

## The Architectural Reality

**Per GitHub Projects v2 API**:
- `addProjectV2ItemById(input: {projectId: ID!, contentId: ID!})` — adds existing issue to project
- `addProjectV2DraftItem(input: {projectId: ID!, title: String!, body: String})` — creates new draft item

**Per Neo MCP tool surface** (V-B-A'd):
- `create_issue` in `ai/mcp/server/github-workflow/openapi.yaml` — accepts `title`/`body`/`labels`/`assignees` only
- NO `addProjectV2*` operations exposed
- NO service file references `projectV2` mutation logic

## The Fix

**Phase 1: Extend create_issue + add project-membership tools**

1. Extend `create_issue` schema with optional `projects` parameter:
   ```yaml
   projects:
     type: array
     items:
       type: object
       properties:
         projectNumber: { type: integer }
         status: { type: string, description: "Optional initial status (e.g., 'Todo', 'In Progress')" }
     description: |
       (Optional) Array of ProjectV2 memberships to add. Each entry specifies a project number
       and optional initial status. Uses `addProjectV2ItemById` GraphQL mutation after issue creation.
   ```

2. Add `manage_issue_projects` tool (mirror of `manage_issue_labels`):
   - `action: 'add'` → `addProjectV2ItemById`
   - `action: 'remove'` → `removeProjectV2Item`
   - `action: 'update_field'` → `updateProjectV2ItemFieldValue` (status, priority, etc.)

3. Update `ticket-create-workflow.md` skill payload — replace `release:v13` label guidance with `projects: [{projectNumber: 12, status: 'Todo'}]` pattern.

**Phase 2: Retire `release:v13` label**

1. Migrate all currently-labeled tickets: add them to ProjectV2 #12 via the new tool, then remove the label
2. Update all skill payloads that mention `release:v13` label to use project-membership pattern instead
3. Close + retire the `release:v13` label from repo settings

**Phase 3: Drop reconcile script**

1. `ai/scripts/reconcileV13Project.mjs` becomes obsolete — drift can't happen if tickets are added directly to project at creation
2. Delete script + remove `npm run ai:reconcile-v13-project` from package.json
3. Close #10961 (pilot ticket) with rationale: pilot proved the wrong-shape approach; correct shape is direct API integration

## Acceptance Criteria

- [ ] **AC1**: `create_issue` MCP tool schema extended with `projects` parameter
- [ ] **AC2**: Service file (`ai/services/github-workflow/IssueService.mjs` or similar) implements `addProjectV2ItemById` call after issue creation
- [ ] **AC3**: New `manage_issue_projects` tool added with add/remove/update_field actions
- [ ] **AC4**: `ticket-create-workflow.md` skill payload updated to use `projects` parameter instead of `release:v13` label
- [ ] **AC5**: Migration: existing `release:v13`-labeled tickets added to ProjectV2 #12 + label removed (~68 tickets currently labeled per V-B-A)
- [ ] **AC6**: `release:v13` label retired from repo settings (after AC5)
- [ ] **AC7**: `ai/scripts/reconcileV13Project.mjs` deleted + `npm run ai:reconcile-v13-project` removed from package.json
- [ ] **AC8**: PR #11227 closed as Drop+Supersede (the pagination fix for a wrong-shape script is no longer needed)
- [ ] **AC9**: #10961 pilot ticket closed with rationale (pilot proved wrong-shape; this ticket's API integration is the corrective)

## Out of Scope

- General label-as-proxy audit across ALL labels (other labels may have legitimate semantic-categorization use)
- ProjectV2 list/query tools beyond what's needed for the create_issue + manage_issue_projects scope
- Cross-org / external-project membership (Neo has one canonical org-level ProjectV2 #12 for v13)

## Avoided Traps

- **Patch the reconcile script (PR #11227 approach)**: rejected — treats symptom not cause; structural drift can't be patched away
- **Use a different label**: rejected — labels are wrong-primitive for project membership regardless of name
- **Auto-detect membership from label at runtime**: rejected — same as current hack, just hidden in tool implementation
- **Project assignment in agent script (post-create)**: rejected — racy and partial; create_issue should atomically establish project membership

## Related

- **#10961** Pilot GitHub Projects for v13 — proved the wrong-shape approach; this ticket is the corrective
- **#10960** v13 Release Tracking — parent epic; AC tracking depends on accurate project membership
- **PR #11227** pagination fix for the wrong-shape reconcile script — drops on AC8 close
- **PR #11225 / PR #11232 lessons**: same §9.0 Premise Pre-Flight failure pattern — mechanical-AC reviews missed architectural-pillar question. Today is the second cluster surfaced.
- **GitHub Projects v2 API**: [addProjectV2ItemById](https://docs.github.com/en/graphql/reference/mutations#addprojectv2itembyid) + [removeProjectV2Item](https://docs.github.com/en/graphql/reference/mutations#deleteprojectv2item)

## Origin Session ID

`c0d5c29d-dc70-44c8-b5af-d3f6c59936ee` (operator-surfaced chief-architect friction this session)

## Handoff Retrieval Hints

- `query_raw_memories(query="release:v13 label hack ProjectV2 API create_issue project membership")`
- `ask_knowledge_base(query="github projects v2 api ticket membership label proxy")`
- File anchors: `ai/mcp/server/github-workflow/openapi.yaml` (create_issue schema), `ai/services/github-workflow/IssueService.mjs` (implementation), `.agents/skills/ticket-create/references/ticket-create-workflow.md` (skill guidance)

---

🤖 Generated with [Claude Code](https://claude.com/claude-code)

## Timeline

- 2026-05-11T21:49:05Z @neo-opus-ada assigned to @neo-opus-ada
- 2026-05-11T21:49:06Z @neo-opus-ada added the `enhancement` label
- 2026-05-11T21:49:06Z @neo-opus-ada added the `ai` label
- 2026-05-11T21:49:06Z @neo-opus-ada added the `architecture` label
- 2026-05-11T21:49:06Z @neo-opus-ada added the `model-experience` label
- 2026-05-11T21:53:26Z @neo-opus-ada cross-referenced by PR #11227
- 2026-05-11T22:00:55Z @neo-gemini-pro cross-referenced by #10961
- 2026-05-11T22:14:10Z @neo-opus-ada cross-referenced by PR #11234
- 2026-05-12T00:25:57Z @neo-gemini-pro cross-referenced by PR #11246
- 2026-05-12T00:27:08Z @neo-gemini-pro cross-referenced by PR #11247
- 2026-05-12T00:32:15Z @neo-gemini-pro referenced in commit `45a9fb4` - "chore(scripts): apply idempotency + author tags to migration script (#11233)"
- 2026-05-12T02:02:11Z @tobiu referenced in commit `ea3af10` - "feat(github-workflow): extend create_issue with projects param + add manage_issue_projects tool (#11233) (#11234)

Phase 1 of the substrate-correct corrective to #10961 pilot's `release:v*`
label-as-project-membership-proxy anti-pattern. Empirical V-B-A on the pilot
showed 31% structural drift (14 labeled-not-in-project + 196 in-project-not-
labeled out of 250 items) — drift that cannot be patched away because labels
(categorization primitive) and ProjectV2 items (membership primitive) are
independent GitHub concepts.

Substrate added:
- `create_issue` MCP tool: optional `projects` parameter (array of
  `{projectNumber}`); after `gh issue create`, issue's GraphQL node ID is
  resolved and each project attach is chained via `addProjectV2ItemById`.
  Partial-attach is the graceful failure mode (issue exists; orphan-rollback
  worse for agent workflows).
- `manage_issue_projects` new MCP tool: mirrors `manage_issue_labels` shape
  with `add` / `remove` / `update_field` actions. Single-select field updates
  resolve field+option IDs by name with `OPTION_NOT_FOUND` returning the
  available option list.
- 4 new GraphQL operations in `mutations.mjs`: `GET_PROJECT_V2_METADATA`,
  `ADD_PROJECT_V2_ITEM`, `DELETE_PROJECT_V2_ITEM`,
  `FIND_PROJECT_V2_ITEM_BY_CONTENT`, `UPDATE_PROJECT_V2_ITEM_SINGLE_SELECT`.
- `IssueService` extended with: `getProjectV2Metadata`, `getIssueNodeId`,
  `attachIssueToProjects`, `detachIssueFromProjects`,
  `updateProjectV2ItemSingleSelect`, `findProjectV2ItemId`,
  `manageIssueProjects`.
- Skill payload updates: `learn/agentos/GitHubWorkflow.md §7` flipped from
  "Read-Only Derived View" framing to "First-Class Membership Primitive";
  `ticket-create-workflow.md §4` + `§10` reference the new `projects`
  parameter + `manage_issue_projects` tool.
- 7 new unit tests in `IssueService.spec.mjs` covering dispatcher validation,
  add-action attach, partial-attach warnings, update_field success, and
  OPTION_NOT_FOUND with available-options surface. 1 new test in
  `ToolRegistration.spec.mjs` verifying serviceMapping entry.

Empirical validation: 15/15 IssueService unit tests pass locally (8 existing
+ 7 new). ToolRegistration manage_issue_projects test passes.

Phases 2 (migrate 68 currently-labeled tickets + retire `release:v13` label)
and 3 (delete `reconcileV13Project.mjs` script + remove npm run target) are
deferred to separate PRs after Phase 1 lands.

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-12T02:02:11Z @tobiu closed this issue
- 2026-05-12T02:07:23Z @neo-gemini-pro referenced in commit `ee7655f` - "chore(ai/scripts): add Phase 2 migration script for ProjectV2 (#11233)"
- 2026-05-12T02:07:23Z @neo-gemini-pro referenced in commit `e933d94` - "chore(scripts): apply idempotency + author tags to migration script (#11233)"
- 2026-05-12T08:11:29Z @tobiu referenced in commit `5dcf6ef` - "chore(ai/scripts): Add Phase 2 ProjectV2 migration script (#11233) (#11246)

* chore(ai/scripts): add Phase 2 migration script for ProjectV2 (#11233)

* chore(scripts): apply idempotency + author tags to migration script (#11233)

* fix(ai/scripts): increase maxBuffer to prevent ENOBUFS during migration

---------

Co-authored-by: neo-gemini-pro <neo-gemini-pro@users.noreply.github.com>"
- 2026-05-12T08:12:08Z @tobiu referenced in commit `2b4f630` - "chore: remove obsolete reconcileV13Project script (#11233) (#11247)

Co-authored-by: neo-gemini-pro <neo-gemini-pro@users.noreply.github.com>"
- 2026-05-12T08:18:54Z @neo-opus-ada cross-referenced by PR #11251
- 2026-05-12T09:51:36Z @neo-gpt cross-referenced by PR #11255
- 2026-05-12T10:31:59Z @tobiu referenced in commit `774b1b5` - "chore: remove obsolete migrateV13ProjectFromLabel script (#11233) (#11251)

Phase 4 cleanup of Epic #11233 — operator-surfaced substrate-shape correction
(2026-05-12): a one-shot migration script + npm-script shortcut is technical
debt post-execution, not playbook substrate. The npm-script entry's
discoverability surface incorrectly signals "this is regular tooling" when
the task only ever needed to fire once.

V-B-A confirmed substrate-state at-rest:
- `release:v*` labels: none exist on neomjs/neo (label-list audit clean)
- `release:v13` labeled issues: 0 (gh issue list --state all confirms)
- migration data state: already complete via another path before PR #11246 merged

Deletes:
- `ai/scripts/migrateV13ProjectFromLabel.mjs` (136 LOC; PR #11246 substrate)
- `package.json:31` `ai:migrate-v13-project` npm-script entry

Substrate-shape lesson: my Cycle 1 review on PR #11246 framed the post-merge
value as "playbook + idempotent-replay safety net" — that framing was wrong-shape
for a one-shot task. Operator correction surfaces the right framing:
post-execution one-shot migration scripts are technical debt, not playbook.

Authored by Claude Opus 4.7 (Claude Code). Session
\`c2912891-b459-4a03-b2af-154d5e264df1\`.

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-12T18:54:40Z @neo-opus-ada cross-referenced by #11273
- 2026-05-13T13:20:43Z @neo-opus-ada cross-referenced by #11308
- 2026-05-16T03:08:02Z @neo-opus-ada cross-referenced by #10138

