GitHub Issue Tree: Implementing Jira-like Hierarchies
A comprehensive guide to creating Epic → Story → Task hierarchies in GitHub Issues using native task lists and the "Tracked by" feature.
Table of Contents
- Overview
- Why This Workaround
- Core Concepts
- Label Setup
- Issue Templates
- Creating the Hierarchy
- How "Tracked by" Works
- Naming Conventions
- Real-World Example
- Querying and Filtering
- Best Practices
- Limitations vs Jira
- Advanced Workflows
Overview
GitHub Issues doesn't natively support hierarchical issue types like Jira's Epic → Story → Task structure. However, by combining task lists with cross-references, we can create a surprisingly effective hierarchy that:
- Provides visual progress tracking with progress bars
- Shows automatic "Tracked by" relationships
- Maintains bidirectional linking between parent and child issues
- Works seamlessly with GitHub Projects and automation
- Requires no third-party tools or plugins
What we'll build:
Epic #100: User Authentication System
├── Progress: ████████░░ 80% (4 of 5 stories completed)
├── Story #101: Login Flow ✓
│ ├── Task #104: API endpoint ✓
│ ├── Task #105: UI component ✓
│ └── Task #106: Tests ✓
├── Story #102: Registration ✓
│ ├── Task #107: Validation logic ✓
│ └── Task #108: Email verification ✓
├── Story #103: Password Reset (In Progress)
│ ├── Task #109: Reset token generation ✓
│ └── Task #110: Email template (Open)
└── Story #111: OAuth Integration (Open)
Why This Workaround
The Problem
GitHub Issues is flat by design. While this simplicity works for many projects, teams migrating from Jira or managing complex features need:
- Hierarchical organization - Group related work into logical units
- Progress visibility - See completion status at a glance
- Scope management - Break large features into manageable pieces
- Sprint planning - Organize work into achievable increments
The Solution
GitHub's task list feature (introduced in 2022) provides:
- Checkboxes that link to issues -
- [ ] #123 - Automatic progress bars - Visual completion tracking
- Bidirectional linking - "Tracked by" appears on child issues
- Markdown-based - No special syntax or tools needed
When to Use This
Use hierarchical issues when:
- Developing complex features spanning multiple PRs
- Coordinating work across multiple team members
- Planning sprints or releases
- Migrating from Jira to GitHub
- Managing dependencies between issues
Don't overcomplicate small projects - flat issues work fine for simple bug fixes or single-person repositories.
Core Concepts
Three-Tier Hierarchy
| Level | Purpose | Typical Scope | Assignee |
|---|---|---|---|
| Epic | Large initiative spanning multiple sprints | 2-6 weeks, 3-10 stories | Product/Team Lead |
| Story | User-facing feature or capability | 3-5 days, 2-5 tasks | Feature Owner |
| Task | Technical implementation unit | 4-8 hours, 1 PR | Developer |
Task List Syntax
GitHub recognizes these patterns:
- [ ] #123 - Unchecked, links to issue #123
- [x] #124 - Checked, marks issue as complete
- [ ] Description without issue number - Simple checkbox
- [ ] #125 Story: Login implementation - Descriptive text
Important: GitHub automatically:
- Creates clickable links from issue numbers
- Shows progress bars when task lists contain issue references
- Adds "Tracked by #parent" to child issues
- Updates progress when child issues are closed
Visual Hierarchy
Epic (type:epic)
│
├─ Story (type:story, epic:100)
│ │
│ ├─ Task (type:task, story:101)
│ ├─ Task (type:task, story:101)
│ └─ Task (type:task, story:101)
│
└─ Story (type:story, epic:100)
│
└─ Task (type:task, story:102)
Label Setup
Labels help filter and organize hierarchical issues. Set up these labels once per repository.
Required Labels
Create labels using gh CLI:
# Issue type labels
gh label create "type:epic" \
--description "Large initiative spanning multiple stories" \
--color "7057ff"
gh label create "type:story" \
--description "User-facing feature or capability" \
--color "0e8a16"
gh label create "type:task" \
--description "Technical implementation unit" \
--color "1d76db"
# Status labels
gh label create "status:planning" \
--description "Still being defined" \
--color "fbca04"
gh label create "status:ready" \
--description "Ready for development" \
--color "0e8a16"
gh label create "status:in-progress" \
--description "Currently being worked on" \
--color "1d76db"
gh label create "status:blocked" \
--description "Cannot proceed due to dependency" \
--color "d93f0b"
gh label create "status:review" \
--description "Awaiting review or testing" \
--color "fbca04"
# Priority labels
gh label create "priority:high" \
--description "High priority work" \
--color "d93f0b"
gh label create "priority:medium" \
--description "Medium priority work" \
--color "fbca04"
gh label create "priority:low" \
--description "Low priority work" \
--color "0e8a16"
Optional Enhancement Labels
# Epic-specific relationship labels
gh label create "epic:auth" \
--description "Part of User Authentication epic" \
--color "7057ff"
gh label create "epic:payments" \
--description "Part of Payment Processing epic" \
--color "7057ff"
# Estimation labels (Fibonacci sequence)
gh label create "estimate:1" --color "c2e0c6"
gh label create "estimate:2" --color "c2e0c6"
gh label create "estimate:3" --color "c2e0c6"
gh label create "estimate:5" --color "c2e0c6"
gh label create "estimate:8" --color "c2e0c6"
gh label create "estimate:13" --color "c2e0c6"
Bulk Label Creation
Save this as labels.json:
[
{"name": "type:epic", "color": "7057ff", "description": "Large initiative spanning multiple stories"},
{"name": "type:story", "color": "0e8a16", "description": "User-facing feature or capability"},
{"name": "type:task", "color": "1d76db", "description": "Technical implementation unit"},
{"name": "status:planning", "color": "fbca04", "description": "Still being defined"},
{"name": "status:ready", "color": "0e8a16", "description": "Ready for development"},
{"name": "status:in-progress", "color": "1d76db", "description": "Currently being worked on"},
{"name": "status:blocked", "color": "d93f0b", "description": "Cannot proceed due to dependency"},
{"name": "priority:high", "color": "d93f0b", "description": "High priority work"},
{"name": "priority:medium", "color": "fbca04", "description": "Medium priority work"},
{"name": "priority:low", "color": "0e8a16", "description": "Low priority work"}
]
Then create all labels:
cat labels.json | jq -r '.[] | "gh label create \"\(.name)\" --description \"\(.description)\" --color \"\(.color)\""' | bash
Issue Templates
Create reusable templates for consistent issue creation.
Epic Template
Save as .github/ISSUE_TEMPLATE/epic.md:
---
name: Epic
about: Large initiative spanning multiple stories
title: '[EPIC] '
labels: 'type:epic, status:planning'
assignees: ''
---
## Epic Overview
**Goal:** [Describe the high-level business objective]
**Value Proposition:** [Why are we building this? What problem does it solve?]
**Success Metrics:**
- [Metric 1: e.g., 50% reduction in login time]
- [Metric 2: e.g., 90% user satisfaction]
- [Metric 3: e.g., Zero security incidents]
## User Stories
<!-- Add stories as task list items. Create story issues first, then link here. -->
### In Scope
- [ ] #<story-number> [Story title]
- [ ] #<story-number> [Story title]
- [ ] #<story-number> [Story title]
### Out of Scope (Future Consideration)
- [ ] [Feature or capability deferred to later]
- [ ] [Feature or capability deferred to later]
## Dependencies
- **Blocks:** #<issue> - [What this epic blocks]
- **Blocked by:** #<issue> - [What blocks this epic]
- **Related to:** #<issue> - [Related epics or initiatives]
## Technical Considerations
- [Key architectural decision or constraint]
- [Technology choices or requirements]
- [Performance/security/scalability concerns]
## Timeline
- **Target Start:** YYYY-MM-DD
- **Target Completion:** YYYY-MM-DD
- **Actual Start:** [Fill when started]
- **Actual Completion:** [Fill when completed]
## Acceptance Criteria
- [ ] All stories completed and merged
- [ ] Documentation updated
- [ ] Tests passing (unit, integration, e2e)
- [ ] Security review completed
- [ ] Performance benchmarks met
- [ ] Deployed to production
## Notes
[Additional context, links to designs, stakeholder discussions, etc.]
Story Template
Save as .github/ISSUE_TEMPLATE/story.md:
---
name: Story
about: User-facing feature or capability
title: '[STORY] '
labels: 'type:story, status:planning'
assignees: ''
---
## User Story
**As a** [user type]
**I want** [goal or desire]
**So that** [benefit or value]
## Description
[Detailed description of what needs to be built]
## Epic
**Tracked by Epic:** #<epic-number>
## Tasks
<!-- Create task issues first, then link here with checkboxes -->
### Backend
- [ ] #<task-number> [API endpoint implementation]
- [ ] #<task-number> [Database schema/migration]
- [ ] #<task-number> [Business logic/validation]
### Frontend
- [ ] #<task-number> [UI component implementation]
- [ ] #<task-number> [State management]
- [ ] #<task-number> [Form validation/error handling]
### Testing & Documentation
- [ ] #<task-number> [Unit tests]
- [ ] #<task-number> [Integration tests]
- [ ] #<task-number> [E2E tests]
- [ ] #<task-number> [Documentation updates]
## Acceptance Criteria
- [ ] [Specific, testable criterion 1]
- [ ] [Specific, testable criterion 2]
- [ ] [Specific, testable criterion 3]
- [ ] [Specific, testable criterion 4]
## Design
**Mockups/Wireframes:** [Link to Figma, screenshots, or design files]
**API Contract:**
```json
// Example request/response
Dependencies
- Depends on: # - [Must be completed first]
- Blocks: # - [This blocks other work]
Estimation
Story Points: [1, 2, 3, 5, 8, 13] Estimated Hours: [Development time estimate]
Notes
[Additional context, technical decisions, edge cases, etc.]
### Task Template
Save as `.github/ISSUE_TEMPLATE/task.md`:
```markdown
---
name: Task
about: Technical implementation unit
title: '[TASK] '
labels: 'type:task, status:ready'
assignees: ''
---
## Task Description
[Clear, concise description of the technical work]
## Story
**Tracked by Story:** #<story-number>
## Implementation Details
**Files to modify:**
- `path/to/file1.ts`
- `path/to/file2.ts`
**Approach:**
1. [Step-by-step implementation approach]
2. [Step 2]
3. [Step 3]
**Code example/pseudocode:**
```typescript
// Example of expected implementation
Acceptance Criteria
- Code implements specification
- Unit tests added/updated
- No breaking changes to existing functionality
- Code reviewed and approved
- Documentation updated (if needed)
Testing Checklist
- Unit tests pass locally
- Integration tests pass locally
- Manual testing completed
- Edge cases handled
Pull Request Checklist
- PR created and linked to this issue
- CI/CD pipeline passing
- Code review approved
- Merged to main branch
Estimated Time
Hours: [e.g., 4-6 hours]
Notes
[Technical notes, gotchas, or considerations]
---
## Creating the Hierarchy
Step-by-step workflow for building issue hierarchies.
### Step 1: Create the Epic (Top-Down Approach)
```bash
# Create epic with template
gh issue create \
--title "[EPIC] User Authentication System" \
--label "type:epic,status:planning,priority:high" \
--body "$(cat .github/ISSUE_TEMPLATE/epic.md)"
# Note the issue number (e.g., #100)
Alternative: Use GitHub UI
- Navigate to Issues → New Issue
- Select "Epic" template
- Fill in the template
- Submit
Step 2: Create Story Issues
Create stories that belong to the epic:
# Story 1
gh issue create \
--title "[STORY] Login Flow Implementation" \
--label "type:story,status:planning,epic:auth" \
--body "Tracked by Epic: #100
As a user
I want to log in with email and password
So that I can access my account securely
## Tasks
<!-- Will add task links here -->
"
# Story 2
gh issue create \
--title "[STORY] User Registration" \
--label "type:story,status:planning,epic:auth" \
--body "Tracked by Epic: #100"
# Story 3
gh issue create \
--title "[STORY] Password Reset Flow" \
--label "type:story,status:planning,epic:auth" \
--body "Tracked by Epic: #100"
Step 3: Link Stories to Epic
Update epic #100 to include story task list:
gh issue edit 100 --body "$(cat <<'EOF'
## Epic Overview
Implement comprehensive user authentication system.
## Stories
- [ ] #101 Login Flow Implementation
- [ ] #102 User Registration
- [ ] #103 Password Reset Flow
## Timeline
Target: Q1 2025
EOF
)"
Result: Epic #100 now shows a progress bar tracking 0/3 stories.
Step 4: Create Task Issues
For each story, create granular tasks:
# Tasks for Story #101 (Login Flow)
gh issue create \
--title "[TASK] Implement JWT authentication endpoint" \
--label "type:task,status:ready,story:101" \
--assignee "@me" \
--body "Tracked by Story: #101
Implement POST /api/auth/login endpoint with JWT token generation."
gh issue create \
--title "[TASK] Create login form component" \
--label "type:task,status:ready,story:101" \
--assignee "@me"
gh issue create \
--title "[TASK] Add login flow tests" \
--label "type:task,status:ready,story:101" \
--assignee "@me"
Step 5: Link Tasks to Story
Update story #101 with task list:
gh issue edit 101 --body "$(cat <<'EOF'
**Tracked by Epic:** #100
As a user I want to log in with email and password.
## Tasks
### Backend
- [ ] #104 Implement JWT authentication endpoint
### Frontend
- [ ] #105 Create login form component
### Testing
- [ ] #106 Add login flow tests
EOF
)"
Step 6: Work the Tasks
As work progresses:
Assign and start task:
gh issue edit 104 --add-label "status:in-progress"Create PR and link:
gh pr create \ --title "Implement JWT authentication endpoint" \ --body "Closes #104"Merge PR: Task #104 automatically closes
Check task in story:
# Manually check the task in story #101 # Or let GitHub auto-check when #104 closes
Bottom-Up Approach (Alternative)
Some teams prefer creating tasks first:
# 1. Create all task issues
gh issue create --title "[TASK] JWT endpoint" --label "type:task"
gh issue create --title "[TASK] Login form" --label "type:task"
gh issue create --title "[TASK] Login tests" --label "type:task"
# 2. Create story and link tasks immediately
gh issue create \
--title "[STORY] Login Flow" \
--label "type:story" \
--body "## Tasks
- [ ] #104 JWT endpoint
- [ ] #105 Login form
- [ ] #106 Login tests"
# 3. Create epic and link stories
gh issue create \
--title "[EPIC] Authentication" \
--label "type:epic" \
--body "## Stories
- [ ] #101 Login Flow
- [ ] #102 Registration
- [ ] #103 Password Reset"
How "Tracked by" Works
GitHub automatically creates bidirectional relationships.
Automatic Behavior
When you add - [ ] #123 to an issue body:
- Parent issue shows task list with checkbox
- Child issue #123 shows banner: "Tracked by #parent"
- Progress bar appears on parent showing completion percentage
- Closing child automatically updates parent's progress
Example Progression
Initial State:
Epic #100: User Authentication
├── Progress: ░░░░░░░░░░ 0% (0 of 3 completed)
├── [ ] #101 Login Flow
├── [ ] #102 Registration
└── [ ] #103 Password Reset
After Story #101 Completes:
Epic #100: User Authentication
├── Progress: ███░░░░░░░ 33% (1 of 3 completed)
├── [x] #101 Login Flow ✓
├── [ ] #102 Registration
└── [ ] #103 Password Reset
Issue #101 Shows:
┌─────────────────────────────────────┐
│ Tracked by #100 │
│ User Authentication System │
└─────────────────────────────────────┘
[Story #101 content here]
Multi-Level Tracking
Tasks can show both story and epic:
Task #104: JWT endpoint
┌─────────────────────────────────────┐
│ Tracked by │
│ #101 Login Flow Implementation │
│ #100 User Authentication System │
└─────────────────────────────────────┘
This happens when:
- Task #104 is in Story #101's task list
- Story #101 is in Epic #100's task list
Manual vs Automatic Checking
Automatic (Recommended):
- Close child issue → Checkbox auto-checks
- Reopen child issue → Checkbox auto-unchecks
Manual:
- Edit parent issue body
- Change
- [ ] #123to- [x] #123 - Useful for non-issue tasks or milestones
Naming Conventions
Consistent naming improves scannability and filtering.
Title Prefixes
Use bracket prefixes to identify issue types at a glance:
[EPIC] User Authentication System
[STORY] Login Flow Implementation
[TASK] Implement JWT authentication endpoint
Enforcement via gh CLI:
# Epic
gh issue create --title "[EPIC] ${EPIC_NAME}"
# Story
gh issue create --title "[STORY] ${STORY_NAME}"
# Task
gh issue create --title "[TASK] ${TASK_NAME}"
Title Structure
Epics: [EPIC] Noun Phrase
✓ [EPIC] User Authentication System
✓ [EPIC] Payment Processing Integration
✓ [EPIC] Multi-Language Support
✗ [EPIC] Implement Auth (too vague)
✗ [EPIC] Fix Login (should be story or task)
Stories: [STORY] User-Facing Feature
✓ [STORY] Login Flow Implementation
✓ [STORY] Shopping Cart Checkout
✓ [STORY] Dark Mode Toggle
✗ [STORY] Add JWT (too technical, not user-facing)
✗ [STORY] Fix Bug (should be task or bug label)
Tasks: [TASK] Specific Technical Work
✓ [TASK] Implement JWT authentication endpoint
✓ [TASK] Create LoginForm component
✓ [TASK] Add unit tests for auth service
✗ [TASK] Do login stuff (too vague)
✗ [TASK] User can log in (should be story)
Label Naming
Pattern: category:value
type:epic, type:story, type:task
status:planning, status:ready, status:in-progress
priority:high, priority:medium, priority:low
epic:auth, epic:payments, epic:reporting
Benefits:
- Enables grouping:
label:type:* - Clear hierarchy: easier to understand relationships
- Better filtering:
label:"type:story" label:"epic:auth"
Branch Naming
Link branches to issues:
epic/100-user-authentication
story/101-login-flow
task/104-jwt-endpoint
Automation:
ISSUE_NUM=104
ISSUE_TITLE=$(gh issue view $ISSUE_NUM --json title -q .title)
BRANCH_NAME="task/${ISSUE_NUM}-$(echo $ISSUE_TITLE | sed 's/\[TASK\] //' | tr '[:upper:]' '[:lower:]' | tr ' ' '-')"
git checkout -b "$BRANCH_NAME"
Real-World Example
Complete workflow for implementing a User Authentication epic.
The Feature
Business Requirement: "Users need to securely create accounts, log in, and reset forgotten passwords."
Step-by-Step Implementation
Week 1: Planning
1. Create Epic (#100)
gh issue create \
--title "[EPIC] User Authentication System" \
--label "type:epic,status:planning,priority:high" \
--body "$(cat <<'EOF'
## Epic Overview
**Goal:** Implement secure user authentication with email/password and OAuth support.
**Value Proposition:** Enable user accounts to support personalized experiences and data security.
**Success Metrics:**
- Login time < 2 seconds
- 99.9% uptime for auth services
- Zero security incidents in first 90 days
- 90%+ user satisfaction with auth flow
## Stories
- [ ] #101 Login Flow Implementation
- [ ] #102 User Registration Flow
- [ ] #103 Password Reset Flow
- [ ] #104 OAuth Integration (Google, GitHub)
- [ ] #105 Session Management
## Timeline
- **Target Start:** 2025-01-15
- **Target Completion:** 2025-02-28
## Acceptance Criteria
- [ ] All stories completed and merged
- [ ] Security audit passed
- [ ] Load tested (1000 concurrent users)
- [ ] Documentation complete
- [ ] Deployed to production
EOF
)"
2. Create Stories
# Story 1: Login Flow
gh issue create \
--title "[STORY] Login Flow Implementation" \
--label "type:story,status:planning,epic:auth,priority:high" \
--body "Tracked by Epic: #100
As a registered user
I want to log in with my email and password
So that I can access my personalized account
## Tasks
<!-- Will add tasks below -->
## Acceptance Criteria
- [ ] User can log in with valid credentials
- [ ] Invalid credentials show clear error message
- [ ] Account locks after 5 failed attempts
- [ ] JWT token expires after 24 hours
- [ ] Remember me option extends session to 30 days"
# Story 2: Registration
gh issue create \
--title "[STORY] User Registration Flow" \
--label "type:story,status:planning,epic:auth,priority:high" \
--body "Tracked by Epic: #100
As a new user
I want to create an account
So that I can use the platform
## Acceptance Criteria
- [ ] Email validation enforced
- [ ] Password strength requirements displayed
- [ ] Email verification sent on registration
- [ ] Duplicate emails rejected gracefully"
# Story 3: Password Reset
gh issue create \
--title "[STORY] Password Reset Flow" \
--label "type:story,status:ready,epic:auth,priority:medium" \
--body "Tracked by Epic: #100
As a user who forgot my password
I want to reset it via email
So that I can regain access to my account"
3. Update Epic with Stories
gh issue edit 100 --body "$(cat <<'EOF'
## Epic Overview
**Goal:** Implement secure user authentication system.
## Stories
- [ ] #101 Login Flow Implementation
- [ ] #102 User Registration Flow
- [ ] #103 Password Reset Flow
- [ ] #104 OAuth Integration
- [ ] #105 Session Management
## Timeline
Target: Q1 2025
EOF
)"
Week 2: Sprint 1 - Login Flow
4. Break Story #101 into Tasks
# Backend tasks
gh issue create \
--title "[TASK] Create User model and database schema" \
--label "type:task,status:ready,story:101,backend" \
--assignee "developer1" \
--body "Tracked by Story: #101
Create User model with fields:
- email (unique)
- password_hash
- created_at
- updated_at
- last_login
Use bcrypt for password hashing."
gh issue create \
--title "[TASK] Implement POST /api/auth/login endpoint" \
--label "type:task,status:ready,story:101,backend" \
--assignee "developer1"
gh issue create \
--title "[TASK] Implement JWT token generation" \
--label "type:task,status:ready,story:101,backend" \
--assignee "developer1"
# Frontend tasks
gh issue create \
--title "[TASK] Create LoginForm component" \
--label "type:task,status:ready,story:101,frontend" \
--assignee "developer2"
gh issue create \
--title "[TASK] Implement auth state management" \
--label "type:task,status:ready,story:101,frontend" \
--assignee "developer2"
gh issue create \
--title "[TASK] Create protected route wrapper" \
--label "type:task,status:ready,story:101,frontend" \
--assignee "developer2"
# Testing tasks
gh issue create \
--title "[TASK] Add unit tests for auth service" \
--label "type:task,status:ready,story:101,testing" \
--assignee "developer3"
gh issue create \
--title "[TASK] Add E2E tests for login flow" \
--label "type:task,status:ready,story:101,testing" \
--assignee "developer3"
5. Update Story #101 with Tasks
gh issue edit 101 --body "$(cat <<'EOF'
**Tracked by Epic:** #100
As a registered user I want to log in with email and password.
## Tasks
### Backend
- [ ] #106 Create User model and database schema
- [ ] #107 Implement POST /api/auth/login endpoint
- [ ] #108 Implement JWT token generation
### Frontend
- [ ] #109 Create LoginForm component
- [ ] #110 Implement auth state management
- [ ] #111 Create protected route wrapper
### Testing
- [ ] #112 Add unit tests for auth service
- [ ] #113 Add E2E tests for login flow
## Acceptance Criteria
- [ ] User can log in with valid credentials
- [ ] Invalid credentials show error
- [ ] Account locks after 5 failed attempts
- [ ] JWT expires after 24 hours
EOF
)"
6. Development Workflow
# Developer 1 starts backend work
gh issue edit 106 --add-label "status:in-progress"
# Create feature branch
git checkout -b task/106-user-model
# ... implement code ...
# Create PR
gh pr create \
--title "Create User model and database schema" \
--body "Closes #106
Implements User model with:
- Email validation
- Password hashing with bcrypt
- Timestamps
- Migration script
**Testing:**
- Unit tests added
- Migration tested on dev DB" \
--assignee "@me"
# After PR approved and merged, #106 auto-closes
# Story #101 progress updates: 1/8 tasks complete
7. Sprint Progress Tracking
# View epic progress
gh issue view 100
# Output shows:
# [EPIC] User Authentication System
# ████░░░░░░ 20% (1 of 5 stories)
#
# - [ ] #101 Login Flow Implementation (1/8 tasks)
# - [ ] #102 User Registration Flow
# - [ ] #103 Password Reset Flow
# - [ ] #104 OAuth Integration
# - [ ] #105 Session Management
Week 3-4: Complete Login Story
As tasks complete, the hierarchy auto-updates:
Epic #100: User Authentication
├── Progress: ████░░░░░░ 40% (2 of 5 stories)
├── [x] #101 Login Flow ✓ (8/8 tasks)
├── [x] #102 Registration ✓ (6/6 tasks)
├── [ ] #103 Password Reset (2/5 tasks)
├── [ ] #104 OAuth Integration
└── [ ] #105 Session Management
Week 5: Epic Completion
# Close final story
gh issue close 105 --comment "All session management tasks complete. Tests passing."
# Epic #100 automatically shows 100% complete
# Now ready to close epic
gh issue close 100 --comment "Epic complete! All authentication features shipped to production.
**Metrics:**
- Login time: 1.2s avg
- Zero security incidents
- 94% user satisfaction
- Deployed: 2025-02-15"
Querying and Filtering
Use GitHub's search and gh CLI to navigate hierarchies.
GitHub Web Search
All epics:
is:issue label:type:epic
Open epics:
is:open is:issue label:type:epic
Stories for specific epic:
is:issue label:type:story label:epic:auth
Tasks assigned to you:
is:open is:issue label:type:task assignee:@me
High priority stories in progress:
is:open label:type:story label:priority:high label:status:in-progress
All issues related to authentication:
is:issue label:epic:auth
gh CLI Queries
List all epics:
gh issue list --label "type:epic" --state all
Show epic with progress:
gh issue view 100 --json title,body,labels,state
List stories for epic:
gh issue list --label "epic:auth" --label "type:story"
Show tasks for a story:
# Extract task numbers from story body
gh issue view 101 --json body -q .body | grep -o '#[0-9]\+' | while read task; do
gh issue view ${task#\#} --json number,title,state -q '"#\(.number): \(.title) [\(.state)]"'
done
Find blocked issues:
gh issue list --label "status:blocked" --json number,title,labels
Sprint planning - ready stories:
gh issue list \
--label "type:story" \
--label "status:ready" \
--json number,title,labels \
--jq '.[] | "#\(.number): \(.title)"'
Advanced Filtering with jq
Stories grouped by epic:
gh issue list --label "type:story" --state all --json number,title,labels --limit 100 | \
jq -r 'group_by(.labels[] | select(.name | startswith("epic:")) | .name) |
.[] | "Epic: \(.[0].labels[] | select(.name | startswith("epic:")) | .name)\n" +
(map(" - #\(.number): \(.title)") | join("\n"))'
Completion percentage per epic:
# Custom script: epic-progress.sh
#!/bin/bash
EPIC_NUM=$1
BODY=$(gh issue view $EPIC_NUM --json body -q .body)
TOTAL=$(echo "$BODY" | grep -c '- \[.\] #')
DONE=$(echo "$BODY" | grep -c '- \[x\] #')
PERCENT=$((DONE * 100 / TOTAL))
echo "Epic #$EPIC_NUM: $DONE/$TOTAL complete ($PERCENT%)"
Usage:
chmod +x epic-progress.sh
./epic-progress.sh 100
# Output: Epic #100: 3/5 complete (60%)
GitHub Projects Integration
Create project board:
gh project create \
--owner "your-org" \
--title "Q1 2025 - Authentication Epic" \
--body "Tracking Epic #100"
Add issues to project:
# Add epic and all related stories
gh project item-add PROJECT_ID --owner your-org --issue 100
gh project item-add PROJECT_ID --owner your-org --issue 101
gh project item-add PROJECT_ID --owner your-org --issue 102
View project:
gh project view PROJECT_ID --owner your-org
Best Practices
Guidelines for effective hierarchical issue management.
1. Keep Hierarchies Shallow
Do:
Epic → Story → Task (3 levels max)
Don't:
Initiative → Epic → Story → Subtask → Microtask (too deep)
Reasoning: GitHub's task lists work best with 2-3 levels. Deeper nesting becomes hard to navigate and maintain.
2. One Epic per Feature
Do:
Epic #100: User Authentication
Epic #150: Payment Processing
Epic #200: Reporting Dashboard
Don't:
Epic #100: Q1 2025 Features
├── User Auth
├── Payments
└── Reporting
Reasoning: Epics should represent cohesive features, not time periods. Use milestones for time-based grouping.
3. Limit Stories per Epic
Recommended: 3-10 stories per epic
Too few (< 3):
- Epic probably isn't needed
- Just use stories
Too many (> 10):
- Epic too large
- Break into multiple epics
4. Tasks Should Take < 1 Day
Good task size:
- 2-8 hours of work
- Single PR
- Clear start and end
Signs a task is too large:
- Takes multiple days
- Requires multiple PRs
- Should probably be a story
5. Use Consistent Labels
Label strategy:
# Always include type
type:epic, type:story, type:task
# Always include status
status:planning, status:ready, status:in-progress, status:review
# Optional: epic relationship
epic:auth, epic:payments
# Optional: component
component:backend, component:frontend, component:testing
6. Close Issues Bottom-Up
Correct order:
- Complete task → Close task
- All tasks done → Close story
- All stories done → Close epic
Anti-pattern:
- Closing epic while stories are still open
- Closing story while tasks are blocked
7. Link PRs to Tasks, Not Stories
Do:
gh pr create --body "Closes #104" # Task number
Don't:
gh pr create --body "Closes #101" # Story number
Reasoning: One PR per task maintains clear traceability. Stories track multiple tasks.
8. Keep Issue Bodies Updated
Update when:
- Adding/removing tasks
- Changing acceptance criteria
- Blocking/unblocking work
- Status changes
Automation:
# Add task to story
STORY=101
NEW_TASK=114
gh issue view $STORY --json body -q .body > /tmp/story-body.md
echo "- [ ] #$NEW_TASK New task description" >> /tmp/story-body.md
gh issue edit $STORY --body-file /tmp/story-body.md
9. Use Milestones for Time Tracking
Combine with epics:
# Create milestone
gh api repos/:owner/:repo/milestones -f title="Q1 2025" -f due_on="2025-03-31T00:00:00Z"
# Add epic to milestone
gh issue edit 100 --milestone "Q1 2025"
View epic + milestone:
gh issue list --milestone "Q1 2025" --label "type:epic"
10. Document Dependencies
In issue body:
## Dependencies
- **Depends on:** #95 - Database migration must complete first
- **Blocks:** #120 - Frontend work waiting on this API
- **Related:** #80 - Similar authentication work for admin panel
Track with labels:
gh label create "blocked-by:external" --color "d93f0b"
gh issue edit 104 --add-label "blocked-by:external"
11. Regular Grooming
Weekly epic review:
# Show all open epics and their progress
gh issue list --label "type:epic" --state open | while read num rest; do
./epic-progress.sh ${num#\#}
done
Clean up stale issues:
# Find issues with no updates in 30 days
gh issue list --state open --label "type:task" \
--json number,title,updatedAt \
--jq '.[] | select(.updatedAt < (now - 30*24*60*60 | todate)) | "#\(.number): \(.title)"'
12. Templates are Your Friend
Use issue templates consistently:
- Standardizes structure
- Reduces errors
- Speeds up issue creation
- Improves searchability
Enforce with CI:
# .github/workflows/issue-validation.yml
name: Validate Issue Format
on:
issues:
types: [opened]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Check for required labels
if: |
!contains(github.event.issue.labels.*.name, 'type:epic') &&
!contains(github.event.issue.labels.*.name, 'type:story') &&
!contains(github.event.issue.labels.*.name, 'type:task')
run: |
echo "::error::Issue must have a type label (type:epic, type:story, or type:task)"
exit 1
Limitations vs Jira
Honest comparison of what you lose (and gain) vs Jira.
What GitHub Issues Lacks
| Feature | Jira | GitHub | Workaround |
|---|---|---|---|
| Native hierarchy | Built-in Epic/Story/Task/Subtask | Task lists only | Use task lists + labels |
| Custom fields | Unlimited custom fields | Limited to labels/assignees | Use labels creatively |
| Story points | Native field | None | Use estimate:X labels |
| Sprint planning | Built-in sprints | None | Use milestones or GitHub Projects |
| Advanced reporting | Burndown charts, velocity | None | Use GitHub Projects or external tools |
| Issue linking types | Blocks, depends on, duplicates | Generic mentions | Document in issue body |
| Workflows | Customizable workflows | Labels only | Use labels + automation |
| Time tracking | Built-in time logging | None | Use labels or external tools |
| Roadmaps | Native roadmap view | None | Use GitHub Projects (beta) |
| Bulk editing | Full bulk edit UI | Limited | Use gh CLI with scripts |
What GitHub Issues Does Better
| Feature | GitHub | Jira |
|---|---|---|
| Markdown support | Full markdown + code blocks | Limited |
| Integration with code | Native PR linking, code refs | Plugin required |
| Speed | Fast, lightweight | Often slow |
| Search | Powerful search syntax | Can be sluggish |
| Developer experience | CLI (gh), API, automation |
Clunky UI |
| Cost | Free for public/private repos | Expensive at scale |
| Simplicity | Clean, minimal UI | Overwhelming options |
| GitHub Actions | Seamless automation | Requires webhooks |
When to Use GitHub Issues
Use GitHub Issues if:
- Small to medium teams (< 50 people)
- Developer-focused workflows
- Want tight code integration
- Value simplicity over complexity
- Open source or GitHub-centric
Consider Jira if:
- Large enterprise (100+ people)
- Need extensive reporting
- Non-technical stakeholders need access
- Require complex workflows
- Need advanced resource planning
Hybrid Approach
Many teams use both:
Jira: Product roadmap, high-level epics
↓
GitHub: Technical stories, tasks, PRs
Sync with automation:
# .github/workflows/sync-to-jira.yml
name: Sync to Jira
on:
issues:
types: [opened, closed]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Create Jira issue
if: contains(github.event.issue.labels.*.name, 'type:epic')
uses: atlassian/gajira-create@v2
with:
project: PROJ
issuetype: Epic
summary: ${{ github.event.issue.title }}
Advanced Workflows
Power user techniques for complex projects.
Automated Issue Creation
Create entire hierarchy from YAML:
epic-template.yaml:
epic:
title: "User Authentication System"
labels: ["type:epic", "priority:high"]
stories:
- title: "Login Flow Implementation"
labels: ["type:story", "epic:auth"]
tasks:
- title: "Create User model"
assignee: "developer1"
- title: "JWT endpoint"
assignee: "developer1"
- title: "Login form component"
assignee: "developer2"
- title: "User Registration"
labels: ["type:story", "epic:auth"]
tasks:
- title: "Registration endpoint"
- title: "Email verification"
Script: create-hierarchy.sh
#!/bin/bash
set -e
YAML_FILE=$1
# Parse YAML and create issues
EPIC_TITLE=$(yq .epic.title $YAML_FILE)
EPIC_LABELS=$(yq '.epic.labels | join(",")' $YAML_FILE)
# Create epic
EPIC_NUM=$(gh issue create --title "$EPIC_TITLE" --label "$EPIC_LABELS" --body "Epic created from template" | grep -o '[0-9]\+$')
echo "Created Epic #$EPIC_NUM"
# Create stories
STORY_COUNT=$(yq '.epic.stories | length' $YAML_FILE)
for i in $(seq 0 $((STORY_COUNT - 1))); do
STORY_TITLE=$(yq ".epic.stories[$i].title" $YAML_FILE)
STORY_LABELS=$(yq ".epic.stories[$i].labels | join(\",\")" $YAML_FILE)
STORY_NUM=$(gh issue create \
--title "[STORY] $STORY_TITLE" \
--label "$STORY_LABELS" \
--body "Tracked by Epic: #$EPIC_NUM" | grep -o '[0-9]\+$')
echo " Created Story #$STORY_NUM"
# Create tasks
TASK_COUNT=$(yq ".epic.stories[$i].tasks | length" $YAML_FILE)
TASK_LIST=""
for j in $(seq 0 $((TASK_COUNT - 1))); do
TASK_TITLE=$(yq ".epic.stories[$i].tasks[$j].title" $YAML_FILE)
TASK_ASSIGNEE=$(yq ".epic.stories[$i].tasks[$j].assignee // \"\"" $YAML_FILE)
ASSIGN_FLAG=""
if [ -n "$TASK_ASSIGNEE" ]; then
ASSIGN_FLAG="--assignee $TASK_ASSIGNEE"
fi
TASK_NUM=$(gh issue create \
--title "[TASK] $TASK_TITLE" \
--label "type:task,story:$STORY_NUM" \
$ASSIGN_FLAG \
--body "Tracked by Story: #$STORY_NUM" | grep -o '[0-9]\+$')
echo " Created Task #$TASK_NUM"
TASK_LIST="$TASK_LIST\n- [ ] #$TASK_NUM $TASK_TITLE"
done
# Update story with tasks
gh issue edit $STORY_NUM --body "Tracked by Epic: #$EPIC_NUM\n\n## Tasks\n$TASK_LIST"
done
echo "✓ Hierarchy created successfully!"
Usage:
chmod +x create-hierarchy.sh
./create-hierarchy.sh epic-template.yaml
Progress Reporting
Daily standup report:
standup-report.sh:
#!/bin/bash
EPIC=$1
TODAY=$(date +%Y-%m-%d)
echo "# Standup Report - $TODAY"
echo "## Epic #$EPIC Progress"
echo ""
# Get epic body
BODY=$(gh issue view $EPIC --json body -q .body)
# Count progress
TOTAL=$(echo "$BODY" | grep -c '- \[.\] #' || true)
DONE=$(echo "$BODY" | grep -c '- \[x\] #' || true)
PERCENT=$((DONE * 100 / TOTAL))
echo "**Overall:** $DONE/$TOTAL stories complete ($PERCENT%)"
echo ""
# Show each story status
echo "## Story Status"
echo "$BODY" | grep -o '- \[.\] #[0-9]\+' | while read checkbox num; do
NUM=${num#\#}
STORY=$(gh issue view $NUM --json title,state,labels -q '{title,state,labels:[.labels[].name]}')
TITLE=$(echo "$STORY" | jq -r .title)
STATE=$(echo "$STORY" | jq -r .state)
STATUS=$(echo "$STORY" | jq -r '.labels[] | select(startswith("status:"))')
if [[ "$checkbox" == *"[x]"* ]]; then
echo "- ✅ #$NUM: $TITLE (CLOSED)"
else
echo "- 🔄 #$NUM: $TITLE ($STATUS)"
fi
done
echo ""
echo "## Today's Work"
# Show in-progress tasks
gh issue list --label "statu
…(truncated)