AgentSkillsCN

claude-code-tasks

指导用户通过外部代码,以编程方式与 Claude Code 的任务/待办事项系统进行交互。每当用户希望从脚本中监控或追踪 Claude Code 任务、构建对待办事项更新作出响应的自动化流程、创建强制执行任务完成条件的钩子、开发自定义任务管理 MCP 工具、利用 Agent SDK 监控或控制任务进度、通过 CLI 流式传输任务更新、将 Claude Code 的任务系统与外部服务集成,或跨子代理统筹工作时,均可触发此技能。即使用户并未明确提及“技能”,只要提到“TodoWrite”、“TaskCompleted”、“Agent SDK 任务”、“编程化任务”、“任务钩子”或“Claude Code 自动化”,同样适用此技能。

SKILL.md
--- frontmatter
name: claude-code-tasks
description: |
  Guide for programmatically interacting with Claude Code's task/todo system from external code. Use this skill whenever the user wants to: monitor or track Claude Code tasks from scripts, build automations that react to todo updates, create hooks that enforce task completion criteria, build custom task management MCP tools, use the Agent SDK to watch or control task progress, stream task updates via the CLI, integrate Claude Code's task system with external services, or orchestrate work across subagents. Also trigger when users mention "TodoWrite", "TaskCompleted", "Agent SDK tasks", "programmatic tasks", "task hooks", or "claude code automation" — even if they don't explicitly say "skill".

Programmatic Task Management for Claude Code

Five verified approaches for interacting with Claude Code's task/todo system from code. Each suits different use cases — from lightweight shell scripts to full orchestration systems.

Quick Reference: Which Approach to Use

GoalApproachLanguage
Watch task progress in real timeAgent SDK message streamPython / TypeScript
React to task changes with side effectsHooks (PostToolUse on TodoWrite)Shell / Python
Block task completion until criteria metHooks (TaskCompleted)Shell / Python / LLM
Add custom task-like tools Claude can callCustom MCP toolsPython / TypeScript
Parse task updates from CLI outputHeadless CLI streamingAny (parses NDJSON)
Delegate work to specialized sub-agentsSubagents via Agent SDKPython / TypeScript

Approach 1: Agent SDK Message Stream

The most direct way to monitor tasks programmatically. The query() function returns an async generator. Every TodoWrite call appears as a tool_use block in the stream.

Install:

bash
npm install @anthropic-ai/claude-agent-sdk   # TypeScript
pip install claude-agent-sdk                  # Python

TodoWrite Schema

typescript
// Input: complete replacement of the todo list each time
interface TodoWriteInput {
  todos: Array<{
    content: string;                              // Imperative form: "Fix the bug"
    status: 'pending' | 'in_progress' | 'completed';
    activeForm: string;                           // Present participle: "Fixing the bug"
  }>;
}

// Output: confirmation with stats
interface TodoWriteOutput {
  message: string;
  stats: { total: number; pending: number; in_progress: number; completed: number };
}

Only one todo should be in_progress at any time. The list is replaced wholesale on each call (not diffed).

TypeScript: Monitor Todos

typescript
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Refactor the auth module and track your progress",
  options: { maxTurns: 15 }
})) {
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "tool_use" && block.name === "TodoWrite") {
        const todos = block.input.todos;
        console.log("--- Todo Update ---");
        todos.forEach((todo, i) => {
          const icon = { completed: "done", in_progress: ">>", pending: "  " }[todo.status];
          console.log(`  [${icon}] ${todo.content}`);
        });
      }
    }
  }
}

Python: Monitor Todos

python
from claude_agent_sdk import query, AssistantMessage, ToolUseBlock

async for message in query(
    prompt="Refactor the auth module and track your progress",
    options={"max_turns": 15}
):
    if isinstance(message, AssistantMessage):
        for block in message.content:
            if isinstance(block, ToolUseBlock) and block.name == "TodoWrite":
                todos = block.input["todos"]
                for todo in todos:
                    status_icon = {"completed": "done", "in_progress": ">>", "pending": "  "}
                    print(f"  [{status_icon[todo['status']]}] {todo['content']}")

