| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| Model | haiku |
/dev:issue-state โ ZenHub ์ด์ ์ํ ๊ด๋ฆฌ ๋์ฐ๋ฏธ#
ZenHub issue state management agent
ํ๋ง๋๋ก#
์์ ์นด๋(์ด์)๋ฅผ ๋ณด๋ ์์์ ์๋ง์ ์นธ์ผ๋ก ์ฎ๊ธฐ๊ณ , ์ผ์ด ๋๋๋ฉด ํ์คํ๊ฒ "์๋ฃ" ์ฒ๋ฆฌ๊น์ง ๋ง๋ฌด๋ฆฌํด ์ฃผ๋ ๋์ฐ๋ฏธ์ ๋๋ค. ์น ํ์ ๋ถ์ธ ํฌ์คํธ์์ "์งํ ์ค โ ๊ฒํ โ ์๋ฃ" ์นธ์ผ๋ก ์ฎ๊ธฐ๊ณ , ๋ค ๋๋ ํฌ์คํธ์์ ๋ผ์ด๋ด ๋ณด๊ดํจ์ ๋ฃ๋ ์ผ์ ๋์ ํด ์ค๋ค๊ณ ๋ณด๋ฉด ๋ฉ๋๋ค.
๋๊ฐยท์ธ์ ์ฐ๋์#
- ๊ฐ๋ฐ ์๋ํ ํ๋ฆ(๋ธ๋์น ์์ฑ โ PR ์์ฑ โ ๋ณํฉ) ์์์ ์ด์์ ํ์ฌ ์์น๋ฅผ ์๋์ผ๋ก ์ฎ๊ฒจ์ผ ํ ๋
- ๋ธ๋์น๋ฅผ ๋ง๋ ์งํ(โ "์งํ ์ค"), PR์ ์ฌ๋ฆฐ ์งํ(โ "๊ฒํ "), PR์ ๋ณํฉํ ์งํ(โ "์๋ฃ/๋ซํ") ๊ฐ์ ๋ถ๊ธฐ๋ง๋ค ํธ์ถ๋ฉ๋๋ค
- ์ด์๊ฐ ์ง๊ธ ์ด๋ ๋จ๊ณ์ ์๋์ง, ๋๊ฐ ๋ถ๋ชจ-์์์ผ๋ก ๋ฌถ์ฌ ์๋์ง ์ํ๋ฅผ ํ์ธํ๊ณ ์ถ์ ๋
๋ฌด์์ ํด์ฃผ๋์#
-
๋ณด๋ ์นธ ์ด๋(Pipeline) โ ์ด์๋ฅผ
In Progress,Review,Closed๋ฑ ์๋ง์ ์นธ์ผ๋ก ์ฎ๊น๋๋ค - ์ด์ ๋ซ๊ธฐ โ PR์ด ๋ณํฉ๋๋ฉด ์ด์๋ฅผ ์๋์ผ๋ก ๋ซ์ต๋๋ค
-
ํ์คํ ๋ง๋ฌด๋ฆฌ ๊ฒ์ฆ โ ๋ณํฉ ํ GitHub๊ณผ ZenHub ์์ชฝ ๋ชจ๋์์ ์ ๋ง
Closed๊ฐ ๋๋์ง ํ์ธํ๊ณ , ์ ๋์ผ๋ฉด ์ง์ ๋ซ์ ๋ณด์ ํฉ๋๋ค (์ด์๊ฐ "๋ซํ ์ค ์์๋๋ฐ ์ด๋ ค ์๋" ์ฌ๊ณ ๋ฅผ ๋ง์ ์ค๋๋ค) - ์ํ ์กฐํ โ ์ด์์ ํ์ฌ ์ํ์ ๋ณด๋ ์์น๋ฅผ ์๋ ค ์ค๋๋ค
- ๋ถ๋ชจ ์ด์ ์๋ ์ ๋ฆฌ โ ๋ฌถ์ฌ ์๋ ์์ ์ด์๊ฐ ์ ๋ถ ๋๋๋ฉด ๋ถ๋ชจ ์ด์๋ ์๋์ผ๋ก ๋ซ์ต๋๋ค
ํต์ฌ ์ฃผ์์ : ZenHub๋ "๋ณด๋ ์นธ ์์น"(Pipeline) ์ "์ค์ ์ด๋ฆผ/๋ซํ"(GitHub state) ์ด๋ผ๋ ๋ ๊ฐ์ ๋ณ๋ ์ํ๋ฅผ ๊ฐ์ง๋๋ค.
Done์นธ์ ์์ด๋ GitHub์์๋ ์ฌ์ ํ "์ด๋ฆผ"์ผ ์ ์์ด์, ์ด ๋์ฐ๋ฏธ๋ ์ด ๋์ ํท๊ฐ๋ฆฌ์ง ์๋๋ก ํญ์ ์ค์ ์ํ๋ฅผ ๊ธฐ์ค์ผ๋ก ํ๋จํฉ๋๋ค.
์ด๋ป๊ฒ ์ฐ๋์#
# ๋ธ๋์น ์์ฑ ํ โ " ์งํ ์ค " ์ผ๋ก ์ด๋
/dev:issue-state 25 --action move_pipeline --pipeline " In Progress "
# PR ์์ฑ ํ โ " ๊ฒํ " ๋ก ์ด๋
/dev:issue-state 25 --action move_pipeline --pipeline " Review/QA "
# PR ๋ณํฉ ํ โ ์ ๋ง ๋ซํ๋์ง ๊ฒ์ฆ + ์ ๋ซํ์ผ๋ฉด ๋ณด์
/dev:issue-state 25 --action verify_closed
- ์ซ์(
25)๋ GitHub ์ด์ ๋ฒํธ์ ๋๋ค. -
--action์๋move_pipeline(์นธ ์ด๋),close(๋ซ๊ธฐ),verify_closed(๋ซํ ๊ฒ์ฆ),update(๋ด์ฉ ๊ฐฑ์ ),get_status(์ํ ์กฐํ)๋ฅผ ์ธ ์ ์์ต๋๋ค. --pipeline์ ์ฎ๊ธธ ๋ณด๋ ์นธ ์ด๋ฆ์ผ๋ก,move_pipeline์ผ ๋๋ง ํจ๊ป ์๋๋ค.-
๋ณํฉ ํ์๋
verify_closed๋ฅผ ๊ถ์ฅํฉ๋๋ค. GitHub์Closes #์๋ ๋ซ๊ธฐ๊ฐ ์กฐ์ฉํ ์คํจํ ์ ์๋๋ฐ, ์ด ๋์์ด ์์ชฝ์ ํ์ธํ๊ณ ๋น ์ง ๋ถ๋ถ์ ์ง์ ๋ณด์ ํด ์ค๋๋ค.
์์์ ๋ฌด์จ ์ผ์ด ๋ฒ์ด์ง๋์#
์์ฒญํ --action์ ๋ฐ๋ผ ๋ค์ ํ๋ฆ์ผ๋ก ๋์ํฉ๋๋ค.
- ์นธ ์ด๋(move_pipeline) โ ์ด์ ์ ๋ณด๋ฅผ ์กฐํํด ํ์ฌ ์นธ์ ํ์ธํ๊ณ , ๋ชฉํ ์นธ์ผ๋ก ์ฎ๊ธด ๋ค, ์ ์ฎ๊ฒจ์ก๋์ง ๊ฒฐ๊ณผ๋ฅผ ํ์ธํฉ๋๋ค.
- ๋ซ๊ธฐ(close) โ ์ด์๋ฅผ
CLOSED๋ก ๋ฐ๊พธ๊ณ , ๋ถ๋ชจ ์ด์๊ฐ ์์ผ๋ฉด ์์์ด ์ ๋ถ ๋๋ฌ๋์ง ํ์ธํด ๊ทธ๋ ๋ค๋ฉด ๋ถ๋ชจ๋ ๋ซ์ต๋๋ค. -
๋ซํ ๊ฒ์ฆ(verify_closed) โ ์ง์ง ๊ธฐ์ค์ธ GitHub ์ํ๋ฅผ ๋จผ์ ํ์ธํ๊ณ , ๋ซํ ์์ง ์์ผ๋ฉด ์ง์ ๋ซ์ต๋๋ค. ๊ทธ๋ค์ ZenHub๊ฐ
Closed์นธ์ผ๋ก ๋๊ธฐํ๋๋์ง ํ์ธํ๊ณ , ๋๋ฝ๋์ผ๋ฉด ์ง์ ๋ณด์ ํฉ๋๋ค. - ์ํ ์กฐํ(get_status) โ ์ด์์ ์ํ, ๋ณด๋ ์์น, ๋ถ๋ชจ-์์ ๊ด๊ณ๋ฅผ ๊ทธ๋๋ก ๋ฐํํฉ๋๋ค.
โ๏ธ ์์ธ ์ต์ ยท์คํ ๋ช ์ธ (๊ฐ๋ฐ์ / AI ์์ด์ ํธ์ฉ)
Role and Responsibilities#
This agent manages ZenHub issue states.
- Pipeline Move: Move issues to the appropriate Pipeline
- Issue Close: Auto-close issues after PR merge
- Closure Verification: After merge, verify the issue reached
Closedon both GitHub and ZenHub, and fall back to an explicit close if not - Status Query: Check current issue state
- Parent Issue Management: Update parent issue when all children are completed
โ ๏ธ ZenHub Two-State Model (read first)#
ZenHub tracks two independent states; conflating them causes "issue stuck open":
| State | Owner | Meaning |
|---|---|---|
| Pipeline | ZenHub | Board column (In Progress, Review/QA, Done, โฆ). Just a position. |
| GitHub state | GitHub | The real open / closed flag. |
Donepipeline โ closed โ an issue inDoneis stillopenon GitHub.Closedpipeline = GitHubclosed, 1:1 โ judge open/closed by state, not pipeline.- This repo's policy is "merge = Close" (B): AI agents run full-stack E2E/review before merge, so
Doneis not used. A PR merging withCloses #Nauto-closes the GitHub issue โ ZenHub syncs toClosedโ butCloses #Nfires only when the PR merges into the repo's default branch. - Hierarchical merges into a parent work-base branch (
story/,epic/) never auto-close (base โ default branch);Closes #Ncan also silently miss / lag on default-branch merges. So after every merge the agent runsaction: verify_closed, which closes explicitly (gh issue close+ ZenHubstate:CLOSED). For hierarchical merges this explicit close is the primary mechanism, not a fallback.
Input Parameters#
| Parameter | Required | Type | Description |
|---|---|---|---|
issue_number | โ | number | GitHub issue number |
action | โ | string | move_pipeline | close | verify_closed | update | get_status |
target_pipeline | โ | string | Target Pipeline (when action=move_pipeline) |
update_body | โ | string | Content to update (when action=update) |
Output#
interface IssueStateResult {
success: boolean;
issue_number: number;
current_pipeline: string;
previous_pipeline?: string;
state: ' OPEN ' | ' CLOSED ' ;
parent_issue?: {
number: number;
all_children_done: boolean;
};
error?: string;
}Pipeline Structure#
Petmedi Workspace Pipeline#
New Issues โ Product Backlog โ Sprint Backlog โ In Progress โ Review/QA โ [Merge = Close โ Closed]| Pipeline | Description | Entry Condition |
|---|---|---|
| New Issues | Newly created issues | On issue creation |
| Product Backlog | Awaiting prioritization | After triage |
| Sprint Backlog | Assigned to sprint | During sprint planning |
| In Progress | Work in progress | On branch creation |
| Review/QA | Awaiting review | On PR creation โ deployment-gated trackers (Jira/Unibook): on staging deploy instead (stay In Progress through PR + merge). See deployment-gated-status. |
Not used (Done โ closed; would leave issue open) | ||
| Closed | Completed & closed | PR merge (Closes #) โ GitHub closed โ ZenHub sync. Verify + fallback. (Not applicable to deployment-gated Jira โ no Closes # auto-close.) |
Pipeline Moves by Workflow#
Moves During Issue Cycle#
1. Branch creation complete
โ move_pipeline: " In Progress "
2. PR creation complete
โ move_pipeline: " Review/QA "
โ โ ๏ธ deployment-gated trackers (Jira/Unibook): SKIP โ keep " In Progress " ;
move to " In Review " only when the merge is reflected in the staging deploy
(see rules/deployment-gated-status.md)
3. PR merge complete (merge = Close)
โ Issue auto-closes via GitHub " Closes # " keyword โ ZenHub syncs to Closed
โ Never move to Done (Done โ closed)
โ action: verify_closed (verify GitHub closed + ZenHub Closed, fallback if not)
4. All child issues completed (parent issue)
โ Close parent issue (directly Close, never via Done)Execution Flow#
action: move_pipeline#
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Step 1: Query Issue Info โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ mcp__zenhub__searchLatestIssues โ
โ - Query issue ID โ
โ - Check current Pipeline โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Step 2: Move Pipeline โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ mcp__zenhub__moveIssueToPipeline โ
โ - issueId: {issue_graphql_id} โ
โ - pipelineId: {target_pipeline_id} โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Step 3: Verify Result โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ - Verify move success โ
โ - Return current Pipeline โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโaction: close#
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Step 1: Close Issue โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ mcp__zenhub__updateIssue โ
โ - issueId: {issue_graphql_id} โ
โ - state: " CLOSED " โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Step 2: Check Parent Issue โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ IF parent issue exists: โ
โ - Check status of all child issues โ
โ - If all completed โ close parent issue โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโaction: verify_closed (after every merge) โญ#
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Step 1: Verify GitHub state (source of truth) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ $ gh issue view {N} --json state -q .state โ
โ IF state != " CLOSED " : โ
โ # `Closes #N` did not fire โ close explicitly โ
โ $ gh issue close {N} --reason completed โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Step 2: Verify ZenHub synced to Closed pipeline โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ mcp__zenhub__searchClosedIssues({ query: " #{N} " }) โ
โ IF issue not in closed results (sync lag): โ
โ mcp__zenhub__updateIssue({ issueId, state: " CLOSED " })โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ// Closure verification + fallback
async function verifyClosed(issueNumber: number, issueId: string) {
// 1. GitHub is the source of truth for open/closed
const ghState = (await Bash(`gh issue view ${issueNumber} --json state -q .state`)).trim();
if (ghState !== " CLOSED " ) {
await Bash(`gh issue close ${issueNumber} --reason completed`);
}
// 2. Confirm ZenHub synced GitHub closed โ Closed pipeline
const closed = await mcp__zenhub__searchClosedIssues({ query: `#${issueNumber}` });
if (!closed.find(i = > i.number === issueNumber)) {
await mcp__zenhub__updateIssue({ issueId, state: " CLOSED " });
}
}action: get_status#
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Query Issue Status โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ mcp__zenhub__searchLatestIssues โ
โ - Return issue state โ
โ - Return Pipeline info โ
โ - Return parent/child relationships โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโMCP Tool Usage#
Issue Query#
// Search by issue number
const result = await mcp__zenhub__searchLatestIssues({
query: " 25 "
});
// Extract issue info from result
const issue = result.find(i = > i.number === 25);
const issueId = issue.id; // GraphQL ID
const pipelineId = issue.pipelineIssue.pipeline.id;Pipeline Move#
// Query pipeline list
const pipelines = await mcp__zenhub__getWorkspacePipelinesAndRepositories();
// Find Pipeline ID
const targetPipeline = pipelines.pipelines.find(
p = > p.name === " In Progress "
);
// Move issue
await mcp__zenhub__moveIssueToPipeline({
issueId: issueId,
pipelineId: targetPipeline.id
});Issue Close#
// Change issue state
await mcp__zenhub__updateIssue({
issueId: issueId,
state: " CLOSED "
});Parent-Child Relationship Check#
// Check parentIssue field when querying issues
const issue = result[0];
if (issue.parentIssue) {
const parentNumber = issue.parentIssue.number;
// Check all children of the parent issue
}Pipeline ID Mapping#
Query Pipeline ID per Workspace#
// Query workspace info
const workspace = await mcp__zenhub__getWorkspacePipelinesAndRepositories();
// Pipeline list
workspace.pipelines.forEach(p = > {
console.log(`${p.name}: ${p.id}`);
});Pipeline Name โ ID Conversion#
// โ ๏ธ ๊ฐ์ ๋ผ์ด๋ธ ํ์ดํ๋ผ์ธ ์ด๋ฆ๊ณผ ์ ํํ ์ผ์นํด์ผ ํ๋ค.
// (๊ณผ๊ฑฐ ' Backlog ' / ' Review ' ๋ ์ค์ฌ ์ปฌ๋ผ์ด ์๋๋ผ find() ๊ฐ undefined โ silent no-op ์๋ค.)
const PIPELINE_NAMES = {
' new ' : ' New Issues ' ,
' icebox ' : ' Icebox ' ,
' backlog ' : ' Product Backlog ' ,
' sprint ' : ' Sprint Backlog ' ,
' progress ' : ' In Progress ' ,
' review ' : ' Review/QA ' ,
// ' done ' / ' closed ' ์์ โ Done ์ปฌ๋ผ ๋ฏธ์ฌ์ฉ, Closed ๋ GitHub state(์ปฌ๋ผ ์๋)
};
function getPipelineId(shortName: string, pipelines: Pipeline[]) {
const fullName = PIPELINE_NAMES[shortName];
const pipeline = pipelines.find(p = > p.name === fullName);
// fail-closed: ๋ชป ์ฐพ์ผ๋ฉด throw (undefined ๋ฅผ ๊ทธ๋๋ก move ์ ๋๊ฒจ ๋ฌด์ ๋๋ฝํ๋ ๊ฒ์ ๊ธ์ง)
if (!pipeline) {
throw new Error(
`Pipeline ' ${fullName ?? shortName} ' not found. Live: ${pipelines.map(p = > p.name).join( " , " )}`
);
}
return pipeline.id;
}Automatic Parent Issue Management#
Handle Parent When Child Is Completed#
1. Close child issue
โ
2. Query parent issue
โ
3. Check status of all child issues
โ
4. If all are CLOSED:
- Close parent issue (without moving to Done pipeline)Called from:
commands/dev/run.mdStep 12.5 (when the just-closed issue's type is Epic โ recurses to Project/Initiative) andcommands/dev/batch.mdPhase 3 Step 6.6 (after the Epic PR merges into development). Story-level closes do not trigger this โ only an Epic close recurses upward.
Epic Auto-Completion Condition#
async function checkAndCloseParent(childIssueNumber: number) {
// โ ๏ธ ZenHub MCP ์๋ getIssue / getChildrenOfParent ๊ฐ ์๋ค โ searchLatestIssues ๋ก๋ง ์กฐํํ๋ค.
const childRes = await mcp__zenhub__searchLatestIssues({ query: `#${childIssueNumber}` });
const child = childRes.find(i = > i.number === childIssueNumber);
const parent = child?.parentIssue; // { id, number }
if (!parent) return;
// ๋ถ๋ชจ์ ๋ชจ๋ ์์ ์กฐํ (๊ฒ์ DSL: parent: < graphqlId > )
const siblings = await mcp__zenhub__searchLatestIssues({ query: `parent:${parent.id}` });
// โ ๏ธ ๋น/์กฐํ์คํจ ๊ฒฐ๊ณผ๋ก ์ธํ ์กฐ๊ธฐ close ๋ฐฉ์ง:
// ์์์ด 1๊ฐ ์ด์์ด๊ณ ์ ๋ถ CLOSED ์ผ ๋๋ง ์งํ (๋น ๋ฐฐ์ด์ .every ๋ true ๊ฐ ๋์ด ์ํ).
if (!siblings || siblings.length === 0) return; // ํ์ ๋ถ๊ฐ โ close ์ ํจ
const allDone = siblings.every(s = > (s.state || " " ).toUpperCase() === " CLOSED " );
if (!allDone) return;
// ๋ถ๋ชจ๋ฅผ ๋ซ๋๋ค (merge=Close, Done ๋ฏธ์ฌ์ฉ). GitHub ๊ฐ source of truth โ ๋ช
์์ close ํ ์์ชฝ ๊ฒ์ฆ.
await Bash(`gh issue close ${parent.number} --reason completed`);
await mcp__zenhub__updateIssue({ issueId: parent.id, state: " CLOSED " });
const closed = await mcp__zenhub__searchClosedIssues({ query: `#${parent.number}` });
if (!closed.find(i = > i.number === parent.number)) {
await mcp__zenhub__updateIssue({ issueId: parent.id, state: " CLOSED " }); // ๋๊ธฐํ ์ง์ฐ ์ ์ฌ์๋
}
// ์กฐ๋ถ๋ชจ๋ ์ฌ๊ท์ ์ผ๋ก ํ์ธ (Story ์๋ฃ โ Epic ์๋ฃ โ Project ์๋ฃ ์ฐ์)
await checkAndCloseParent(parent.number);
}Usage Examples#
After Branch Creation#
# Move to In Progress
/dev:issue-state 25 --action move_pipeline --pipeline " In Progress "
# Result:
# issue_number: 25
# current_pipeline: " In Progress "
# previous_pipeline: " Sprint Backlog "After PR Creation#
# Move to Review
/dev:issue-state 25 --action move_pipeline --pipeline " Review/QA "After PR Merge (merge = Close)#
# Issue auto-closes via GitHub " Closes # " keyword โ ZenHub syncs to Closed.
# Never move to Done (Done โ closed). Then VERIFY + fallback:
/dev:issue-state 25 --action verify_closed
# 1. gh issue view 25 โ if not CLOSED โ gh issue close 25 --reason completed
# 2. searchClosedIssues " #25 " โ if missing โ updateIssue state:CLOSED
# Parent issue is auto-checked when all children are closedError Handling#
Common Errors#
| Error | Cause | Resolution |
|---|---|---|
Issue not found | Wrong issue number | Verify number |
Pipeline not found | Wrong Pipeline name | Verify name |
Already in pipeline | Already in that Pipeline | Skip |
Cannot close | No permission | Manual handling needed |
Recovery Strategy#
1. Log the error
2. Return current state
3. Report failure (success: false)Key Rules#
- State Tracking: Record state before and after every move
- Two-State Awareness: Pipeline โ GitHub state; judge closed by GitHub state, never by a
Done/Closedcolumn alone - Merge = Close: Never park merged issues in
Done; after merge, runverify_closedso GitHubclosedand ZenHubClosedare both confirmed - Automatic Parent Management: Auto-check parent when child is completed
- Idempotency: Safely skip when moving to the same Pipeline / when already closed
- Failure Tolerance: Continue workflow even on failure
- Detailed Logging: Detailed logs for all state changes