| name | blocked-sync | ||||||
|---|---|---|---|---|---|---|---|
| description | GitHub Actions workflow that automatically unblocks dependent issues when their blocking issues are closed. Handles multi-blocker scenarios and detects manual/external blocks. | ||||||
| summary | 1. Workflow triggers when any issue is closed 2. Script searches for issues with "Blocked by #N" that have "blocked" label 3. If sole blocker: Remove label, post "Auto-unblocked" comment 4. If multiple blockers: Post update, keep label with remaining blockers 5. Manual blocks (external, approval) are skipped | ||||||
| triggers |
|
This playbook describes how to configure and use the blocked status sync workflow, which automatically manages the "blocked" label lifecycle as blocking issues are resolved.
flowchart TD
subgraph Trigger["Workflow Trigger"]
CLOSE["Issue #100 Closed"]
end
subgraph Detection["Find Dependents"]
SEARCH["Search: 'Blocked by #100'"]
VERIFY["Verify 'blocked' label exists"]
PARSE["Parse blocker list"]
end
subgraph Decision["Determine Action"]
SOLE{{"Sole blocker?"}}
MANUAL{{"Manual block?"}}
end
subgraph Actions["Take Action"]
UNBLOCK["Remove 'blocked' label\nPost: 'Auto-unblocked'"]
UPDATE["Post: 'Still blocked by #X, #Y'"]
SKIP["Skip - log warning"]
end
CLOSE --> SEARCH
SEARCH --> VERIFY
VERIFY --> PARSE
PARSE --> MANUAL
MANUAL -->|Yes| SKIP
MANUAL -->|No| SOLE
SOLE -->|Yes| UNBLOCK
SOLE -->|No| UPDATE
The workflow recognizes these "Blocked by" patterns in issue body or comments:
| Pattern | Example | Notes |
|---|---|---|
Blocked by: #N |
Blocked by: #100 | Standard format with colon |
Blocked by #N |
Blocked by #100 | Without colon |
Blocked by: #N, #M, #O |
Blocked by: #100, #101 | Multiple blockers |
blocked by #N (lowercase) |
blocked by #100 | Case-insensitive |
Blocked by #N (with notes) |
Blocked by #100 (reason) | Notes after are ignored |
Required for detection:
- Issue must have the
blockedlabel - Issue body or comment must contain "Blocked by" pattern with issue number
Issues with these keywords in the blocking statement are NOT auto-unblocked:
waiting forexternalapprovalclientvendorthird party
Example (skipped):
Blocked by: #100 (waiting for external approval)
Ensure the unblock script exists at scripts/issue-driven-delivery/unblock-dependents.sh.
The script is included in this repository. For other repositories, copy it from:
skills/issue-driven-delivery/scripts/unblock-dependents.sh (if packaged separately).
Copy the template from skills/issue-driven-delivery/templates/sync-blocked-status.yml
to .github/workflows/auto-unblock.yml (or sync-blocked-status.yml).
The workflow requires issues: write permission. This is set in the template but verify
your repository allows this permission for Actions.
The unblock script can also be run manually for testing or maintenance:
# Dry-run: See what would happen
./scripts/issue-driven-delivery/unblock-dependents.sh 100
# Apply changes
./scripts/issue-driven-delivery/unblock-dependents.sh 100 --apply
# Show dependency graph
./scripts/issue-driven-delivery/unblock-dependents.sh 100 --graph
# Auto-detect recently closed issues
./scripts/issue-driven-delivery/unblock-dependents.sh --graph| Option | Description |
|---|---|
| (number) | Issue number to process |
--apply |
Actually make changes (dry-run by default) |
--graph |
Show dependency graph (ASCII or Mermaid) |
--help |
Show help message |
Terminal (ASCII):
Dependency Graph (issues blocked by #100):
=============================================
#100 (closed/processing)
├── #101 (will unblock) - Implement feature X
├── #102 (still blocked by #103, #104) - Deploy...
└── #105 (will unblock) - Update documentation
Piped (Mermaid):
graph TD
100["#100 (closed)"]
100 --> 101["#101"]
100 --> 102["#102"]
100 --> 105["#105"]
The script detects circular dependencies and suggests resolution:
[WARN] Circular dependency detected: #101 #102 #103 #101
Resolution suggestion based on rework-cost heuristics:
#101: LOW rework cost
#102: MEDIUM rework cost
#103: HIGH rework cost
Recommendation: Unblock the issue with LOWEST rework cost first.
Create a follow-up task for rework after the cycle completes.
| Cost | Keywords in blocking statement |
|---|---|
| LOW | interface, mock, stub, config |
| HIGH | schema, migration, architecture, breaking |
| MEDIUM | (default) |
Check 1: Does the dependent issue have the blocked label?
gh issue view 101 --json labels --jq '.labels[].name'Check 2: Does the body/comment contain the exact pattern?
gh issue view 101 --json body,comments \
--jq '([.body] + [.comments[].body]) | .[] | select(test("(?i)blocked by"))'Check 3: Is it a manual block?
Look for keywords: "waiting for", "external", "approval", "client", "vendor".
Check: Is the workflow file in .github/workflows/?
Check: Is the trigger correct?
on:
issues:
types: [closed]Check: GitHub token has issues: write permission.
Check: Script is executable:
chmod +x ./scripts/issue-driven-delivery/unblock-dependents.shThe script verifies each search result:
- Must have
blockedlabel - Must have
Blocked by: #Npattern (not just mentioned)
If false positives occur, check for text that coincidentally matches the pattern.
Run the script without --apply:
./scripts/issue-driven-delivery/unblock-dependents.sh 100
# Shows [DRY-RUN] prefix for all actionsThis workflow complements:
- validate-labels.yml - Ensures
blockedlabel is applied correctly - sync-project-status.yml - Updates project board when blocked status changes
- detect-duplicates.yml - May find blocked issues as potential duplicates
skills/issue-driven-delivery/templates/sync-blocked-status.yml- Template workflow.github/workflows/auto-unblock.yml- Installed workflowscripts/issue-driven-delivery/unblock-dependents.sh- Core scriptdocs/playbooks/ticket-lifecycle.md- Issue state transitions