For a full real-time progress tracker class with change detection, see references/progress-tracker.md.

Approach 2: Hooks

Hooks fire at lifecycle points and are configured in .claude/settings.json (project-scoped) or ~/.claude/settings.json (global). Three hook handler types exist: command (shell script), prompt (single LLM call), and agent (multi-turn subagent with tool access).

Intercept TodoWrite Updates

Use PostToolUse with matcher "TodoWrite" to react after a todo update, or PreToolUse to validate/block before it happens.

Log every todo update (.claude/settings.json):

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "TodoWrite",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/log-todos.sh"
          }
        ]
      }
    ]
  }
}

.claude/hooks/log-todos.sh:

bash
#!/bin/bash
INPUT=$(cat)
TODOS=$(echo "$INPUT" | jq '.tool_input.todos')
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "{\"timestamp\": \"$TIMESTAMP\", \"todos\": $TODOS}" >> "$CLAUDE_PROJECT_DIR/.claude/todo-log.jsonl"
exit 0

Block certain todos via PreToolUse:

bash
#!/bin/bash
INPUT=$(cat)
FORBIDDEN=$(echo "$INPUT" | jq -r '.tool_input.todos[].content' | grep -i "deploy to production")
if [ -n "$FORBIDDEN" ]; then
  jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"Production deploys require manual approval"}}'
else
  exit 0
fi

TaskCompleted Hook

Fires when any task is being marked as completed. Exit code 2 blocks completion and feeds stderr back to the agent as corrective feedback. No matcher support — fires on every completion.

Input fields: task_id, task_subject, task_description (optional), teammate_name (optional), team_name (optional).

Require tests to pass:

