Manage GitHub sub-issues and dependencies (blocked_by/blocking). Use when breaking issues into sub-tasks, checking progress, or viewing a dependency graph.
Manage GitHub sub-issues and dependencies (blocked_by/blocking). Use when breaking issues into sub-tasks, checking progress, or viewing a dependency graph.
Adding/removing native GitHub sub-issues to a parent issue
Use git-issue-manage for transfer, pin, lock, develop-branch operations
Marking issue A as blocked_by issue B (or unblocking)
Use github-issue-writing to create well-structured issue bodies in the first place
Viewing a parent issue's sub-issue completion progress and dependency graph
Use git-issue to actually start working on issues end-to-end
Checking the dependency graph before starting work on a multi-issue feature
Use gh-cli-agentic for raw gh issue --json queries without hierarchy logic
Context
Repo: !git remote -v
Parent issue: (parsed from arguments)
Parameters
Parse these parameters from the command:
Every issue argument — <parent-issue> and each <N> flag value below — is
accepted as a bare number, #N, or a full GitHub issue URL, and normalized to a
(number, repo) pair in Step 0.
Parameter
Description
<parent-issue>
Parent issue as a bare number, #N, or full GitHub issue URL (https://github.com/<owner>/<repo>/issues/<N>) — see Step 0
--add <N...>
Add existing issues as sub-issues (each N as number, #N, or URL)
--create "<title>"
Create a new issue and add it as sub-issue
--remove <N...>
Remove sub-issues from parent (each N as number, #N, or URL)
--status
Show sub-issue completion progress
--list
List all sub-issues of the parent
--deps
Show dependency graph (blocked_by + blocking + sub-issues) for the issue
--blocking
List issues the parent is blocking
--block <N>
Mark issue N as blocked by the parent (parent blocks N); N as number, #N, or URL
--blocked-by <N>
Mark the parent as blocked by issue N; N as number, #N, or URL
--unblock <N>
Remove blocking relationship with issue N in either direction; N as number, #N, or URL
When to Use
Use this skill when...
Use X instead when...
Breaking issues into sub-tasks
Creating standalone issues (github-issue-writing)
Checking sub-issue completion progress
Implementing/processing issues (git:issue)
Recording blocked_by / blocking dependencies
Auto-detecting related issues (github-issue-autodetect)
Viewing a blocker graph before picking work
Searching for OSS solutions (github-issue-search)
Sub-issues vs. dependencies vs. "related to"
GitHub ships three distinct ways to link issues. Pick the right one — they're
not interchangeable:
Relationship
When to use
API surface
Sub-issue (parent ↔ child)
Child issue is a part of the parent's scope. Completing all children fulfils the parent.
issues/{N}/sub_issues
Blocked by (hard dependency)
Parent cannot start or ship until the other issue closes. Makes the blocked issue render a "Blocked" badge on boards.
issues/{N}/dependencies/blocked_by
Blocking (read-only inverse)
You want to see everything this issue gates. Managed by creating blocked_by links on the other side.
issues/{N}/dependencies/blocking
"Related to #N" in body
Soft cross-reference, no lifecycle coupling, no board indicator.
Plain markdown — no API needed
Sub-issues express composition ("is part of"). Dependencies express
ordering ("must happen before"). The same two issues should rarely use
both — a sub-issue is implicitly ordered by its parent's scope.
Execution
Execute the requested issue hierarchy operation.
Step 0: Normalize issue references
Before any API call, normalize <parent-issue>and every issue value passed
to a flag (--add, --remove, --block, --blocked-by, --unblock) into a
(number, repo) pair. Accept these forms:
Input form
Extract
123
number 123, repo = current remote
#123
number 123, repo = current remote
https://github.com/<owner>/<repo>/issues/123
number 123, repo = <owner>/<repo>
.../issues/123#issuecomment-...
number 123 (drop the #... fragment), repo = <owner>/<repo>
Rules:
Strip a leading #; strip a URL #... fragment after the number. A token is
an issue ref only if, after stripping, it is all digits or matches the
/issues/<digits> URL shape (require trailing digits — a /pull/<N>,
/discussions/<N>, or bare /issues list URL is not a ref).
For the plural flags (--add/--remove, which take <N...>), normalize
each space-separated value independently; never split a single URL into two
refs.
For a URL whose <owner>/<repo> differs from the current remote (Step 1
REPO), record it as cross-repo and carry -R <owner>/<repo> on every
gh issue call and target repos/<owner>/<repo>/… on every gh api call
for that issue. Sub-issue and dependency links require both endpoints to live
in the same repo — if a normalized ref points at a different repo than
the parent, surface that rather than issuing a mismatched cross-repo link.
Downstream steps use the normalized $PARENT, $N, and $CHILDnumbers;
where a ref was cross-repo, substitute its <owner>/<repo> for $OWNER/$REPO_NAME
and add -R <owner>/<repo> to the corresponding gh issue call.
If Step 0 normalized <parent-issue> to a cross-repo URL, use that URL's
<owner>/<repo> as $OWNER/$REPO_NAME (and pass -R <owner>/<repo> to the
gh issue view below) instead of the current remote.
Determine which operation to perform based on parsed parameters.
If --status (or no flags):
Display sub-issue summary and list.
If --add:
Add existing issues as sub-issues.
If --create:
Create new issue, then add as sub-issue.
If --remove:
Remove specified sub-issues.
If --list:
List all sub-issues with their states.
If --deps, --blocking, --block, --blocked-by, --unblock:
Manage native GitHub issue dependencies via the dependencies/blocked_by and
dependencies/blocking API endpoints.
Step 3: Execute API Calls
Sub-Issue Status
# Get summary
gh issue view $PARENT --json title,state,subIssuesSummary
# List all sub-issues with details
gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/sub_issues \
--jq '.[] | "#\(.number) \(.state) \(.title)"'
# Get the issue's node ID (required for sub_issue_id)
CHILD_ID=$(gh api repos/$OWNER/$REPO_NAME/issues/$CHILD --jq '.id')
# Add as sub-issue
gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/sub_issues \
-f sub_issue_id=$CHILD_ID
Verify each was added successfully. Report any errors (e.g., issue not found, already a sub-issue, sub-issues not enabled).
Create and Add Sub-Issue
# Create the new issue
NEW_ISSUE=$(gh issue create --title "$TITLE" --body "Parent: #$PARENT" --json number --jq '.number')
# Get its ID
NEW_ID=$(gh api repos/$OWNER/$REPO_NAME/issues/$NEW_ISSUE --jq '.id')
# Add as sub-issue
gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/sub_issues \
-f sub_issue_id=$NEW_ID
Remove Sub-Issues
For each issue number in --remove:
# Get the sub-issue ID from the sub-issues list
SUB_ISSUE_ID=$(gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/sub_issues \
--jq ".[] | select(.number == $CHILD) | .id")
# Remove it
gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/sub_issues/$SUB_ISSUE_ID -X DELETE
Dependency Management
Dependencies use GitHub's native dependencies/blocked_by and
dependencies/blocking endpoints. They appear in the issue sidebar under
"Relationships" and mark the blocked issue with a "Blocked" badge on project
boards. Both endpoints require the target issue's node id (.id on the
issue payload), not the human-readable issue number.
Add "blocked by" relationship (--blocked-by <N>): parent is blocked by N
# Resolve the blocker's node id
BLOCKER_ID=$(gh api repos/$OWNER/$REPO_NAME/issues/$N --jq '.id')
# Record the dependency on the parent
gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/dependencies/blocked_by \
-f issue_id=$BLOCKER_ID
Add "blocks" relationship (--block <N>): parent blocks issue N
The API is one-directional — write the relationship on the blocked side:
# Resolve the parent's node id
PARENT_ID=$(gh api repos/$OWNER/$REPO_NAME/issues/$PARENT --jq '.id')
# Record on issue N that it is blocked by the parent
gh api repos/$OWNER/$REPO_NAME/issues/$N/dependencies/blocked_by \
-f issue_id=$PARENT_ID
Remove relationship (--unblock <N>):
Look up which side carries the link, then delete it. The DELETE path takes
the stored dependency's {issue_id} segment:
# Is the parent blocked by N?
gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/dependencies/blocked_by \
--jq ".[] | select(.number == $N) | .id"# Or does the parent block N?
gh api repos/$OWNER/$REPO_NAME/issues/$N/dependencies/blocked_by \
--jq ".[] | select(.number == $PARENT) | .id"# Delete whichever is present
gh api repos/$OWNER/$REPO_NAME/issues/$ISSUE/dependencies/blocked_by/$DEP_ID \
-X DELETE
List what the parent blocks (--blocking):
gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/dependencies/blocking \
--jq '.[] | "#\(.number) \(.state) \(.title)"'
Show dependency graph (--deps):
Combine both dependency endpoints with the sub-issues summary. Do not parse
issue bodies — the native API is authoritative:
# What blocks the parent
gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/dependencies/blocked_by \
--jq '.[] | "#\(.number) \(.state) \(.title)"'# What the parent blocks
gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/dependencies/blocking \
--jq '.[] | "#\(.number) \(.state) \(.title)"'# Sub-issues (composition, not ordering)
gh api repos/$OWNER/$REPO_NAME/issues/$PARENT/sub_issues \
--jq '.[] | "#\(.number) \(.state) \(.title)"'