InkdownInkdown
Start writing

Claude-Code

62 files·4 subfolders

Shared Workspace

Claude-Code
codex

05-state-management

Shared from "Claude-Code" on Inkdown

State Management

How the application tracks and shares state across its many subsystems.


Two Layers of State

Claude Code uses two distinct state management patterns:

  1. Bootstrap State — Global singleton with getter/setter functions
  2. AppState Store — Redux-like store with React integration

Bootstrap State (bootstrap/state.ts)

A singleton module that holds global session state. Think of it as "carefully managed global variables."

Design Principles
0000_start_here_index_and_recommended_reading_order.md
0100_project_overview_tech_stack_runtime_modes_and_folder_map.md
0200_startup_flow_entry_points_and_cold_start_sequence.md
0300_codebase_modules_layers_state_models_and_schemas.md
0400_system_architecture_and_design_rationale.md
0500_interactive_repl_request_flow_end_to_end.md
0600_headless_sdk_and_print_mode_request_flow_end_to_end.md
0700_mcp_integration_connection_and_tool_call_flow.md
0800_external_services_sdks_storage_and_local_dependencies.md
0900_environment_variables_settings_feature_flags_and_failure_modes.md
1000_non_obvious_patterns_gotchas_and_debugging_traps.md
1100_full_codebase_file_inventory_grouped_by_directory.md
kimi
00-overview.md
01-entrypoints.md
02-state-management.md
03-query-system.md
04-tools-system.md
05-tasks-system.md
06-ui-components.md
07-bridge-remote.md
08-services.md
09-skills-plugins.md
10-commands.md
11-testing-architecture.md
12-permission-system.md
13-build-system.md
14-ink-internals.md
15-git-internals.md
16-context-compaction.md
17-vim-mode.md
18-mailbox-notifications.md
19-session-persistence.md
20-hooks-system.md
21-error-recovery.md
README.md
qwen
00-overview.md
01-entry-points.md
02-query-engine.md
03-tools-and-tasks.md
04-commands-and-skills.md
05-state-management.md
06-ink-rendering.md
07-bridge-remote.md
08-mcp-services.md
09-services-overview.md
10-multi-agent.md
11-system-prompt-constants.md
12-tool-interface.md
13-memory-system.md
14-buddy-companion.md
15-keybindings.md
16-stop-hooks.md
17-vim-mode.md
18-upstreamproxy.md
19-cost-tracking-history.md
20-contexts-styles-onboarding.md
21-hooks.md
22-screens.md
tweets-explain
claude-code-memory-analysis.md
compact
memory-system
agentic-architecture
  • "DO NOT ADD MORE STATE HERE" — The file explicitly warns against bloat
  • Getter/setter pattern — No direct state access, only through functions
  • Import DAG leaf — Imports only from utilities, imported by everything else
  • Test-safe — resetStateForTests() for clean test state
  • What It Tracks
    Session Identity
    Plain text
    getSessionId()          — Current session UUID
    switchSession(id)       — Switch to a new session
    getParentSessionId()    — Parent session (for lineage tracking)
    Directories
    Plain text
    getOriginalCwd()        — Where the user started
    getProjectRoot()        — Stable project root (set once)
    getCwdState()           — Current working directory (can change)
    getAdditionalDirectoriesForClaudeMd()  — Extra dirs from --add-dir
    Cost & Usage
    Plain text
    getTotalCostUSD()       — Total API cost in dollars
    getTotalAPIDuration()   — Total API wait time
    getTotalToolDuration()  — Total tool execution time
    getTotalInputTokens()   — Total input tokens
    getTotalOutputTokens()  — Total output tokens
    getTotalLinesAdded()    — Lines of code added
    getTotalLinesRemoved()  — Lines of code removed
    Model Configuration
    Plain text
    getInitialMainLoopModel()     — Model set at startup
    getMainLoopModelOverride()    — Runtime model override
    getModelUsage()               — Per-model token usage
    getSdkBetas()                 — SDK beta feature flags
    Telemetry (OpenTelemetry)
    Plain text
    getMeter()              — OTel meter for metrics
    getTracerProvider()     — OTel tracer provider
    getEventLogger()        — OTel event logger
    getSessionCounter()     — Session counter metric
    getCostCounter()        — Cost counter metric
    getTokenCounter()       — Token counter metric
    Session Behavior
    Plain text
    getIsInteractive()            — Interactive TUI vs headless
    getClientType()               — cli, sdk, github-action, etc.
    getKairosActive()             — Assistant mode active
    getIsRemoteMode()             — --remote flag
    getStrictToolResultPairing()  — Strict tool result mode
    Prompt Cache Optimization
    Plain text
    getFastModeHeaderLatched()     — Once on, stays on
    getAfkModeHeaderLatched()      — Once on, stays on
    getCacheEditingHeaderLatched() — Once on, stays on
    getThinkingClearLatched()      — Once on, stays on

    The "sticky-on latch" pattern prevents prompt cache busting. Once a beta header is activated, it stays active for the entire session.

    Skills & Compaction
    Plain text
    addInvokedSkill(skillName)     — Track skill invocations
    getInvokedSkills()             — Get all invoked skills
    markPostCompaction()           — Flag set after compaction
    consumePostCompaction()        — Clear and return flag
    Debug & Diagnostics
    Plain text
    getLastAPIRequest()           — Last API request body
    getLastAPIRequestMessages()   — Messages from last request
    getLastClassifierRequests()   — Classifier requests
    addToInMemoryErrorLog(error)  — Recent errors (capped at 100)

    AppState Store (state/)

    A Redux-like store that holds all UI and runtime state. Built on React's useSyncExternalStore.

    Architecture
    Plain text
    ┌─────────────────────────────────────────────────────────┐
    │                    AppStateProvider                     │
    │                                                         │
    │  const store = createStore(initialState, onChange)      │
    │                                                         │
    │  ┌──────────────┐  ┌───────────────┐                   │
    │  │ AppStoreCtx  │  │ onChangeState │                   │
    │  │ (React ctx)  │  │ (side effects)│                   │
    │  └──────┬───────┘  └───────┬───────┘                   │
    │         │                  │                           │
    │  ┌──────┴──────┐    ┌──────┴──────┐                    │
    │  │useAppState  │    │ Persist to  │                    │
    │  │(subscribe)  │    │ disk, notify│                    │
    │  └─────────────┘    │ CCR, etc.   │                    │
    │                     └─────────────┘                    │
    └─────────────────────────────────────────────────────────┘
    The Store (store.ts)

    A minimal, generic store implementation:

    TypeScript
    function createStore<T>(initialState: T, onChange?: (newState: T, oldState: T) => void) {
      let state = initialState
      const listeners = new Set<() => void>()
    
      return {
        getState: () => state,
        setState: (updater: (prev: T) => T) => {
          const next = updater(state)
          if (!Object.is(next, state)) {
            const old = state
            state = next
            onChange?.(next, old)        // Side effects
            listeners.forEach(l => l())  // Notify subscribers
          }
        },
        subscribe: (listener) => {
          listeners.add(listener)
          return () => listeners.delete(listener)
        }
      }
    }

    Key design:

    • Immutable updates via updater function
    • Referential equality check — skips notification if same reference
    • onChange callback — for side effects outside React
    AppState Shape

    The AppState type is large and deeply structured. Major categories:

    Settings & Model
    TypeScript
    settings: Settings
    verbose: boolean
    mainLoopModel: string | null
    mainLoopModelForSession: string | null
    thinkingEnabled: boolean
    UI State
    TypeScript
    expandedView: ExpandedView | null
    isBriefOnly: boolean
    footerSelection: string
    selectedIPAgentIndex: number
    viewSelectionMode: boolean
    statusLineText: string
    Tasks & Agents
    TypeScript
    tasks: TaskState[]
    foregroundedTaskId: string | null
    viewingAgentTaskId: string | null
    agentNameRegistry: Map<string, AgentInfo>
    MCP
    TypeScript
    mcp: {
      clients: McpClient[]
      tools: Tool[]
      commands: Command[]
      resources: McpResource[]
    }
    Bridge / Remote
    TypeScript
    replBridgeState: 'disconnected' | 'connecting' | 'connected' | 'failed'
    replBridgeSessionUrl: string | null
    remoteConnectionStatus: 'disconnected' | 'connecting' | 'connected'
    remoteSessionUrl: string | null
    remoteBackgroundTaskCount: number
    Permissions
    TypeScript
    toolPermissionContext: ToolPermissionContext
    denialTracking: Map<string, number>
    Companion (Buddy)
    TypeScript
    companionReaction: CompanionReaction | null
    companionPetAt: number | null
    Tmux / Tungsten
    TypeScript
    tungstenActiveSession: string | null
    tungstenPanelVisible: boolean
    tungstenPanelAutoHidden: boolean
    tungstenLastCommand: string | null
    Web Browser
    TypeScript
    bagelActive: boolean
    bagelUrl: string | null
    bagelPanelVisible: boolean
    Computer Use
    TypeScript
    computerUseMcpState: ComputerUseState | null
    Notifications
    TypeScript
    notifications: Notification[]
    elicitation: ElicitationState
    Communication (Swarms)
    TypeScript
    inbox: Message[]
    teamContext: TeamContext | null
    standaloneAgentContext: AgentContext | null
    React Hooks
    TypeScript
    // Subscribe to a slice of state
    const model = useAppState(s => s.mainLoopModel)
    
    // Get the setState function
    const setState = useSetAppState()
    
    // Get the full store (for non-React code)
    const store = useAppStateStore()
    
    // Safe version (works outside provider)
    const value = useAppStateMaybeOutsideOfProvider(s => s.field)

    Critical rule: Selectors must return existing references, NOT create new objects:

    TypeScript
    // ✅ GOOD — returns existing reference
    const { text } = useAppState(s => s.promptSuggestion)
    
    // ❌ BAD — creates new object every render → infinite re-renders
    const data = useAppState(s => ({ text: s.promptSuggestion.text }))
    Side Effects (onChangeAppState.ts)

    The onChange callback handles cross-cutting concerns:

    State ChangeSide Effect
    toolPermissionContext.modeNotify CCR (remote daemon), notify SDK listeners
    mainLoopModel set/clearedSave to settings, set/clear model override
    expandedView changedPersist showExpandedTodos and showSpinnerTree to disk
    verbose changedPersist to global config
    tungstenPanelVisible changedPersist to global config (ant-only)
    settings changedClear auth caches, re-apply env variables

    This is the single choke point for state-change side effects, ensuring consistency across all mutation paths.

    Selectors (selectors.ts)

    Pure functions that derive computed state:

    TypeScript
    // Look up the teammate task being viewed
    getViewedTeammateTask({ viewingAgentTaskId, tasks })
    
    // Determine where user input should be routed
    getActiveAgentForInput(appState)
    // Returns: { type: 'leader' } | { type: 'viewed', task } | { type: 'named_agent', name }

    Default State Factory

    getDefaultAppState() creates the initial state:

    TypeScript
    function getDefaultAppState(): AppState {
      return {
        settings: getInitialSettings(),
        mainLoopModel: getDefaultMainLoopModel(),
        thinkingEnabled: shouldEnableThinkingByDefault(),
        promptSuggestionEnabled: shouldEnablePromptSuggestion(),
        tasks: [],
        mcp: { clients: [], tools: [], commands: [], resources: [] },
        plugins: { enabled: new Set(), disabled: new Set(), errors: [], ... },
        replBridgeState: 'disconnected',
        // ... many more defaults
      }
    }

    Uses lazy require() to avoid circular dependencies.


    Settings Change Handling

    When settings change (from UI, file watcher, or remote sync):

    Plain text
    Settings file changes
        │
        ▼
    useSettingsChange(callback)
        │
        ▼
    applySettingsChange(appState, newSettings)
        │
        ▼
    store.setState(prev => ({
      ...prev,
      settings: newSettings,
      // Derived state updates...
    }))
        │
        ▼
    onChangeAppState fires
        ├─ Clear auth caches
        ├─ Re-apply environment variables
        └─ Notify external systems

    Key Files Reference

    FilePurpose
    src/bootstrap/state.tsGlobal singleton state (session ID, cost, model, etc.)
    src/state/store.tsGeneric store implementation
    src/state/AppStateStore.tsAppState type and default state factory
    src/state/AppState.tsxReact provider and hooks
    src/state/onChangeAppState.tsSide effects on state change
    src/state/selectors.tsComputed state selectors
    src/state/teammateViewHelpers.tsTeammate view helper functions