LogoSkills

ZenHub Pipeline Registry

---

ZenHub pipeline dynamic query guide


Workspace Info Query#

Pipeline ID, Repository ID, and Issue Type ID differ per workspace. Dynamically query based on the X-zh-workspace configured in the project root's .mcp.json:

// Pipeline ID + Repository ID
const workspace = await mcp__zenhub__getWorkspacePipelinesAndRepositories();
// workspace.pipelines โ†’ [{ id, name }, ...]
// workspace.githubRepositories โ†’ [{ id, name }, ...]

// Issue Type ID
const issueTypes = await mcp__zenhub__getIssueTypes({ repositoryId });
// issueTypes โ†’ [{ id, name, level }, ...]
// (Initiative L1, Project L2, Epic L3, Feature/Bug/Task L4, Sub-task L5, ...)

Query Issue Type IDs via mcp__zenhub__getIssueTypes().


Two States: Pipeline vs GitHub State โš ๏ธ#

ZenHub tracks two independent states for every issue. Confusing them is the #1 source of "issue stuck open" bugs:

StateOwnerWhat it means
Pipeline ZenHub The board column (New Issues, In Progress, Review/QA, Done, โ€ฆ). Moving columns is purely a board position.
GitHub state GitHub The issue's actual open / closed flag.

Key consequences:

  • Done pipeline โ‰  closed. Moving an issue to a Done column does not close the GitHub issue โ€” it stays open. Done means "ready to close", not "closed".
  • Closed pipeline = GitHub closed, 1:1. ZenHub does not treat "Closed" as an ordinary column; it is wired directly to the GitHub closed state. ZenHub's own API says to judge open/closed by state, not by pipeline.
  • An issue becomes truly Closed in exactly one of three ways:
    1. Dragged into the Closed pipeline (โ†’ GitHub closes it), or
    2. Closed directly on GitHub (โ†’ ZenHub auto-moves it to Closed), or
    3. A linked PR merges with Closes #N in its body (โ†’ GitHub auto-closes โ†’ ZenHub syncs to Closed).

This repo's policy = (B) "merge = Close". AI agents run full-stack E2E tests + review before merge, so a merged PR is treated as Done and Closed. We therefore rely on mechanism #3 (Closes #N) and never park issues in a Done column. Because #3 can silently fail (missing keyword, ZenHub sync lag), every merge is followed by a closure verification + fallback (see below).

Pipeline Transitions by Workflow#

Issue Processing Workflow#

New Issues โ†’ Product Backlog โ†’ Sprint Backlog โ†’ In Progress โ†’ Review/QA โ†’ [Merge = Close โ†’ Closed]

Note: Done pipeline is not used. On PR merge, Closes # auto-closes the GitHub issue, which syncs the ZenHub issue to the Closed pipeline. Always verify the Closed state after merge and fall back to an explicit close if the sync did not happen.

StepTriggerPipeline / State
Issue creationAutomaticNew Issues
Backlog registrationAfter triageProduct Backlog
Sprint assignmentSprint planningSprint Backlog
Start workOn branch creationIn Progress
Request review After PR creation Review/QA
Complete (merge = Close) PR merge โ†’ GitHub auto Close โ†’ ZenHub sync Closed (verify + fallback). Done not used.

Priority-based Initial Placement (issue creation flows)#

Issues created via /zenhub:epic / /product:epic skip New Issues triage โ€” they are placed directly by priority tier (see rules/zenhub-conventions.md โ†’ Priority Review and Pipeline Sorting):

TargetInitial Pipeline
Project / EpicProduct Backlog
Story P0 (sprint specified)Sprint Backlog
Story P0/P1Product Backlog
Story P2Icebox
Sub-taskFollows parent Story

moveIssueToPipeline has no in-pipeline position param โ€” move in descending priority order (P0 โ†’ P1 โ†’ P2).

MCP Call Examples#

// Query workspace info (once at session start)
const workspace = await mcp__zenhub__getWorkspacePipelinesAndRepositories();
const inProgress = workspace.pipelines.find(p = >   p.name ===  " In Progress " );
const reviewQA = workspace.pipelines.find(p = >   p.name ===  " Review/QA " );

// On work start (move to In Progress)
mcp__zenhub__moveIssueToPipeline({
  issueId:  " {issue.graphqlId} " ,
  pipelineId: inProgress.id
})

