A comprehensive guide to understanding and controlling Claude Code's Task tool delegation system.
- What is the Task Tool?
- Subagent Types
- Task(Explore) In Depth
- When Claude Uses Task(Explore)
- Task Tool Input/Output Schema
- Intercepting Task Calls with Hooks
- Common Pitfalls
- Best Practices
- Integration with Task Tracking (Beads)
The Task tool is Claude Code's built-in mechanism for delegating work to specialized subagents. Instead of the main Claude instance doing everything in one context window, it can spawn lightweight "child" agents that:
- Operate in separate context windows (prevents bloat)
- Can run in parallel (up to ~10 concurrent)
- Have specialized capabilities based on type
- Return only relevant findings to the parent
Think of it as Claude's ability to "hire assistants" for specific jobs.
| Without Subagents | With Subagents |
|---|---|
| All exploration results fill main context | Exploration stays isolated |
| Serial execution only | Parallel execution possible |
| One model for everything | Right-sized models per task |
| Context window exhaustion | Context stays clean |
Claude Code has three built-in subagent types:
Model: claude-haiku-4-5 (fast, cheap)
Mode: READ-ONLY
Tools: Glob, Grep, Read, Bash (read-only commands only)
Purpose: Fast codebase exploration without modification risk.
Model: claude-sonnet-4 (capable)
Mode: FULL ACCESS (read/write)
Tools: All tools available to main agent
Purpose: Complex multi-step tasks requiring code changes.
Model: claude-sonnet-4
Mode: READ-ONLY
Tools: Glob, Grep, Read, Bash
Purpose: Research during plan mode (before implementation approval).
| Feature | Explore | General-Purpose | Plan |
|---|---|---|---|
| Model | Haiku | Sonnet | Sonnet |
| Speed | Fast (~2-5s) | Slower (~10-30s) | Medium |
| Cost | Low | Higher | Medium |
| Can Write Files | No | Yes | No |
| Can Run Commands | Read-only | Yes | Read-only |
| Context Isolation | Yes | Yes | Yes |
| Best For | Search, analysis | Implementation | Planning research |
When Claude invokes Task with subagent_type: "Explore", it:
- Spawns a Haiku-powered subagent with read-only tools
- Passes the prompt describing what to search for
- Subagent explores using Glob, Grep, Read, Bash
- Returns findings as a single response to the parent
- Context is discarded - only the final report persists
// These tools are available to Explore subagent:
{
Glob: "File pattern matching (e.g., **/*.ts)",
Grep: "Content search with regex",
Read: "Read file contents",
Bash: "Read-only commands only"
}
// Read-only Bash commands allowed:
const allowedCommands = [
"ls", "find", "cat", "head", "tail",
"git status", "git log", "git diff", "git show",
"wc", "file", "which", "pwd", "echo"
]
// NOT allowed in Explore:
const blockedCommands = [
"rm", "mv", "cp", "mkdir", "touch",
"git commit", "git push", "npm install",
"any write operation"
]When Claude spawns an Explore agent, it specifies thoroughness:
| Level | Behavior | Use Case |
|---|---|---|
quick |
Single search strategy, first matches | Known file names, simple lookups |
medium |
Multiple strategies, moderate depth | General exploration |
very thorough |
Exhaustive search, multiple patterns | Unfamiliar codebases, edge cases |
{
"tool": "Task",
"input": {
"subagent_type": "Explore",
"description": "Find auth handlers",
"prompt": "Search the codebase for all authentication-related code. Look for:\n1. Files with 'auth' in the name\n2. Functions handling login/logout\n3. JWT or session token handling\n4. Middleware that checks authentication\n\nReturn absolute file paths with relevant code snippets."
}
}The subagent returns a single text response containing:
- File paths found
- Relevant code snippets
- Analysis/summary
- Recommendations for next steps
## Authentication Code Analysis
### Files Found
- `/src/auth/login.ts` - Main login handler
- `/src/middleware/auth.ts` - JWT verification middleware
- `/src/utils/token.ts` - Token generation utilities
### Key Findings
1. Login uses bcrypt for password hashing (line 45)
2. JWT tokens expire after 24h (line 12 in token.ts)
3. No refresh token implementation found
### Recommendation
Consider adding refresh token logic before modifying auth flow.Claude automatically uses Task(Explore) when:
| User Says | Claude Does |
|---|---|
| "Where is X handled?" | Explore for X-related code |
| "How does Y work?" | Explore Y implementation |
| "Find all Z" | Explore with grep/glob patterns |
| "What's the codebase structure?" | Explore directories and files |
| "Show me the architecture" | Explore key files and patterns |
From Claude Code's system prompt:
When exploring the codebase to gather context or to answer a question
that is not a needle query for a specific file/class/function, it is
CRITICAL that you use the Task tool with subagent_type=Explore instead
of running search commands directly.
- Specific file known: Use
Readdirectly - Single grep needed: Use
Grepdirectly - Implementation required: Use
general-purpose - Planning phase: Use
Plansubagent
interface TaskInput {
// Required
subagent_type: "Explore" | "general-purpose" | "Plan" | string;
prompt: string; // Detailed instructions for subagent
description: string; // Short (3-5 word) summary
// Optional
model?: "sonnet" | "opus" | "haiku"; // Override default model
run_in_background?: boolean; // Don't wait for completion
resume?: string; // Agent ID to resume from
}interface TaskOutput {
result: string; // Final text response from subagent
usage?: {
input_tokens: number;
output_tokens: number;
};
total_cost_usd?: number;
duration_ms?: number;
}// Start background task
{
"tool": "Task",
"input": {
"subagent_type": "Explore",
"prompt": "...",
"run_in_background": true
}
}
// Returns: { "agentId": "abc123" }
// Check on background task
{
"tool": "AgentOutputTool",
"input": {
"agentId": "abc123",
"block": false // Don't wait, just check status
}
}Claude Code hooks can intercept tool calls at two points:
User Prompt → [UserPromptSubmit] → Claude Thinking →
[PreToolUse] → Tool Execution → [PostToolUse] → Response
You can intercept Task calls in PreToolUse.ts:
// ~/.claude/hooks/PreToolUse.ts
import type { PreToolUseHookInput, HookJSONOutput } from "@anthropic-ai/claude-agent-sdk"
const input = await Bun.stdin.json() as PreToolUseHookInput
if (input.tool_name === "Task") {
const toolInput = input.tool_input as {
subagent_type?: string
prompt?: string
description?: string
}
// Example: Block Explore for certain patterns
if (toolInput.subagent_type === "Explore") {
const prompt = toolInput.prompt?.toLowerCase() || ""
// Inject context before allowing
if (prompt.includes("implement") || prompt.includes("create")) {
const output: HookJSONOutput = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
additionalContext: `<reminder>Create beads issues before exploration.</reminder>`
}
}
console.log(JSON.stringify(output))
process.exit(0)
}
}
}| Action | Effect |
|---|---|
allow |
Proceed with tool call |
deny |
Block tool call, return error to Claude |
allow + updatedInput |
Modify tool input before execution |
allow + additionalContext |
Inject system message with approval |
if (toolInput.subagent_type === "general-purpose" && !hasBeadsIssue) {
const output: HookJSONOutput = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Create a beads issue before starting implementation work."
}
}
console.log(JSON.stringify(output))
process.exit(0)
}// Force thoroughness level
if (toolInput.subagent_type === "Explore") {
const modifiedPrompt = toolInput.prompt + "\n\nUse VERY THOROUGH exploration."
const output: HookJSONOutput = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
updatedInput: {
...toolInput,
prompt: modifiedPrompt
}
}
}
console.log(JSON.stringify(output))
process.exit(0)
}// ~/.claude/hooks/PreToolUse.ts
import type { PreToolUseHookInput, HookJSONOutput } from "@anthropic-ai/claude-agent-sdk"
const input = await Bun.stdin.json() as PreToolUseHookInput
type TaskToolInput = {
description?: string
prompt?: string
subagent_type?: string
}
if (input.tool_name === "Task") {
const toolInput = input.tool_input as TaskToolInput
const prompt = (toolInput.prompt || "").toLowerCase()
const subagentType = toolInput.subagent_type || ""
// Track all Task invocations
console.error(`[Hook] Task(${subagentType}): ${toolInput.description}`)
// Multi-step detection
const multiStepWords = ["implement", "create", "build", "features", "steps"]
const isMultiStep = multiStepWords.some(w => prompt.includes(w))
// Inject reminder for multi-step tasks
if (isMultiStep && process.env.BEADS_INITIALIZED === "true") {
const output: HookJSONOutput = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow",
additionalContext: `<beads-reminder>
Before delegating multi-step work:
1. Create beads issues first
2. Track progress with beads_update
3. Close completed work with beads_close
</beads-reminder>`
}
}
console.log(JSON.stringify(output))
process.exit(0)
}
}
// Default: allow everything else
const output: HookJSONOutput = {
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "allow"
}
}
console.log(JSON.stringify(output))Problem: Claude uses Task(Explore) immediately, implements everything, never tracks progress.
User: "Implement 10 features"
Claude: Task(Explore) → code → code → code... (no tracking)
Solution: Use hooks to inject beads reminders, or update system prompt to require tracking first.
Problem: Explore findings don't persist - next session has no memory.
Session 1: Task(Explore) finds auth code locations
Session 2: "Where's the auth code?" - starts over
Solution: Use beads to persist exploration results:
beads_add({
title: "Auth code locations",
body: "Found in: /src/auth/*, /src/middleware/auth.ts",
type: "task"
})Problem: Using Explore when implementation is needed.
User: "Refactor the auth module"
Claude: Task(Explore) - finds code but can't change it
Solution: Clarify in prompt or use general-purpose for implementation tasks.
Problem: Claude explores but doesn't use findings effectively.
Solution: Explicit prompt engineering:
"Based on the exploration results, identify the specific files
that need modification and explain your implementation plan
before making any changes."
Good Explore Prompt:
Search the codebase for authentication implementation:
1. Find all files containing 'auth', 'login', 'jwt', 'session'
2. Identify the main authentication entry points
3. Trace the auth flow from request to response
4. Note any security concerns or patterns used
Return:
- Absolute file paths
- Key code snippets (20 lines max each)
- Flow diagram if complex
- Recommendations for the main task
Bad Explore Prompt:
Find auth code
1. beads_add({ title: "Feature X", type: "feature" })
2. Task(Explore) - understand codebase
3. beads_update({ notes: "Found: /src/x.ts needs changes" })
4. Implement changes
5. beads_close({ reason: "Completed" })
// Launch multiple explorations in parallel
const tasks = [
Task({ subagent_type: "Explore", prompt: "Find auth code", run_in_background: true }),
Task({ subagent_type: "Explore", prompt: "Find database models", run_in_background: true }),
Task({ subagent_type: "Explore", prompt: "Find API routes", run_in_background: true })
]
// Collect results
for (const { agentId } of tasks) {
const result = await AgentOutputTool({ agentId, block: true })
// Process each result
}// Require beads issue before general-purpose tasks
if (toolInput.subagent_type === "general-purpose") {
const hasBeadsIssue = await checkActiveBeadsIssue()
if (!hasBeadsIssue) {
deny("Create a beads issue before starting implementation.")
}
}Without tracking integration:
- Explore results are lost between sessions
- Multi-step tasks have no progress visibility
- No audit trail of what was found/done
// 1. Create tracking issue FIRST
beads_add({
title: "Refactor authentication module",
type: "feature",
body: "User requested auth refactor"
})
// Returns: { id: "cm-proj-123" }
// 2. THEN explore
Task({
subagent_type: "Explore",
prompt: "Find all auth-related code for refactoring..."
})
// Returns: exploration results
// 3. Update issue with findings
beads_update({
id: "cm-proj-123",
notes: "Found: /src/auth/login.ts, /src/middleware/auth.ts need changes"
})
// 4. Break down into subtasks if needed
beads_add({
title: "Update login handler",
parent: "cm-proj-123"
})
beads_add({
title: "Update auth middleware",
parent: "cm-proj-123"
})
// 5. Implement and close
// ... do work ...
beads_close({
id: "cm-proj-123.1",
reason: "Login handler updated with new token format"
})// Auto-show pending work before any exploration
const readyWork = await beads_ready()
if (readyWork.issues.length > 0) {
inject(`You have ${readyWork.issues.length} items ready to work on.
Consider completing existing work before exploring new tasks.`)
}┌─────────────────────────────────────────────────────────┐
│ SESSION START │
├─────────────────────────────────────────────────────────┤
│ 1. SessionStart hook runs │
│ 2. Auto-init beads if missing │
│ 3. Show beads_ready items │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ USER REQUEST │
├─────────────────────────────────────────────────────────┤
│ User: "Implement 10 features" │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ CLAUDE RESPONSE │
├─────────────────────────────────────────────────────────┤
│ 1. beads_add(epic) ← CREATE TRACKING FIRST │
│ 2. beads_batch_add(10 subtasks) │
│ 3. Task(Explore) ← THEN EXPLORE │
│ 4. PreToolUse hook injects reminder │
│ 5. Implement feature 1 │
│ 6. beads_close(subtask 1) │
│ 7. Repeat for remaining features │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ SESSION END │
├─────────────────────────────────────────────────────────┤
│ 1. beads_update(notes) for in-progress work │
│ 2. beads_sync() to persist │
│ 3. Next session: beads_ready() shows where we left off │
└─────────────────────────────────────────────────────────┘
// From @anthropic-ai/claude-agent-sdk
interface PreToolUseHookInput {
session_id: string
tool_name: string
tool_input: Record<string, unknown>
}
interface HookJSONOutput {
hookSpecificOutput: {
hookEventName: "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "SessionStart"
permissionDecision?: "allow" | "deny"
permissionDecisionReason?: string
updatedInput?: Record<string, unknown>
additionalContext?: string
}
}
interface TaskToolInput {
subagent_type: string
prompt: string
description: string
model?: string
run_in_background?: boolean
resume?: string
}Last updated: 2025-12-06