Skip to content

Instantly share code, notes, and snippets.

@johnlindquist
Created December 6, 2025 20:49
Show Gist options
  • Select an option

  • Save johnlindquist/d22c70fd70660b4f6fb4d0b05d0792d2 to your computer and use it in GitHub Desktop.

Select an option

Save johnlindquist/d22c70fd70660b4f6fb4d0b05d0792d2 to your computer and use it in GitHub Desktop.
Claude Code Task Tool Deep Dive: Subagents, Explore, and Hooks

Claude Code Task Tool Deep Dive: Subagents, Explore, and Hooks

A comprehensive guide to understanding and controlling Claude Code's Task tool delegation system.

Table of Contents

  1. What is the Task Tool?
  2. Subagent Types
  3. Task(Explore) In Depth
  4. When Claude Uses Task(Explore)
  5. Task Tool Input/Output Schema
  6. Intercepting Task Calls with Hooks
  7. Common Pitfalls
  8. Best Practices
  9. Integration with Task Tracking (Beads)

What is the Task Tool?

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.

Why Subagents Matter

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

Subagent Types

Claude Code has three built-in subagent types:

1. Explore (Default for Search)

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.

2. General-Purpose

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.

3. Plan

Model: claude-sonnet-4
Mode: READ-ONLY
Tools: Glob, Grep, Read, Bash

Purpose: Research during plan mode (before implementation approval).

Comparison Table

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

Task(Explore) In Depth

What It Does

When Claude invokes Task with subagent_type: "Explore", it:

  1. Spawns a Haiku-powered subagent with read-only tools
  2. Passes the prompt describing what to search for
  3. Subagent explores using Glob, Grep, Read, Bash
  4. Returns findings as a single response to the parent
  5. Context is discarded - only the final report persists

Available Tools in Explore Mode

// 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"
]

Thoroughness Levels

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

Example Explore Invocation

{
  "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."
  }
}

What Explore Returns

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.

When Claude Uses Task(Explore)

Automatic Triggers

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

System Prompt Guidance

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.

When NOT to Use Explore

  • Specific file known: Use Read directly
  • Single grep needed: Use Grep directly
  • Implementation required: Use general-purpose
  • Planning phase: Use Plan subagent

Task Tool Input/Output Schema

Input Schema

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
}

Output Schema

interface TaskOutput {
  result: string;              // Final text response from subagent
  usage?: {
    input_tokens: number;
    output_tokens: number;
  };
  total_cost_usd?: number;
  duration_ms?: number;
}

Background Tasks

// 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
  }
}

Intercepting Task Calls with Hooks

Hook Architecture

Claude Code hooks can intercept tool calls at two points:

User Prompt → [UserPromptSubmit] → Claude Thinking →
  [PreToolUse] → Tool Execution → [PostToolUse] → Response

PreToolUse Hook for Task

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)
    }
  }
}

Hook Actions

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

Deny Example

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)
}

Modify Input Example

// 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)
}

Full Hook Example

// ~/.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))

Common Pitfalls

1. Explore Before Tracking

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.

2. Context Loss Between Sessions

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"
})

3. Wrong Subagent Type

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.

4. Ignoring Explore Results

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."

Best Practices

1. Prompt Engineering for Explore

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

2. Ordering: Track → Explore → Implement

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" })

3. Use Background Tasks for Parallel Exploration

// 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
}

4. Hook-Based Quality Gates

// 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.")
  }
}

Integration with Task Tracking (Beads)

The Problem

Without tracking integration:

  • Explore results are lost between sessions
  • Multi-step tasks have no progress visibility
  • No audit trail of what was found/done

The Solution: Beads + Task Protocol

// 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"
})

SessionStart Hook Integration

// 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.`)
}

The Complete Flow

┌─────────────────────────────────────────────────────────┐
│                    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  │
└─────────────────────────────────────────────────────────┘

Appendix: Hook Type Definitions

// 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
}

References


Last updated: 2025-12-06

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment