Introduction
Complex projects rarely consist of independent tasks. More often, work flows in sequences where one task cannot begin until another completes. The CSS cannot be written until the HTML structure exists. The JavaScript interactions cannot be implemented until the DOM elements are in place. The backend API cannot be tested until the frontend form is ready to send data.
Without explicit dependency management, you track these relationships mentally. You remember that Task B needs Task A, that Task C needs both A and B, and that Task D is independent. As projects grow, this mental bookkeeping becomes unsustainable. You forget dependencies, start work prematurely, or waste time checking if prerequisites are met.
Claude Code's Task system solves this with built-in dependency tracking. Tasks can declare what they're blocked by and what they block. When a blocking task completes, dependents automatically become unblocked. You define the relationships once; the system enforces them throughout the project.
This article shows you how to use addBlockedBy and addBlocks to build sophisticated task workflows. You'll learn the patterns that work for sequential pipelines, parallel work that merges, and approval gates. By the end, you'll manage complex multi-phase projects with confidence.
Understanding Blockers
Every task in Claude Code can have two types of relationships with other tasks: things that block it, and things it blocks. Understanding this distinction is essential for building correct dependency graphs.
blockedBy: Tasks That Must Complete First
When a task is "blocked by" another task, it cannot proceed until that blocking task completes. The blocked task exists, it's defined, but it's not ready for work. Think of it as being in a waiting room.
A task that's blocked will show a blocked status in TaskList. Claude knows not to work on blocked tasks. The dependency is explicit and enforced, not just documented.
Task: "Add CSS styling"
blockedBy: ["Create HTML structure"]
status: blocked
Meaning: Cannot start CSS until HTML structure is complete.
The task exists but is not ready for work.
blocks: Tasks Waiting on This One
The inverse relationship is "blocks." When a task blocks another, completing this task will unblock the dependent. This is the same relationship viewed from the other direction.
You can define dependencies from either side. Saying "Task A blocks Task B" is equivalent to saying "Task B is blocked by Task A." Use whichever direction feels more natural when creating or updating tasks.
Task: "Create HTML structure"
blocks: ["Add CSS styling", "Add JavaScript interactions"]
status: in-progress
Meaning: When this task completes, CSS and JavaScript tasks
will automatically become unblocked and ready for work.
Checking If a Task Is Blocked
TaskList shows the blocked status of every task. Tasks that are blocked display their blocking dependencies, so you know exactly what's holding them up. This visibility is crucial for project planning and identifying bottlenecks.
TaskList
ID Title Status Blocked By
------------------------------------------------------------
t1 Create HTML structure in-progress -
t2 Add CSS styling blocked t1
t3 Add JavaScript blocked t2
t4 Write unit tests pending -
Ready for work: t1, t4
Blocked: t2 (waiting on t1), t3 (waiting on t2)
Setting Up Dependencies
You define dependencies when creating tasks with TaskCreate or when updating existing tasks with TaskUpdate. Both support addBlockedBy and addBlocks parameters that specify task IDs.
Using addBlockedBy in TaskUpdate
When you have a task that depends on another, use addBlockedBy to declare what must complete first. This is the most common approach when you're creating dependent tasks after their prerequisites exist.
TaskUpdate
id: "css-styling"
addBlockedBy: ["html-structure"]
Result: The css-styling task is now blocked by html-structure.
It will automatically unblock when html-structure completes.
Using addBlocks in TaskUpdate
When you're working on a task and know what depends on it, use addBlocks to declare downstream dependencies. This is useful when you discover dependencies during implementation.
TaskUpdate
id: "html-structure"
addBlocks: ["css-styling", "js-interactions"]
Result: The html-structure task now blocks both css-styling
and js-interactions. When it completes, both become unblocked.
Example: 3-Phase Feature Implementation
Let's walk through setting up a realistic three-phase feature. We'll create a contact form that requires HTML structure, then CSS styling, then JavaScript validation. Each phase depends on the previous one.
# Phase 1: Create the HTML structure task
TaskCreate
id: "contact-form-html"
title: "Create contact form HTML structure"
description: "Semantic form with name, email, message fields"
# Phase 2: Create CSS task, blocked by HTML
TaskCreate
id: "contact-form-css"
title: "Style the contact form"
description: "Mobile-first responsive styling"
TaskUpdate
id: "contact-form-css"
addBlockedBy: ["contact-form-html"]
# Phase 3: Create JS task, blocked by CSS
TaskCreate
id: "contact-form-js"
title: "Add form validation and submission"
description: "Client-side validation, AJAX submission"
TaskUpdate
id: "contact-form-js"
addBlockedBy: ["contact-form-css"]
# Resulting dependency chain:
# contact-form-html -> contact-form-css -> contact-form-js
With this setup, Claude knows the exact order of operations. It will work on HTML first. When HTML completes, CSS automatically unblocks. When CSS completes, JavaScript automatically unblocks. No manual tracking required.
Automatic Unblocking
The real power of task dependencies is automatic unblocking. When a blocking task completes, all tasks it blocks automatically become unblocked. You don't manually update blocked tasks; the system handles it.
When a Task Completes, Dependents Auto-Unblock
Consider our contact form example. When you mark "contact-form-html" as complete, the system automatically checks what it blocks. It finds "contact-form-css" and changes its status from blocked to pending. The CSS task is now ready for work.
# Before: HTML task is in-progress, CSS is blocked
TaskList
contact-form-html in-progress -
contact-form-css blocked contact-form-html
contact-form-js blocked contact-form-css
# Complete the HTML task
TaskUpdate
id: "contact-form-html"
status: "completed"
# After: CSS is automatically unblocked
TaskList
contact-form-html completed -
contact-form-css pending - <- Now ready!
contact-form-js blocked contact-form-css
How Claude Knows What's Ready to Work On
When Claude reads the task list, it can immediately identify which tasks are available for work. Tasks that are pending (no blockers) or in-progress are candidates. Tasks that are blocked are explicitly off-limits until their dependencies complete.
This visibility helps Claude make intelligent decisions about what to work on next. If asked to "continue working," Claude can pick up the next unblocked task without you specifying which one. The dependency graph guides the workflow.
TaskList Shows Blocked Status
TaskList is your window into the dependency state. Every blocked task shows what's blocking it. This transparency helps you understand project bottlenecks and predict when tasks will become available.
TaskList
Active Tasks:
-------------
[t1] Create HTML structure
Status: in-progress
Blocks: t2, t3
[t2] Add CSS styling
Status: blocked
Blocked by: t1 (Create HTML structure)
[t3] Add JavaScript interactions
Status: blocked
Blocked by: t1 (Create HTML structure)
[t4] Create API endpoint
Status: pending
(No blockers - ready for work)
Summary: 1 in-progress, 2 blocked, 1 pending
Ready for work: t1, t4
Common Patterns
Certain dependency patterns appear repeatedly in software projects. Understanding these patterns helps you structure work effectively.
Sequential Pipeline (A -> B -> C)
The simplest pattern is a linear chain where each task depends on the previous one. This is ideal for phases that must happen in strict order, like build processes or feature implementations that layer on each other.
Pipeline: Design -> Implement -> Test -> Deploy
TaskCreate id: "design"
TaskCreate id: "implement"
TaskCreate id: "test"
TaskCreate id: "deploy"
TaskUpdate id: "implement" addBlockedBy: ["design"]
TaskUpdate id: "test" addBlockedBy: ["implement"]
TaskUpdate id: "deploy" addBlockedBy: ["test"]
Flow:
design (ready) -> implement (blocked) -> test (blocked) -> deploy (blocked)
As each completes, the next unblocks automatically.
Parallel Then Merge (A + B -> C)
Sometimes tasks can proceed in parallel, but a downstream task needs results from both. This pattern is common when multiple components must integrate, or when separate teams work on pieces that combine later.
Pattern: Frontend and Backend in parallel, Integration after both
TaskCreate id: "frontend-form"
TaskCreate id: "backend-api"
TaskCreate id: "integration"
# Integration blocked by BOTH frontend and backend
TaskUpdate id: "integration" addBlockedBy: ["frontend-form", "backend-api"]
Flow:
frontend-form (ready) \
-> integration (blocked until BOTH complete)
backend-api (ready) /
Both frontend and backend can work simultaneously.
Integration only unblocks when both are done.
Approval Gates (Requires Human Sign-off)
Some workflows require human approval before proceeding. You can model this with a task that represents the approval step. Downstream tasks are blocked by the approval task, which only completes when a human marks it done.
Pattern: Development -> Review Approval -> Production Deploy
TaskCreate id: "develop-feature"
TaskCreate id: "code-review-approval"
description: "Human must approve after reviewing changes"
TaskCreate id: "deploy-production"
TaskUpdate id: "code-review-approval" addBlockedBy: ["develop-feature"]
TaskUpdate id: "deploy-production" addBlockedBy: ["code-review-approval"]
Flow:
develop-feature -> code-review-approval -> deploy-production
(human gate)
Development completes, review becomes available.
Deploy stays blocked until human completes review task.
This pattern prevents automated systems from proceeding past checkpoints that require human judgment. The dependency system enforces the gate; humans control when it opens.
Practical Example: Building a Feature
Let's put everything together with a complete example. We'll build a user profile feature with three phases: HTML structure, CSS styling, and JavaScript interactions. Each phase must complete before the next can begin.
Phase 1: Create HTML Structure
Start by creating the foundational task. This has no blockers since it's the first in the chain.
TaskCreate
id: "profile-html"
title: "Create user profile HTML structure"
description: |
Build semantic HTML for user profile page:
- Profile header with avatar and name
- Bio section with edit capability
- Settings form with preferences
- Activity feed placeholder
Use proper heading hierarchy and ARIA labels.
status: "in-progress"
Phase 2: Add CSS Styling (Blocked by Phase 1)
Create the styling task and immediately block it by Phase 1. This task exists but cannot start until HTML is complete.
TaskCreate
id: "profile-css"
title: "Style the user profile page"
description: |
Apply styles to existing profile HTML:
- Mobile-first responsive layout
- Avatar with loading state
- Form styling matching design system
- Activity feed card styles
Use CSS custom properties for theming.
status: "pending"
TaskUpdate
id: "profile-css"
addBlockedBy: ["profile-html"]
# profile-css is now blocked, waiting for profile-html
Phase 3: Add JavaScript (Blocked by Phase 2)
Create the JavaScript task and block it by Phase 2. This creates the full chain: HTML -> CSS -> JavaScript.
TaskCreate
id: "profile-js"
title: "Add JavaScript interactions to profile"
description: |
Implement profile page interactivity:
- Avatar upload with preview
- Inline bio editing with save
- Form validation and submission
- Activity feed lazy loading
Handle loading and error states.
status: "pending"
TaskUpdate
id: "profile-js"
addBlockedBy: ["profile-css"]
# profile-js is now blocked, waiting for profile-css
The Complete Workflow
With all tasks created and dependencies set, here's how the workflow proceeds:
# Initial state
TaskList
profile-html in-progress -
profile-css blocked profile-html
profile-js blocked profile-css
# Claude completes HTML work
TaskUpdate id: "profile-html" status: "completed"
# CSS automatically unblocks
TaskList
profile-html completed -
profile-css pending - <- Ready!
profile-js blocked profile-css
# Claude works on CSS, then completes it
TaskUpdate id: "profile-css" status: "completed"
# JavaScript automatically unblocks
TaskList
profile-html completed -
profile-css completed -
profile-js pending - <- Ready!
# Claude completes JavaScript
TaskUpdate id: "profile-js" status: "completed"
# All phases complete
TaskList
profile-html completed -
profile-css completed -
profile-js completed -
Going Further
Task dependencies solve the sequencing problem: ensuring work happens in the right order. But complex projects have another challenge: ensuring the right specialists handle each task.
Dependencies Handle Sequencing
With addBlockedBy and addBlocks, you've mastered the timing of work. Tasks automatically unlock when prerequisites complete. The dependency graph guides the workflow without manual intervention.
But Who Does Each Task Matters
Sequencing is only half the equation. Consider our user profile example: Phase 1 (HTML) should be handled by someone who understands semantic markup. Phase 2 (CSS) requires visual design expertise. Phase 3 (JavaScript) needs programming skills.
In a single-agent setup, Claude handles everything. But as projects scale, you want specialists: a frontend designer for HTML/CSS, a web developer for JavaScript, an iOS developer for Swift code, a Firebase developer for backend logic.
Multi-Agent Development
This is where task dependencies combine with specialist agents for maximum effectiveness. Each phase routes to the right expert. Dependencies ensure work flows in order. The result is coordinated, high-quality output across all aspects of a project.
Task: profile-html
Agent: frontend-designer
Status: in-progress
Blocks: profile-css
Task: profile-css
Agent: frontend-designer
Status: blocked
Blocked by: profile-html
Blocks: profile-js
Task: profile-js
Agent: web-developer
Status: blocked
Blocked by: profile-css
Each task goes to the right specialist.
Dependencies ensure correct sequencing.
The PM coordinates the handoffs.
Dependencies tell you WHEN work can happen. Specialist agents tell you WHO should do it. Together, they enable sophisticated project coordination that scales to complex, multi-platform development.
Ready for Multi-Agent Development?
Claude Architect provides specialist agents (frontend, backend, iOS, web) coordinated by a PM agent. Task dependencies work seamlessly with agent routing for production-grade project management.
Join the Waitlist