skills/mav-github-issue-workflow/SKILL.md
Standard patterns for interacting with GitHub issues — reading, commenting, updating, state tracking, branching, and PR creation. Use as a dependency from workflow skills, not directly.
npx skillsauth add thermiteau/maverick mav-github-issue-workflowInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Depends on: mav-git-workflow
Reusable patterns for GitHub issue interactions. Workflow skills (e.g., do-issue-guided, do-issue-solo) reference this skill for consistent GitHub operations.
Maintain a local state file at .claude/issue-state.json to track progress across sessions and subagents. This file MUST be gitignored.
{
"issue": 42,
"repo": "owner/repo",
"branch": "feat/42-short-description",
"phase": "understand|design|plan|branch|implement|complete",
"comments": {
"design": null,
"plan": null,
"completion": null
}
}
Create the state file at the start of work. Infer the repo from the current git remote.
REPO=$(gh repo view --json nameWithOwner -q '.nameWithOwner')
mkdir -p .claude
cat > .claude/issue-state.json << EOF
{
"issue": $ISSUE_NUMBER,
"repo": "$REPO",
"branch": null,
"phase": "understand",
"comments": {
"design": null,
"plan": null,
"completion": null
}
}
EOF
cat .claude/issue-state.json
Use jq (or equivalent) to update individual fields:
# Update phase
jq '.phase = "design"' .claude/issue-state.json > .claude/issue-state.tmp && mv .claude/issue-state.tmp .claude/issue-state.json
# Update branch
jq '.branch = "feat/42-add-export"' .claude/issue-state.json > .claude/issue-state.tmp && mv .claude/issue-state.tmp .claude/issue-state.json
Before creating the state file, ensure it is gitignored:
grep -q 'issue-state.json' .gitignore 2>/dev/null || echo '.claude/issue-state.json' >> .gitignore
Always fetch structured JSON to get the full picture including comments.
gh issue view $ISSUE_NUMBER --json title,body,labels,assignees,milestone,comments,state
# Just the body
gh issue view $ISSUE_NUMBER --json body -q '.body'
# Labels as comma-separated list
gh issue view $ISSUE_NUMBER --json labels -q '[.labels[].name] | join(", ")'
# Comment count
gh issue view $ISSUE_NUMBER --json comments -q '.comments | length'
Use one comment per artifact (design, plan, completion). Capture the comment ID so it can be updated later.
Maverick-generated comments — design, plan, tasks, completion, PR-review verdicts, and updates to any of those — are posted as the Maverick GitHub App so they are visually distinct from the human user's edits. Use uv run maverick gh-app gh -- <args> for any write that produces a comment. Plain gh is fine for read operations (gh issue view, gh api ... --jq '.body').
Failure modes — do not silently switch identity mid-workflow:
uv run maverick gh-app gh ... exits with code 4 and a GitHub App not configured message. For solo / ad-hoc single-machine use this is acceptable; fall back to plain gh issue comment ... (the comment will be authored by the human user). The fallback only applies to the entire session, not per-call: if the App is configured, every comment goes through it.issues:write or pull_requests:write — the underlying gh call fails with HTTP 403. This is a setup error, not a runtime fallback. Stop the workflow and tell the user to grant the permission via the GitHub App settings, then re-run. Do not retry as the human user.COMMENT_URL=$(uv run maverick gh-app gh -- issue comment $ISSUE_NUMBER --body "$(cat <<'EOF'
## Section Title
Content here
---
*Posted by Claude Code*
EOF
)" 2>&1)
Extract the comment ID from the URL for later updates:
# gh issue comment prints the URL to stdout, extract the numeric ID
COMMENT_ID=$(echo "$COMMENT_URL" | grep -o '[0-9]*$')
Then save to state:
jq ".comments.design = $COMMENT_ID" .claude/issue-state.json > .claude/issue-state.tmp && mv .claude/issue-state.tmp .claude/issue-state.json
COMMENT_URL=$(uv run maverick gh-app gh -- issue comment $ISSUE_NUMBER --body "$(cat <<'DESIGN_EOF'
## Solution Design
### Approach
<high-level description>
### Areas Affected
- <list of packages/files>
### Key Decisions
- <architectural choices and rationale>
### Risks / Open Questions
- <anything that might complicate implementation>
---
*Posted by Claude Code*
DESIGN_EOF
)" 2>&1)
COMMENT_ID=$(echo "$COMMENT_URL" | grep -o '[0-9]*$')
jq ".comments.design = $COMMENT_ID | .phase = \"design\"" .claude/issue-state.json > .claude/issue-state.tmp && mv .claude/issue-state.tmp .claude/issue-state.json
COMMENT_URL=$(uv run maverick gh-app gh -- issue comment $ISSUE_NUMBER --body "$(cat <<'PLAN_EOF'
## Implementation Plan
1. **Step name**
- Files: `path/to/file.ts`
- Change: <what the change does>
- Verify: <test command or check>
2. **Step name**
- Files: `path/to/file.ts`
- Change: <what the change does>
- Verify: <test command or check>
---
*Posted by Claude Code*
PLAN_EOF
)" 2>&1)
COMMENT_ID=$(echo "$COMMENT_URL" | grep -o '[0-9]*$')
jq ".comments.plan = $COMMENT_ID | .phase = \"plan\"" .claude/issue-state.json > .claude/issue-state.tmp && mv .claude/issue-state.tmp .claude/issue-state.json
BRANCH=$(jq -r '.branch' .claude/issue-state.json)
COMMENT_URL=$(uv run maverick gh-app gh -- issue comment $ISSUE_NUMBER --body "$(cat <<DONE_EOF
## Implementation Complete
**Branch:** \`$BRANCH\`
### Changes Made
- <summary of changes>
### Verification
- [ ] Linting passes
- [ ] Tests pass
---
*Posted by Claude Code*
DONE_EOF
)" 2>&1)
COMMENT_ID=$(echo "$COMMENT_URL" | grep -o '[0-9]*$')
jq ".comments.completion = $COMMENT_ID | .phase = \"complete\"" .claude/issue-state.json > .claude/issue-state.tmp && mv .claude/issue-state.tmp .claude/issue-state.json
When a design or plan is revised, update the existing comment instead of posting a new one. Read the comment ID from the state file.
REPO=$(jq -r '.repo' .claude/issue-state.json)
COMMENT_ID=$(jq -r '.comments.design' .claude/issue-state.json)
uv run maverick gh-app gh -- api "repos/$REPO/issues/comments/$COMMENT_ID" \
-X PATCH \
-f body="$(cat <<'EOF'
## Solution Design (Revised)
<updated content>
---
*Posted by Claude Code*
EOF
)"
The update goes through the same App identity as the original post, so the comment author stays consistent across edits.
Follow the mav-git-workflow skill for branch naming conventions, base branch identification, and branch creation.
After creating the branch, save to state:
jq ".branch = \"$BRANCH_NAME\" | .phase = \"branch\"" .claude/issue-state.json > .claude/issue-state.tmp && mv .claude/issue-state.tmp .claude/issue-state.json
ISSUE_NUMBER=$(jq -r '.issue' .claude/issue-state.json)
BRANCH=$(jq -r '.branch' .claude/issue-state.json)
PR_TARGET=$(uv run maverick git-workflow pr-target)
git push -u origin $BRANCH
gh pr create --base "$PR_TARGET" --title "<concise title>" --body "$(cat <<PR_EOF
## Summary
<1-3 bullet points>
Closes #$ISSUE_NUMBER
## Test Plan
- [ ] <verification steps>
---
*Created by Claude Code*
PR_EOF
)"
Using Closes #N in the PR body automatically closes the issue when the PR is merged.
When resuming work on an issue (new session, after crash, subagent picking up):
digraph resume {
"State file exists?" [shape=diamond];
"Read state file" [shape=box];
"Read phase" [shape=box];
"Continue from current phase" [shape=box];
"Ask user for issue number" [shape=box];
"Initialise state" [shape=box];
"State file exists?" -> "Read state file" [label="yes"];
"State file exists?" -> "Ask user for issue number" [label="no"];
"Read state file" -> "Read phase";
"Read phase" -> "Continue from current phase";
"Ask user for issue number" -> "Initialise state";
"Initialise state" -> "Continue from current phase";
}
.claude/issue-state.json existsAfter the PR is created and the workflow is complete:
rm .claude/issue-state.json
Do not delete the state file until the PR is successfully created, as it is needed for crash recovery.
<!-- maverick-plugin-version: 3.3.6 -->development
--- name: do-test description: Write or update tests for a code change. Operates in two modes: `unit` (module-scoped, fast, deterministic) and `integration` (crosses module / service / database boundaries). Intended to be invoked once per testable change from inside a do-issue-* or do-epic phase. Mode is required. argument-hint: mode: unit or integration user-invocable: true disable-model-invocation: false --- **Depends on:** mav-bp-unit-testing, mav-bp-integration-testing, mav-local-verificati
development
Implement a focused code change. Use this skill as the wrapper for any implementation work so the Maverick workflow report captures what was done and so the agent applies the project's coding standards before editing. Intended to be invoked once per task from inside a do-issue-* or do-epic phase, not standalone.
testing
How to stack a PR on top of an unmerged sibling branch, and how to retarget it to the repo's default branch once the sibling merges. Prevents orphan-merge incidents when a dependent story is ready before its parent.
development
Claim, lease, heartbeat, and release protocols for when multiple Claude Code instances may act on the same issue or epic concurrently. GitHub labels and marker comments are the coordination surface; local state is a cache.