Why this matters
Some work cannot finish inside one synchronous tool call. Claude Code uses the task layer to keep that work named, tracked, and visible while it is still running.
That same shared task model can support several concrete task kinds. One page can explain the contract, while the child pages each study a specific implementation.
How task work stays understandable
A shared model defines the common fields, concrete implementations fill in the real task kinds, and background visibility decides what shows up in the indicator.
One mental model for many kinds of work
Task.ts defines the common contract every task state starts from. It gives the
system shared labels for task type, status, and the base fields that every task
needs.
tasks/types.ts then decides which concrete task states count as background
tasks. That keeps the user-facing background indicator separate from the shared
task contract itself.
The child pages study the concrete task kinds one by one. Start with the shared model here, then move into the specific local shell, local agent, and remote agent flows after this.
export type TaskType =
| 'local_bash'
| 'local_agent'
| 'remote_agent'
| 'in_process_teammate'
| 'local_workflow'
| 'monitor_mcp'
| 'dream'
export type TaskStatus =
| 'pending'
| 'running'
| 'completed'
| 'failed'
| 'killed'
/**
* True when a task is in a terminal state and will not transition further.
* Used to guard against injecting messages into dead teammates, evicting
* finished tasks from AppState, and orphan-cleanup paths.
*/
export function isTerminalTaskStatus(status: TaskStatus): boolean {
return status === 'completed' || status === 'failed' || status === 'killed'
}
export type TaskHandle = {
taskId: string
cleanup?: () => void
}
export type SetAppState = (f: (prev: AppState) => AppState) => void
export type TaskContext = {
abortController: AbortController
getAppState: () => AppState
setAppState: SetAppState
}
// Base fields shared by all task states
export type TaskStateBase = {
id: string
type: TaskType
status: TaskStatus
description: string
toolUseId?: string
startTime: number
endTime?: number
totalPausedMs?: number
outputFile: string
outputOffset: number
notified: boolean
}
export type LocalShellSpawnInput = {
command: string
description: string
timeout?: number
toolUseId?: string
agentId?: AgentId
/** UI display variant: description-as-label, dialog title, status bar pill. */
kind?: 'bash' | 'monitor'
}
// What getTaskByType dispatches for: kill. spawn/render were never
// called polymorphically (removed in #22546). All six kill implementations
// use only setAppState — getAppState/abortController were dead weight.
export type Task = {
name: string
type: TaskType
kill(taskId: string, setAppState: SetAppState): Promise<void>
}
Task.ts is the common contract. It defines the names the rest of the system
can share before any individual task kind gets involved.
// Union of all concrete task state types
// Use this for components that need to work with any task type
import type { DreamTaskState } from './DreamTask/DreamTask.js'
import type { InProcessTeammateTaskState } from './InProcessTeammateTask/types.js'
import type { LocalAgentTaskState } from './LocalAgentTask/LocalAgentTask.js'
import type { LocalShellTaskState } from './LocalShellTask/guards.js'
import type { LocalWorkflowTaskState } from './LocalWorkflowTask/LocalWorkflowTask.js'
import type { MonitorMcpTaskState } from './MonitorMcpTask/MonitorMcpTask.js'
import type { RemoteAgentTaskState } from './RemoteAgentTask/RemoteAgentTask.js'
export type TaskState =
| LocalShellTaskState
| LocalAgentTaskState
| RemoteAgentTaskState
| InProcessTeammateTaskState
| LocalWorkflowTaskState
| MonitorMcpTaskState
| DreamTaskState
// Task types that can appear in the background tasks indicator
export type BackgroundTaskState =
| LocalShellTaskState
| LocalAgentTaskState
| RemoteAgentTaskState
| InProcessTeammateTaskState
| LocalWorkflowTaskState
| MonitorMcpTaskState
| DreamTaskState
/**
* Check if a task should be shown in the background tasks indicator.
* A task is considered a background task if:
* 1. It is running or pending
* 2. It has been explicitly backgrounded (not a foreground task)
*/
export function isBackgroundTask(task: TaskState): task is BackgroundTaskState {
if (task.status !== 'running' && task.status !== 'pending') {
return false
}
// Foreground tasks (isBackgrounded === false) are not yet "background tasks"
if ('isBackgrounded' in task && task.isBackgrounded === false) {
return false
}
return true
}
tasks/types.ts is the background-visibility filter. It decides which concrete
task states count as background tasks, while the child pages go deeper into the
specific local shell, local agent, and remote agent implementations one by one.
How this part breaks down
task-model-and-lifecycle-basicsStart with the shared language every task uses: task types, statuses, state fields, and the rule for when a task counts as background work.task-and-agent-data-structuresRead this appendix early if the task state objects feel abstract. It introduces the recurring shapes before later pages depend on them.local-shell-task-and-background-commandsSee how long-running bash commands become durable tasks with output files, stall detection, and notifications.local-agent-task-lifecycleLearn how Claude Code tracks a background local agent, including its progress, pending messages, and foreground/background transitions.remote-agent-task-lifecycleFollow the remote version of the same idea: eligibility checks, remote metadata, completion polling, and plan extraction.