json
{
  "hooks": {
    "TaskCompleted": [
      {
        "hooks": [{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/require-tests.sh" }]
      }
    ]
  }
}
bash
#!/bin/bash
INPUT=$(cat)
TASK=$(echo "$INPUT" | jq -r '.task_subject')
if ! npm test 2>&1; then
  echo "Tests failing. Fix before completing: $TASK" >&2
  exit 2
fi
exit 0

LLM-based completion gate:

json
{
  "hooks": {
    "TaskCompleted": [
      {
        "hooks": [{
          "type": "prompt",
          "prompt": "Evaluate whether this task is truly complete: $ARGUMENTS. Check all subtasks are done and no errors remain.",
          "timeout": 30
        }]
      }
    ]
  }
}

The LLM responds with {"ok": true} or {"ok": false, "reason": "..."}.

Agent-based deep verification:

json
{
  "hooks": {
    "TaskCompleted": [
      {
        "hooks": [{
          "type": "agent",
          "prompt": "Verify task completion: check tests pass, no TODO comments in changed files, docs updated. Context: $ARGUMENTS",
          "timeout": 120
        }]
      }
    ]
  }
}

Agent hooks spawn a subagent with access to Read, Grep, and Glob tools for multi-turn investigation.

Other Useful Hook Events

EventMatcherUse Case
StopnonePrevent Claude from stopping if tasks remain incomplete
SubagentStartagent typeReact when a subagent spawns
SubagentStopagent typeBlock subagent from finishing; can force more work
SessionStartstartup/resumeInject task context at session start

For the complete hook reference (all events, inputs, outputs, exit codes), see references/hooks-reference.md.

Approach 3: Custom MCP Tools

Build your own task management tools as an in-process MCP server. Claude can call these alongside its built-in TodoWrite.

Important: Custom MCP tools require streaming input mode (async generator for the prompt parameter, not a plain string).

TypeScript: Custom Task Board

typescript
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";

const taskBoard = createSdkMcpServer({
  name: "task-board",
  version: "1.0.0",
  tools: [
    tool("create_task", "Create a task on the external board",
      { title: z.string(), priority: z.enum(["low", "medium", "high"]) },
      async (args) => {
        // Insert into DB, call API, etc.
        return { content: [{ type: "text", text: `Created: ${args.title} [${args.priority}]` }] };
      }
    ),
    tool("complete_task", "Mark a task as done",
      { task_id: z.string() },
      async (args) => {
        return { content: [{ type: "text", text: `Completed: ${args.task_id}` }] };
      }
    )
  ]
});

// Streaming input is required for MCP servers
async function* messages() {
  yield { type: "user" as const, message: { role: "user" as const, content: "Create a task to fix the login bug" } };
}

for await (const msg of query({
  prompt: messages(),
  options: {
    mcpServers: { "task-board": taskBoard },
    allowedTools: ["mcp__task-board__create_task", "mcp__task-board__complete_task"]
  }
})) {
  if ("result" in msg) console.log(msg.result);
}

Python: Custom Task Board

python
from claude_agent_sdk import tool, create_sdk_mcp_server
from typing import Any

@tool("create_task", "Create a task on the external board", {"title": str, "priority": str})
async def create_task(args: dict[str, Any]) -> dict[str, Any]:
    return {"content": [{"type": "text", "text": f"Created: {args['title']} [{args['priority']}]"}]}

@tool("complete_task", "Mark a task as done", {"task_id": str})
async def complete_task(args: dict[str, Any]) -> dict[str, Any]:
    return {"content": [{"type": "text", "text": f"Completed: {args['task_id']}"}]}

task_server = create_sdk_mcp_server(name="task-board", version="1.0.0", tools=[create_task, complete_task])

Tool names follow the pattern mcp__{server_name}__{tool_name} (e.g., mcp__task-board__create_task).

Approach 4: CLI Headless Streaming

Parse task updates from claude -p output without any SDK code. Ideal for shell scripts, CI/CD, and language-agnostic integrations.

Stream and Filter TodoWrite Events

bash
claude -p "Refactor this module and track progress" \
  --output-format stream-json \
  --verbose \
  --include-partial-messages | \
  jq -c 'select(.type == "assistant") |
    .message.content[]? |
    select(.type == "tool_use" and .name == "TodoWrite") |
    .input.todos'

Session Management

bash
# Capture session ID
SESSION=$(claude -p "Start analysis" --output-format json | jq -r '.session_id')

# Resume later
claude -p "Continue the analysis" --resume "$SESSION"

# Or just continue most recent
claude -p "What's left to do?" --continue

Key CLI Flags

FlagPurpose
-pNon-interactive (headless) mode
--output-format stream-jsonNDJSON streaming for real-time parsing
--output-format jsonStructured JSON with session_id and metadata
--verboseFull turn-by-turn output
--include-partial-messagesToken-level streaming
--continue / --resume <id>Session continuity
--allowedTools "Bash,Read,Edit"Auto-approve specific tools
--append-system-prompt "..."Add instructions while keeping defaults

Approach 5: Subagents

Define specialized agents that handle focused subtasks. The main agent delegates via the Task tool.

typescript
for await (const message of query({
  prompt: "Review auth security, then run tests",
  options: {
    allowedTools: ["Read", "Grep", "Glob", "Bash", "Task"],
    agents: {
      "security-reviewer": {
        description: "Security code review specialist.",
        prompt: "Identify vulnerabilities, injection risks, and auth flaws.",
        tools: ["Read", "Grep", "Glob"],   // read-only
        model: "sonnet"
      },
      "test-runner": {
        description: "Test execution and analysis specialist.",
        prompt: "Run test suites and analyze failures.",
        tools: ["Bash", "Read", "Grep"]
      }
    }
  }
})) {
  // Detect delegation
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "tool_use" && block.name === "Task") {
        console.log(`Delegated to: ${block.input.subagent_type}`);
      }
    }
  }
}

Key rules: subagents cannot spawn their own subagents; include Task in allowedTools to enable delegation; omit tools to inherit all parent tools.

For resuming subagents and advanced patterns, see references/subagent-patterns.md.

Combining Approaches

These compose naturally. Common combinations:

  • SDK + Hooks: Monitor via message stream while hooks enforce quality gates.
  • CLI + Custom MCP: Use claude -p in CI with project-specific task tools.
  • SDK + MCP + Subagents: Full orchestration — custom task tools, specialized agents, real-time monitoring.

For detailed combined examples, see references/combined-patterns.md.

Official Documentation