Rely on Native Projects v2 Workflows for Status Hygiene; Add a Hook Only for the In-Progress Gap
ADR-0003: Rely on Native Projects v2 Workflows for Status Hygiene; Add a Hook Only for the In-Progress Gap
Section titled “ADR-0003: Rely on Native Projects v2 Workflows for Status Hygiene; Add a Hook Only for the In-Progress Gap”Status
Section titled “Status”Accepted
Context
Section titled “Context”Background and Problem Statement
Section titled “Background and Problem Statement”During a multi-PR campaign against this org’s Projects v2 board (project #1),
issue Status fields were set by hand through the github-sdlc-planning
plugin’s set_field_value MCP tool: Todo when an issue was filed, In
Progress when work began, with the intent to set Done once the closing PR
merges. Partway through, the board was found to already carry board items
whose Status did not match anything any tool call in the session had set:
one item showed In Progress and another showed Done while its closing PR
was still open, both attributed to the same account the session’s own token
authenticates as, at timestamps the session’s own tool-call log does not
account for.
Investigating found the underlying cause: this org project has all eleven
of GitHub’s built-in Projects v2 workflows enabled (Auto-add to project,
Auto-add sub-issues to project, Item added to project, Item closed,
Item reopened, Pull request linked to issue, Pull request merged,
Code review approved, Code changes requested, Auto-close issue,
Auto-archive items, confirmed via the ProjectV2.workflows GraphQL
field, enabled: true on every one). Two consequences follow. First, the
same issue was being added to the board twice: once by Auto-add to project/Auto-add sub-issues to project firing the moment the issue was
created, and a second time by the session’s own explicit
add_item_to_project call moments later, since addProjectV2ItemById has
no idempotency key and happily creates a duplicate item for content already
on the board. Second, at least part of the unexplained status drift is
plausibly this same native automation moving a board item’s Status when a
PR referencing it is opened, closed, or merged, running concurrently with
and independently of any manual set_field_value call the session made
against a different (often the wrong, duplicate) item.
Current Limitations
Section titled “Current Limitations”- Manual status-setting duplicates work the platform already does.
Item added to project,Item closed, andPull request mergedcover Todo-on-add and Done-on-close-or-merge natively; a plugin or hook that also sets these transitions by hand is redundant at best. - Manual calls can target the wrong item when duplicates exist. Native
auto-add and an explicit
add_item_to_projectcall can each create a separate item for the same issue; a subsequentset_field_valueagainst one does not affect the other, and nothing surfaces the mismatch. - No native signal exists for “work has begun.” GitHub’s built-in workflows react to the item being added, closed, reopened, or linked to a PR; there is no built-in transition for “an agent or contributor started working on this issue” before any PR exists to link.
- No Claude Code hook currently addresses any of this.
github-sdlc-planning’s three hooks (session-start.mjs, fetches open milestones and injects them as session context;validate-mif.mjs, MIF frontmatter conformance;confirm-mutation.mjs, a confirmation prompt before a board mutation) do not watch for or nudge a status transition of any kind.
Decision Drivers
Section titled “Decision Drivers”Primary Decision Drivers
Section titled “Primary Decision Drivers”- No duplicated automation. When GitHub’s own Projects v2 workflow already performs a status transition, this org’s tooling shall not re-implement or race against it with a manual mutation.
- No silent duplicate board items. Adding an issue to a project via the MCP tool shall not create a second item when the issue is already on the board through native auto-add.
- The in-progress gap shall be closed. An issue that a contributor or agent has started active work on, with no PR yet open, shall be visible as In Progress on the board without requiring a human to remember to drag the card.
Secondary Decision Drivers
Section titled “Secondary Decision Drivers”- Minimal new surface. Whatever closes the in-progress gap should be the smallest addition that does the job, not a general-purpose workflow engine.
- Auditable. Whatever sets In Progress should leave a clear trace of why (which tool call, which issue) rather than an unexplained status change like the ones this ADR investigates.
Considered Options
Section titled “Considered Options”Option 1: Custom GitHub Actions workflow reacting to issue/PR events
Section titled “Option 1: Custom GitHub Actions workflow reacting to issue/PR events”Description: A repo-level Actions workflow on issues and
pull_request events that mints a token and calls the Projects v2 GraphQL
API directly to set Status, replacing reliance on the built-in workflows
entirely.
Technical Characteristics: Full control over transition logic and target status values; requires its own token-minting, permission scoping, and testing.
Advantages: Works identically regardless of which MCP tool or UI action created the PR or issue; not tied to Claude Code being in the loop.
Disadvantages: Duplicates functionality the platform already provides for every transition except in-progress; adds a new maintained component whose only job partially overlaps with a toggle already enabled in the project’s settings.
Disqualifying Factor: violates the no-duplicated-automation driver for the majority of the transitions it would handle; the org project already has this coverage switched on.
Risk Assessment:
- Technical Risk: Medium. A custom implementation can drift from or conflict with the native workflows it overlaps, as this investigation’s own duplicate-item and unexplained-status findings demonstrate.
- Schedule Risk: Medium. New workflow, new token/permission surface, new tests.
- Ecosystem Risk: Low.
Option 2: Rely on native workflows for closed/merged/added; add a narrow hook for in-progress only
Section titled “Option 2: Rely on native workflows for closed/merged/added; add a narrow hook for in-progress only”Description: Do nothing new for Todo-on-add, Done-on-close, and
Done-on-merge; the native workflows already listed above cover them.
Close the one real gap, marking In Progress before a PR exists, with a
small addition to github-sdlc-planning’s existing hook set: a
PostToolUse hook on create_issue/add_sub_issue/an explicit
“start work” signal that calls set_field_value to move the item to In
Progress, and nothing else. Also fix add_item_to_project (or the tool
that wraps it) to check for an existing item on the board for that content
before creating a new one, closing the duplicate-item gap directly.
Technical Characteristics: One new hook, one existing-tool guard; no new GitHub Actions workflow; no change to how Todo/Done transitions happen.
Advantages: Satisfies every primary driver: no duplicated automation
for the transitions the platform already handles, no silent duplicates
once add_item_to_project checks first, and the in-progress gap closes
with the smallest addition that does the job.
Disadvantages: The in-progress hook only fires when the triggering action goes through a Claude Code session using this plugin’s own tools; work started by a human directly on GitHub, or by an agent using a different tool path, would not trigger it. This is an accepted, bounded gap (see Consequences), not a blocking one, since the alternative (Option 1’s server-side coverage) would itself still miss “work has begun” with no PR yet, for the same reason: there is no native GitHub event for that transition regardless of who or what initiates it.
Risk Assessment:
- Technical Risk: Low. Reuses the existing hook and mutation-tool infrastructure; the idempotency check is a straightforward query-before- mutate addition.
- Schedule Risk: Low. Small, scoped change.
- Ecosystem Risk: Low.
Option 3: Do nothing; keep setting status by hand
Section titled “Option 3: Do nothing; keep setting status by hand”Description: Continue relying on a human or agent to remember to call
set_field_value at each transition, as before this investigation.
Technical Characteristics: No new code.
Advantages: Zero implementation cost.
Disadvantages: This is the status quo that produced the duplicate items and unexplained status drift this ADR investigates; it has already demonstrated it does not hold up under real, sustained multi-issue work.
Disqualifying Factor: fails the in-progress-gap driver by definition, and does nothing to prevent recurrence of the duplicate-item problem.
Risk Assessment:
- Technical Risk: High. Already observed to produce incorrect board state in practice.
- Schedule Risk: Low.
- Ecosystem Risk: Low.
Decision
Section titled “Decision”We adopt Option 2: rely on the org project’s already-enabled native Projects v2 workflows for Todo-on-add, Done-on-close, and Done-on-merge, and add a narrow, scoped fix for the two gaps native automation does not cover:
- Idempotent add: before
add_item_to_project(or its wrapping tool) creates a new item, it queries whether the target content already has an item on the project and returns that item instead of creating a duplicate. - In-progress hook: a new
PostToolUsehook inplugins/github-sdlc-planning/hooks/fires on tool calls that represent the start of active work (at minimum, a call to a tool that adds a sub-issue or begins implementation against an already-filed issue) and sets that issue’s board item to In Progress via the existingset_field_valuetool, if the item is not already past that state.
No new GitHub Actions workflow is introduced. No change is made to any of the eleven built-in workflows already enabled on the project.
Consequences
Section titled “Consequences”Positive
Section titled “Positive”- No more duplicate items or unexplained drift from tooling conflict: the idempotent-add fix removes the mechanism that produced the duplicate items this investigation found.
- In-progress is now visible without manual discipline: the one gap native automation leaves is closed by a small, auditable hook rather than depending on a human or agent remembering every time, which this session’s own experience shows does not hold up.
- No redundant custom automation to maintain: the org project’s existing, already-enabled workflows continue doing the work they were built for.
Negative
Section titled “Negative”- The in-progress hook only covers Claude-Code-mediated work. Work
started by a human directly on GitHub, or by tooling outside this
plugin, will not trigger it; mitigated by this being a strict
improvement over the current all-manual state, not a regression, and by
the native
Item added to projectworkflow still giving every issue a visible (if less precise) Todo starting state. - This ADR does not conclusively explain every anomaly it investigates.
The duplicate items are fully explained by native auto-add racing the
explicit tool call. The two specific unexplained status values found
during this investigation (one item showing In Progress, another
showing Done while its closing PR was still open) are plausibly native
Pull request linked to issue/Pull request mergedautomation acting on a duplicate item independently of the session’s own calls, but this was not conclusively traced to a specific event for either occurrence; mitigated by leaving those two items untouched pending direct owner review, as already agreed separately from this ADR.
Neutral
Section titled “Neutral”- The eleven built-in workflows’ exact target-status mappings are not
inspectable via the public API (the
ProjectV2WorkflowGraphQL type exposesname/enabled/timestamps only, not its configured field value), confirmed by introspection. Understanding precisely which status value each workflow applies requires checking the project’s settings UI directly; this ADR proceeds on the workflows’ documented, named behavior.
Decision Outcome
Section titled “Decision Outcome”The decision achieves its primary objective, correct board state without
duplicated automation, measured by: zero new duplicate items created by
add_item_to_project after the idempotency fix ships (verified against a
test board), and every issue an agent begins active work on showing In
Progress without a manual set_field_value call, verified in the next
campaign that exercises the hook.
Mitigations:
- The Claude-Code-only coverage gap is accepted and documented above, not silently left unaddressed.
- The two unexplained historical status values are excluded from this ADR’s scope and left for direct owner review, per prior agreement in the session that surfaced them.
Related Decisions
Section titled “Related Decisions”None yet; this is the first ADR addressing Projects v2 board-status automation in this repository.
- About workflows in Projects: GitHub Docs - the built-in workflow catalog this ADR relies on.
- Using the API to manage Projects: GitHub Docs -
confirms the
ProjectV2Workflowtype’s exposed fields.
More Information
Section titled “More Information”- Date: 2026-07-05
- Source: board-state investigation during a multi-PR campaign against org project #1; the duplicate-item and unexplained-status findings that motivated this ADR
- Related ADRs: none
2026-07-05
Section titled “2026-07-05”Status: Pending
Findings:
| Finding | Files | Lines | Assessment |
|---|---|---|---|
| Drafted as proposed; awaiting maintainer review | - | - | pending |
Summary: Drafted with Option 2 recommended after confirming, via the
ProjectV2.workflows GraphQL field, that all eleven built-in Projects v2
workflows are already enabled on this org project. The decision is not
binding until the status moves to accepted.
Action Required: Maintainer review; on acceptance, implement the
idempotent-add fix and the in-progress hook in
plugins/github-sdlc-planning.
2026-07-05
Section titled “2026-07-05”Status: Compliant
Findings:
| Finding | Files | Lines | Assessment |
|---|---|---|---|
| add_item_to_project is idempotent: existing item returned, no dup | plugins/github-sdlc-planning/mcp-server/src/tools/projects.ts | - | compliant |
| In-progress hook shipped, config-gated, no-ops without a configured board | plugins/github-sdlc-planning/hooks/ | - | compliant |
| Native Done-on-close validated live: issues #28-49/#51/#55 auto-set | - | - | compliant |
Summary: Accepted by the maintainer, with the two decision items
implemented in the same change that records this acceptance. One
implementation detail differs from the decision text: a hook process runs
outside the MCP JSON-RPC session and cannot invoke set_field_value
directly, so the in-progress hook performs the identical
updateProjectV2ItemFieldValue GraphQL mutation through gh api graphql
(the same graceful-degradation path session-start.mjs documents), gated
on a per-project settings file and skipping items already In Progress or
Done. The native-workflow half of the decision was validated live before
acceptance: every issue closed by the campaign’s merges had its board item
set to Done by the native Item closed automation with no manual call.
Action Required: None; this decision is in force.