// After PR creation (move to Review/QA)
mcp__zenhub__moveIssueToPipeline({
  issueId:  " {issue.graphqlId} " ,
  pipelineId: reviewQA.id
})

// After PR merge: do NOT move to Done.  " merge = Close " .
// `Closes #N` should auto-close GitHub โ†’ ZenHub syncs to Closed.
// โš ๏ธ Verify it actually happened, fall back to explicit close otherwise:

// 1. GitHub is the source of truth for open/closed โ€” verify it first.
const ghState = await Bash(`gh issue view ${issueNumber} --json state -q .state`);
if (ghState.trim() !==  " CLOSED " ) {
  // `Closes #N` did not fire (missing keyword, cross-repo link, etc.) โ†’ close explicitly
  await Bash(`gh issue close ${issueNumber} --reason completed`);
}

// 2. Confirm ZenHub synced the GitHub closed โ†’ Closed pipeline.
//    Closing on GitHub normally syncs automatically; if lagging, force the state.
const closed = await mcp__zenhub__searchClosedIssues({ query: `#${issueNumber}` });
if (!closed.find(i = >   i.number === issueNumber)) {
  await mcp__zenhub__updateIssue({ issueId:  " {issue.graphqlId} " , state:  " CLOSED "   });
}

GitHub Issue Creation Example#

const workspace = await mcp__zenhub__getWorkspacePipelinesAndRepositories();
const repoId = workspace.githubRepositories.find(r = >   /* select GitHub repo */).id;
const issueTypes = await mcp__zenhub__getIssueTypes({ repositoryId: repoId });

mcp__zenhub__createGitHubIssue({
  repositoryId: repoId,
  title:  " feat(scope): Feature title " ,
  body:  " ## Summary\n...\n\n## Acceptance Criteria\n- [ ] ... " ,
  issueTypeId: issueTypes.find(t = >   t.name ===  " Feature " ).id,
  labels: [ " feature " ],
})

Workspace Initialization#

To set up a new workspace with the standard pipeline structure, use the /zenhub:init-workspace command:

/zenhub:init-workspace

Standard structure:

New Issues โ†’ Icebox โ†’ Product Backlog โ†’ Sprint Backlog โ†’ In Progress โ†’ Review/QA

If a Done pipeline exists, internal issues are Closed and manual deletion is guided.


Referencing Skills/Agents#

Files that reference this registry:

FilePurpose
commands/dev/run.mdFull workflow
commands/dev/batch.mdSequential issue processing
commands/dev/issue.mdSingle issue processing
commands/agents/dev/issue-state-agent.mdIssue state management
commands/agents/issue-processor-agent.mdIssue processing agent
commands/zenhub/init-workspace.mdWorkspace pipeline initialization
commands/zenhub/manage.mdPR linking + pipeline move
commands/zenhub/epic.mdEpic/Story ์ƒ์„ฑ + ์šฐ์„ ์ˆœ์œ„ยทํƒ€์ž„๋ผ์ธ ๋ฐฐ์น˜
commands/zenhub/changelog.md๋‹ซํžŒ ์ด์Šˆ ์œˆ๋„์šฐ ์ง‘๊ณ„ โ†’ ๋ฆด๋ฆฌ์Šค ๋…ธํŠธ
commands/zenhub/sprint-rollover.md๋ฏธ์™„๋ฃŒ ์ด์Šˆ ๋‹ค์Œ ์Šคํ”„๋ฆฐํŠธ carryover
commands/zenhub/triage.md์™ธ๋ถ€ ํ”ผ๋“œ๋ฐฑ dedup โ†’ New Issues ์ธ์ž…
commands/zenhub/rerank.md์ „์—ญ ๋ฐฑ๋กœ๊ทธ ์šฐ์„ ์ˆœ์œ„ ์žฌ์ •๋ ฌ
commands/zenhub/team-align.mdํŒ€ ๋ถ€ํ•˜ยท์šฐ์„ ์ˆœ์œ„ ์ถฉ๋Œ ๋ฆฌํฌํŠธ

Important Notes#

  1. No Hardcoded IDs: All IDs must be dynamically queried via MCP tools
  2. .mcp.json Based: Per-project workspace is determined by the X-zh-workspace header in .mcp.json
  3. ID Query: All IDs obtained via getWorkspacePipelinesAndRepositories() + getIssueTypes() combination