Module: ClaudeAgentSDK

Defined in:
lib/claude_agent_sdk.rb,
lib/claude_agent_sdk/query.rb,
lib/claude_agent_sdk/types.rb,
lib/claude_agent_sdk/errors.rb,
lib/claude_agent_sdk/version.rb,
lib/claude_agent_sdk/observer.rb,
lib/claude_agent_sdk/sessions.rb,
lib/claude_agent_sdk/streaming.rb,
lib/claude_agent_sdk/transport.rb,
lib/claude_agent_sdk/configuration.rb,
lib/claude_agent_sdk/session_store.rb,
lib/claude_agent_sdk/fiber_boundary.rb,
lib/claude_agent_sdk/message_parser.rb,
lib/claude_agent_sdk/sdk_mcp_server.rb,
lib/claude_agent_sdk/session_resume.rb,
lib/claude_agent_sdk/command_builder.rb,
lib/claude_agent_sdk/session_summary.rb,
lib/claude_agent_sdk/session_mutations.rb,
lib/claude_agent_sdk/instrumentation/otel.rb,
lib/claude_agent_sdk/subprocess_cli_transport.rb,
lib/claude_agent_sdk/transcript_mirror_batcher.rb,
lib/claude_agent_sdk/testing/session_store_conformance.rb

Overview

Claude Agent SDK for Ruby

Defined Under Namespace

Modules: FiberBoundary, Instrumentation, Observer, SessionMutations, SessionResume, SessionStores, SessionSummary, Sessions, Streaming, Testing Classes: APIRetryMessage, AgentDefinition, AssistantMessage, AsyncHookJSONOutput, AuthStatusMessage, BaseHookInput, CLIConnectionError, CLIJSONDecodeError, CLINotFoundError, ClaudeAgentOptions, ClaudeSDKError, Client, CommandBuilder, CompactBoundaryMessage, CompactMetadata, ConfigChangeHookInput, Configuration, ControlRequestTimeoutError, CwdChangedHookInput, CwdChangedHookSpecificOutput, DeferredToolUse, ElicitationCompleteMessage, ElicitationHookInput, ElicitationResultHookInput, FileChangedHookInput, FileChangedHookSpecificOutput, FilesPersistedMessage, ForkSessionResult, HookContext, HookMatcher, HookProgressMessage, HookResponseMessage, HookStartedMessage, InMemorySessionStore, InitMessage, InstructionsLoadedHookInput, LocalCommandOutputMessage, MaterializedResume, McpClaudeAIProxyServerConfig, McpHttpServerConfig, McpSSEServerConfig, McpSdkServerConfig, McpSdkServerConfigStatus, McpServerInfo, McpServerStatus, McpStatusResponse, McpStdioServerConfig, McpToolAnnotations, McpToolInfo, MessageParseError, MessageParser, MirrorErrorMessage, NotificationHookInput, NotificationHookSpecificOutput, PermissionDeniedHookInput, PermissionDeniedHookSpecificOutput, PermissionRequestHookInput, PermissionRequestHookSpecificOutput, PermissionResultAllow, PermissionResultDeny, PermissionRuleValue, PermissionUpdate, PostCompactHookInput, PostToolUseFailureHookInput, PostToolUseFailureHookSpecificOutput, PostToolUseHookInput, PostToolUseHookSpecificOutput, PreCompactHookInput, PreToolUseHookInput, PreToolUseHookSpecificOutput, ProcessError, PromptSuggestionMessage, Query, RateLimitEvent, RateLimitInfo, ResultMessage, SDKSessionInfo, SandboxFilesystemConfig, SandboxNetworkConfig, SandboxSettings, SdkMcpPrompt, SdkMcpResource, SdkMcpServer, SdkMcpTool, SdkPluginConfig, ServerToolResultBlock, ServerToolUseBlock, SessionEndHookInput, SessionMessage, SessionStartHookInput, SessionStartHookSpecificOutput, SessionStateChangedMessage, SessionStore, SetupHookInput, SetupHookSpecificOutput, StatusMessage, StopFailureHookInput, StopHookInput, StreamEvent, SubagentStartHookInput, SubagentStartHookSpecificOutput, SubagentStopHookInput, SubprocessCLITransport, SyncHookJSONOutput, SystemMessage, SystemPromptFile, SystemPromptPreset, TaskBudget, TaskCompletedHookInput, TaskCreatedHookInput, TaskNotificationMessage, TaskProgressMessage, TaskStartedMessage, TaskUpdatedMessage, TaskUsage, TeammateIdleHookInput, TextBlock, ThinkingBlock, ThinkingConfigAdaptive, ThinkingConfigDisabled, ThinkingConfigEnabled, ToolPermissionContext, ToolProgressMessage, ToolResultBlock, ToolUseBlock, ToolUseSummaryMessage, ToolsPreset, TranscriptMirrorBatcher, Transport, Type, UnknownBlock, UnknownHookInput, UserMessage, UserPromptSubmitHookInput, UserPromptSubmitHookSpecificOutput, WorktreeCreateHookInput, WorktreeRemoveHookInput

Constant Summary collapse

OBSERVER_INTERFACE =

The duck-typed observer surface probed by resolve_observers — implementing any one of these counts as an observer (see Observer's no-op defaults).

%i[on_user_prompt on_message on_error on_close].freeze
PERMISSION_MODES =

Type constants for permission modes

%w[default acceptEdits plan bypassPermissions dontAsk auto].freeze
SETTING_SOURCES =

Type constants for setting sources

%w[user project local].freeze
EFFORT_LEVELS =

Effort levels for ClaudeAgentOptions#effort. The CLI (Claude Code 2.1.111+) accepts these values; the set of supported levels is model-dependent (e.g. xhigh is only supported on Opus 4.7 and falls back to high on Opus 4.6 / Sonnet 4.6). An Integer is also accepted and forwarded verbatim.

%w[low medium high xhigh max].freeze
PERMISSION_UPDATE_DESTINATIONS =

Type constants for permission update destinations

%w[userSettings projectSettings localSettings session].freeze
PERMISSION_BEHAVIORS =

Type constants for permission behaviors

%w[allow deny ask].freeze
HOOK_EVENTS =

Type constants for hook events

%w[
  PreToolUse
  PostToolUse
  PostToolUseFailure
  Notification
  UserPromptSubmit
  SessionStart
  SessionEnd
  Stop
  StopFailure
  SubagentStart
  SubagentStop
  PreCompact
  PostCompact
  PermissionRequest
  PermissionDenied
  Setup
  TeammateIdle
  TaskCreated
  TaskCompleted
  Elicitation
  ElicitationResult
  ConfigChange
  WorktreeCreate
  WorktreeRemove
  InstructionsLoaded
  CwdChanged
  FileChanged
].freeze
ASSISTANT_MESSAGE_ERRORS =

Type constants for assistant message errors

%w[authentication_failed billing_error rate_limit invalid_request server_error max_output_tokens unknown].freeze
SDK_BETAS =

Type constants for SDK beta features Available beta features that can be enabled via the betas option

%w[context-1m-2025-08-07].freeze
TASK_NOTIFICATION_STATUSES =

Task lifecycle notification statuses

%w[completed failed stopped].freeze
TASK_UPDATED_STATUSES =

Possible status values reported inside a task_updated patch. pending/running/paused are non-terminal; completed/failed/killed are terminal. Note: task_updated reports the raw "killed"; the CLI maps that to "stopped" only when it emits a task_notification.

%w[pending running paused completed failed killed].freeze
TERMINAL_TASK_STATUSES =

Task statuses that mean the task has finished and should be cleared from any "active task" tracking. Spans both lifecycle vocabularies: task_notification reports "stopped" (the CLI's mapped form of a killed task) while task_updated reports the raw "killed". Treat the status of a TaskNotificationMessage and a TaskUpdatedMessage the same way.

%w[completed failed stopped killed].freeze
RATE_LIMIT_STATUSES =

Type constants for rate limit statuses

%w[allowed allowed_warning rejected].freeze
RATE_LIMIT_TYPES =

Type constants for rate limit types

%w[five_hour seven_day seven_day_opus seven_day_sonnet overage].freeze
THINKING_DISPLAY_VALUES =

Thinking configuration types

display controls how thinking content appears in responses. Valid values are "summarized" (plaintext summary) and "omitted" (empty thinking field, signature only). Defaults are model-dependent: Opus 4.6/Sonnet 4.6 default to "summarized"; Opus 4.7 and Mythos Preview default to "omitted". Pass display: "summarized" explicitly on Opus 4.7 to get visible thinking text. Not supported with ThinkingConfigDisabled.

%w[summarized omitted].freeze
MCP_SERVER_CONNECTION_STATUSES =

MCP server connection status values

%w[connected failed needs-auth pending disabled].freeze
VERSION =
'0.22.0'
SESSION_STORE_FLUSH_MODES =

Controls when transcript-mirror entries are flushed to a SessionStore.

  • "batched" (default): buffer entries and flush once per turn (on the result message) or when the pending buffer exceeds 500 entries / 1 MiB.
  • "eager": trigger a background flush after every transcript_mirror frame so SessionStore#append sees entries in near real time.
%w[batched eager].freeze

Class Method Summary collapse

Class Method Details

.configurationConfiguration

Get the configuration object

Returns:



58
59
60
# File 'lib/claude_agent_sdk/configuration.rb', line 58

def configuration
  @configuration ||= Configuration.new
end

.configure {|Configuration| ... } ⇒ Object

Configure the SDK with default options

Examples:

Set default env and other options

ClaudeAgentSDK.configure do |config|
  config.default_options = {
    env: { 'API_KEY' => 'xxx' },
    permission_mode: 'bypassPermissions'
  }
end

Yields:



51
52
53
# File 'lib/claude_agent_sdk/configuration.rb', line 51

def configure
  yield(configuration)
end

.create_prompt(name:, description: nil, arguments: nil, &generator) ⇒ SdkMcpPrompt

Helper function to create a prompt definition

Examples:

Simple prompt

prompt = create_prompt(
  name: 'code_review',
  description: 'Review code for best practices'
) do |args|
  {
    messages: [
      {
        role: 'user',
        content: {
          type: 'text',
          text: 'Please review this code for best practices and suggest improvements.'
        }
      }
    ]
  }
end

Prompt with arguments

prompt = create_prompt(
  name: 'git_commit',
  description: 'Generate a git commit message',
  arguments: [
    { name: 'changes', description: 'Description of changes', required: true }
  ]
) do |args|
  {
    messages: [
      {
        role: 'user',
        content: {
          type: 'text',
          text: "Generate a concise git commit message for: #{args[:changes]}"
        }
      }
    ]
  }
end

Parameters:

  • name (String)

    Unique identifier for the prompt

  • description (String, nil) (defaults to: nil)

    Optional description

  • arguments (Array<Hash>, nil) (defaults to: nil)

    Optional argument definitions

  • generator (Proc)

    Block that generates prompt messages

Returns:

Raises:

  • (ArgumentError)


594
595
596
597
598
599
600
601
602
603
# File 'lib/claude_agent_sdk/sdk_mcp_server.rb', line 594

def self.create_prompt(name:, description: nil, arguments: nil, &generator)
  raise ArgumentError, 'Block required for prompt generator' unless generator

  SdkMcpPrompt.new(
    name: name,
    description: description,
    arguments: arguments,
    generator: generator
  )
end

.create_resource(uri:, name:, description: nil, mime_type: nil, &reader) ⇒ SdkMcpResource

Helper function to create a resource definition

Examples:

File resource

resource = create_resource(
  uri: 'file:///config/settings.json',
  name: 'Application Settings',
  description: 'Current application configuration',
  mime_type: 'application/json'
) do
  content = File.read('/path/to/settings.json')
  {
    contents: [{
      uri: 'file:///config/settings.json',
      mimeType: 'application/json',
      text: content
    }]
  }
end

Database resource

resource = create_resource(
  uri: 'db://users/count',
  name: 'User Count',
  description: 'Total number of registered users'
) do
  count = User.count
  {
    contents: [{
      uri: 'db://users/count',
      mimeType: 'text/plain',
      text: count.to_s
    }]
  }
end

Parameters:

  • uri (String)

    Unique identifier for the resource (e.g., "file:///path/to/file")

  • name (String)

    Human-readable name

  • description (String, nil) (defaults to: nil)

    Optional description

  • mime_type (String, nil) (defaults to: nil)

    Optional MIME type (e.g., "text/plain", "application/json")

  • reader (Proc)

    Block that returns the resource content

Returns:

Raises:

  • (ArgumentError)


536
537
538
539
540
541
542
543
544
545
546
# File 'lib/claude_agent_sdk/sdk_mcp_server.rb', line 536

def self.create_resource(uri:, name:, description: nil, mime_type: nil, &reader)
  raise ArgumentError, 'Block required for resource reader' unless reader

  SdkMcpResource.new(
    uri: uri,
    name: name,
    description: description,
    mime_type: mime_type,
    reader: reader
  )
end

.create_sdk_mcp_server(name:, version: '1.0.0', tools: [], resources: [], prompts: []) ⇒ Hash

Create an SDK MCP server

Examples:

Simple calculator server

add_tool = ClaudeAgentSDK.create_tool('add', 'Add numbers', { a: :number, b: :number }) do |args|
  { content: [{ type: 'text', text: "Sum: #{args[:a] + args[:b]}" }] }
end

calculator = ClaudeAgentSDK.create_sdk_mcp_server(
  name: 'calculator',
  version: '2.0.0',
  tools: [add_tool]
)

options = ClaudeAgentOptions.new(
  mcp_servers: { calc: calculator },
  allowed_tools: ['mcp__calc__add']
)

Server with resources and prompts

config_resource = ClaudeAgentSDK.create_resource(
  uri: 'config://app',
  name: 'App Config'
) { { contents: [{ uri: 'config://app', text: 'config data' }] } }

review_prompt = ClaudeAgentSDK.create_prompt(
  name: 'review',
  description: 'Code review'
) { { messages: [{ role: 'user', content: { type: 'text', text: 'Review this' } }] } }

server = ClaudeAgentSDK.create_sdk_mcp_server(
  name: 'dev-tools',
  resources: [config_resource],
  prompts: [review_prompt]
)

Parameters:

  • name (String)

    Unique identifier for the server

  • version (String) (defaults to: '1.0.0')

    Server version (default: '1.0.0')

  • tools (Array<SdkMcpTool>) (defaults to: [])

    List of tool definitions

  • resources (Array<SdkMcpResource>) (defaults to: [])

    List of resource definitions

  • prompts (Array<SdkMcpPrompt>) (defaults to: [])

    List of prompt definitions

Returns:

  • (Hash)

    MCP server configuration for ClaudeAgentOptions



646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
# File 'lib/claude_agent_sdk/sdk_mcp_server.rb', line 646

def self.create_sdk_mcp_server(name:, version: '1.0.0', tools: [], resources: [], prompts: [])
  server = SdkMcpServer.new(
    name: name,
    version: version,
    tools: tools,
    resources: resources,
    prompts: prompts
  )

  # Return configuration for ClaudeAgentOptions
  {
    type: 'sdk',
    name: name,
    instance: server
  }
end

.create_tool(name, description, input_schema, annotations: nil, meta: nil, &handler) ⇒ SdkMcpTool

Helper function to create a tool definition

Examples:

Simple tool

tool = create_tool('greet', 'Greet a user', { name: :string }) do |args|
  { content: [{ type: 'text', text: "Hello, #{args[:name]}!" }] }
end

Tool with multiple parameters

tool = create_tool('add', 'Add two numbers', { a: :number, b: :number }) do |args|
  result = args[:a] + args[:b]
  { content: [{ type: 'text', text: "Result: #{result}" }] }
end

Tool with error handling

tool = create_tool('divide', 'Divide numbers', { a: :number, b: :number }) do |args|
  if args[:b] == 0
    { content: [{ type: 'text', text: 'Error: Division by zero' }], is_error: true }
  else
    result = args[:a] / args[:b]
    { content: [{ type: 'text', text: "Result: #{result}" }] }
  end
end

Parameters:

  • name (String)

    Unique identifier for the tool

  • description (String)

    Human-readable description

  • input_schema (Hash)

    Schema defining input parameters

  • handler (Proc)

    Block that implements the tool logic

Returns:

Raises:

  • (ArgumentError)


470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/claude_agent_sdk/sdk_mcp_server.rb', line 470

def self.create_tool(name, description, input_schema, annotations: nil, meta: nil, &handler)
  raise ArgumentError, 'Block required for tool handler' unless handler

  # Auto-populate _meta with maxResultSizeChars from annotations if present
  resolved_meta = meta
  if resolved_meta.nil? && annotations
    max_chars = annotations[:maxResultSizeChars] || annotations['maxResultSizeChars']
    resolved_meta = { 'anthropic/maxResultSizeChars' => max_chars } if max_chars
  end

  # tools/call arrives from the CLI with the name as a JSON String; the mcp
  # gem keys its lookup on the value stored here, so a Symbol name would be
  # advertised in tools/list yet miss on every invocation ('Tool not found').
  name = name.to_s if name.is_a?(Symbol)

  SdkMcpTool.new(
    name: name,
    description: description,
    input_schema: input_schema,
    handler: handler,
    annotations: annotations,
    meta: resolved_meta
  )
end

.deep_normalize_schema(obj) ⇒ Object

Like deep_symbolize_keys, but also converts Symbol VALUES to strings so a prebuilt schema written with symbols ({ type: :object, ... }) emits clean wire-format JSON Schema.



18
19
20
21
22
23
24
25
# File 'lib/claude_agent_sdk/sdk_mcp_server.rb', line 18

def self.deep_normalize_schema(obj)
  case obj
  when Hash then obj.transform_keys(&:to_sym).transform_values { |v| deep_normalize_schema(v) }
  when Array then obj.map { |v| deep_normalize_schema(v) }
  when Symbol then obj.to_s
  else obj
  end
end

.deep_symbolize_keys(obj) ⇒ Object

Recursively convert all hash keys to symbols



7
8
9
10
11
12
13
# File 'lib/claude_agent_sdk/sdk_mcp_server.rb', line 7

def self.deep_symbolize_keys(obj)
  case obj
  when Hash then obj.transform_keys(&:to_sym).transform_values { |v| deep_symbolize_keys(v) }
  when Array then obj.map { |v| deep_symbolize_keys(v) }
  else obj
  end
end

.default_optionsHash

Get merged default options for use with ClaudeAgentOptions

Returns:

  • (Hash)

    Default options hash



70
71
72
# File 'lib/claude_agent_sdk/configuration.rb', line 70

def default_options
  configuration.default_options || {}
end

.delete_session(session_id:, directory: nil) ⇒ Object

Delete a session by removing its JSONL file (hard delete).

Parameters:

  • session_id (String)

    UUID of the session to delete

  • directory (String, nil) (defaults to: nil)

    Project directory path



239
240
241
# File 'lib/claude_agent_sdk.rb', line 239

def self.delete_session(session_id:, directory: nil)
  SessionMutations.delete_session(session_id: session_id, directory: directory)
end

.delete_session_via_store(session_store:, session_id:, directory: nil) ⇒ Object

Delete a session from a SessionStore (store-backed counterpart to delete_session). No-op when the store does not implement #delete (WORM/append-only backends).

Raises:

  • (ArgumentError)

    if session_id is invalid



329
330
331
332
# File 'lib/claude_agent_sdk.rb', line 329

def self.delete_session_via_store(session_store:, session_id:, directory: nil)
  SessionMutations.delete_session_via_store(session_store: session_store, session_id: session_id,
                                            directory: directory)
end

.extract_exclude_dynamic_sections(system_prompt) ⇒ Object

Internal: pull exclude_dynamic_sections out of a preset system prompt for the initialize request (older CLIs ignore unknown initialize fields). Shared by Client#connect and the one-shot query() path.



68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/claude_agent_sdk.rb', line 68

def self.extract_exclude_dynamic_sections(system_prompt)
  if system_prompt.is_a?(SystemPromptPreset)
    eds = system_prompt.exclude_dynamic_sections
    return eds if [true, false].include?(eds)
  elsif system_prompt.is_a?(Hash)
    type = system_prompt[:type] || system_prompt['type']
    if type == 'preset'
      eds = system_prompt.fetch(:exclude_dynamic_sections) { system_prompt['exclude_dynamic_sections'] }
      return eds if [true, false].include?(eds)
    end
  end
  nil
end

.extract_sdk_mcp_servers(mcp_servers) ⇒ Object

Internal: pull live SDK MCP server instances out of an mcp_servers Hash. Accepts both raw Hash configs and typed Mcp*ServerConfig objects — a McpSdkServerConfig passed without .to_h previously failed the Hash-only guard, so its in-process server was silently never registered.



54
55
56
57
58
59
60
61
62
63
# File 'lib/claude_agent_sdk.rb', line 54

def self.extract_sdk_mcp_servers(mcp_servers)
  return {} unless mcp_servers.is_a?(Hash)

  servers = {}
  mcp_servers.each do |name, config|
    config = config.to_h if config.is_a?(Type)
    servers[name] = config[:instance] if config.is_a?(Hash) && config[:type] == 'sdk'
  end
  servers
end

.extract_user_prompt_text(message) ⇒ Object

Extract the user-visible prompt text from a streamed input item, or nil when there is none (non-user messages, tool_result-only content, …). Only Hash and JSON-string items are inspected; arbitrary objects written via to_s are never notified.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/claude_agent_sdk.rb', line 101

def self.extract_user_prompt_text(message)
  data = case message
         when Hash then message
         when String
           # Cheap prefilter: skip the full parse for items that cannot be
           # user messages (e.g. multi-MB tool_result frames) — parsing
           # would block the reactor fiber for the duration. False
           # positives just cost one parse; correctness is unchanged.
           return nil unless message.include?('user')

           begin
             JSON.parse(message)
           rescue JSON::ParserError
             nil
           end
         end
  return nil unless data.is_a?(Hash)
  return nil unless (data[:type] || data['type']) == 'user'

  inner = data[:message] || data['message']
  return nil unless inner.is_a?(Hash)

  prompt_text_from_content(inner[:content] || inner['content'])
end

.flexible_fetch(hash, camel_key, snake_key) ⇒ Object

Look up a value in a hash that may use symbol or string keys in camelCase or snake_case. Returns the first non-nil value found, preserving false as a meaningful value.



164
165
166
167
168
169
170
# File 'lib/claude_agent_sdk.rb', line 164

def self.flexible_fetch(hash, camel_key, snake_key)
  val = hash[camel_key.to_sym]
  val = hash[camel_key.to_s] if val.nil?
  val = hash[snake_key.to_sym] if val.nil?
  val = hash[snake_key.to_s] if val.nil?
  val
end

.fold_session_summary(prev, key, entries) ⇒ Hash

Fold a batch of appended transcript entries into a running session summary. SessionStore adapters call this inside #append to maintain a summary sidecar incrementally (see SessionStore#list_session_summaries).

Parameters:

  • prev (Hash, nil)

    previous summary entry for this key

  • key (Hash)

    the SessionKey (string keys)

  • entries (Array<Hash>)

    newly appended transcript entries

Returns:

  • (Hash)

    the updated summary entry



270
271
272
# File 'lib/claude_agent_sdk.rb', line 270

def self.fold_session_summary(prev, key, entries)
  SessionSummary.fold_session_summary(prev, key, entries)
end

.fork_session(session_id:, directory: nil, up_to_message_id: nil, title: nil) ⇒ ForkSessionResult

Fork a session into a new branch with fresh UUIDs.

Parameters:

  • session_id (String)

    UUID of the session to fork

  • directory (String, nil) (defaults to: nil)

    Project directory path

  • up_to_message_id (String, nil) (defaults to: nil)

    Truncate the fork at this message UUID

  • title (String, nil) (defaults to: nil)

    Custom title for the fork

Returns:



249
250
251
252
# File 'lib/claude_agent_sdk.rb', line 249

def self.fork_session(session_id:, directory: nil, up_to_message_id: nil, title: nil)
  SessionMutations.fork_session(session_id: session_id, directory: directory,
                                up_to_message_id: up_to_message_id, title: title)
end

.fork_session_via_store(session_store:, session_id:, directory: nil, up_to_message_id: nil, title: nil) ⇒ ForkSessionResult

Fork a session in a SessionStore into a new branch with fresh UUIDs (store-backed counterpart to fork_session).

Returns:

Raises:

  • (ArgumentError)

    if session_id/up_to_message_id is invalid or there are no messages

  • (Errno::ENOENT)

    if the source session is not found in the store



339
340
341
342
# File 'lib/claude_agent_sdk.rb', line 339

def self.fork_session_via_store(session_store:, session_id:, directory: nil, up_to_message_id: nil, title: nil)
  SessionMutations.fork_session_via_store(session_store: session_store, session_id: session_id,
                                          directory: directory, up_to_message_id: up_to_message_id, title: title)
end

.get_session_info(session_id:, directory: nil) ⇒ SDKSessionInfo?

Read metadata for a single session by ID (no full directory scan)

Parameters:

  • session_id (String)

    UUID of the session to look up

  • directory (String, nil) (defaults to: nil)

    Project directory path

Returns:



186
187
188
# File 'lib/claude_agent_sdk.rb', line 186

def self.get_session_info(session_id:, directory: nil)
  Sessions.get_session_info(session_id: session_id, directory: directory)
end

.get_session_info_from_store(session_store:, session_id:, directory: nil) ⇒ SDKSessionInfo?

Read metadata for a single session from a SessionStore.

Returns:



283
284
285
# File 'lib/claude_agent_sdk.rb', line 283

def self.get_session_info_from_store(session_store:, session_id:, directory: nil)
  Sessions.get_session_info_from_store(session_store: session_store, session_id: session_id, directory: directory)
end

.get_session_messages(session_id:, directory: nil, limit: nil, offset: 0) ⇒ Array<SessionMessage>

Get messages from a session transcript

Parameters:

  • session_id (String)

    The session UUID

  • directory (String, nil) (defaults to: nil)

    Working directory to search in

  • limit (Integer, nil) (defaults to: nil)

    Maximum number of messages

  • offset (Integer) (defaults to: 0)

    Number of messages to skip

Returns:



196
197
198
# File 'lib/claude_agent_sdk.rb', line 196

def self.get_session_messages(session_id:, directory: nil, limit: nil, offset: 0)
  Sessions.get_session_messages(session_id: session_id, directory: directory, limit: limit, offset: offset)
end

.get_session_messages_from_store(session_store:, session_id:, directory: nil, limit: nil, offset: 0) ⇒ Array<SessionMessage>

Read a session's conversation messages from a SessionStore.

Returns:



289
290
291
292
# File 'lib/claude_agent_sdk.rb', line 289

def self.get_session_messages_from_store(session_store:, session_id:, directory: nil, limit: nil, offset: 0)
  Sessions.get_session_messages_from_store(session_store: session_store, session_id: session_id,
                                           directory: directory, limit: limit, offset: offset)
end

.get_subagent_messages(session_id:, agent_id:, directory: nil, limit: nil, offset: 0) ⇒ Array<SessionMessage>

Read a subagent's conversation messages from local disk

Parameters:

  • session_id (String)

    The session UUID

  • agent_id (String)

    The subagent ID (without the agent- prefix)

  • directory (String, nil) (defaults to: nil)

    Working directory to search in

  • limit (Integer, nil) (defaults to: nil)

    Maximum number of messages

  • offset (Integer) (defaults to: 0)

    Number of messages to skip

Returns:



215
216
217
218
# File 'lib/claude_agent_sdk.rb', line 215

def self.get_subagent_messages(session_id:, agent_id:, directory: nil, limit: nil, offset: 0)
  Sessions.get_subagent_messages(session_id: session_id, agent_id: agent_id,
                                 directory: directory, limit: limit, offset: offset)
end

.get_subagent_messages_from_store(session_store:, session_id:, agent_id:, directory: nil, limit: nil, offset: 0) ⇒ Array<SessionMessage>

Read a subagent's conversation messages from a SessionStore.

Returns:



302
303
304
305
306
# File 'lib/claude_agent_sdk.rb', line 302

def self.get_subagent_messages_from_store(session_store:, session_id:, agent_id:, directory: nil, limit: nil,
                                          offset: 0)
  Sessions.get_subagent_messages_from_store(session_store: session_store, session_id: session_id,
                                            agent_id: agent_id, directory: directory, limit: limit, offset: offset)
end

.import_session_to_store(session_id:, session_store:, directory: nil, include_subagents: true, batch_size: TranscriptMirrorBatcher::MAX_PENDING_ENTRIES) ⇒ Object

Replay a local on-disk session transcript into a SessionStore (migration / gap-backfill). Keys under the on-disk project dir so the imported session is resumable via session_store + resume from the original cwd.

Raises:

  • (ArgumentError)

    if session_id is not a valid UUID

  • (Errno::ENOENT)

    if the session JSONL cannot be found



349
350
351
352
353
# File 'lib/claude_agent_sdk.rb', line 349

def self.import_session_to_store(session_id:, session_store:, directory: nil, include_subagents: true,
                                 batch_size: TranscriptMirrorBatcher::MAX_PENDING_ENTRIES)
  Sessions.import_session_to_store(session_id: session_id, session_store: session_store, directory: directory,
                                   include_subagents: include_subagents, batch_size: batch_size)
end

.list_sessions(directory: nil, limit: nil, offset: 0, include_worktrees: true) ⇒ Array<SDKSessionInfo>

List sessions for a directory (or all sessions)

Parameters:

  • directory (String, nil) (defaults to: nil)

    Working directory to list sessions for

  • limit (Integer, nil) (defaults to: nil)

    Maximum number of sessions to return

  • offset (Integer) (defaults to: 0)

    Number of sessions to skip (for pagination)

  • include_worktrees (Boolean) (defaults to: true)

    Whether to include git worktree sessions

Returns:

  • (Array<SDKSessionInfo>)

    Sessions sorted by last_modified descending



178
179
180
# File 'lib/claude_agent_sdk.rb', line 178

def self.list_sessions(directory: nil, limit: nil, offset: 0, include_worktrees: true)
  Sessions.list_sessions(directory: directory, limit: limit, offset: offset, include_worktrees: include_worktrees)
end

.list_sessions_from_store(session_store:, directory: nil, limit: nil, offset: 0) ⇒ Array<SDKSessionInfo>

List sessions from a SessionStore (store-backed counterpart to list_sessions).

Parameters:

Returns:



277
278
279
# File 'lib/claude_agent_sdk.rb', line 277

def self.list_sessions_from_store(session_store:, directory: nil, limit: nil, offset: 0)
  Sessions.list_sessions_from_store(session_store: session_store, directory: directory, limit: limit, offset: offset)
end

.list_subagents(session_id:, directory: nil) ⇒ Array<String>

List subagent IDs recorded for a session on local disk

Parameters:

  • session_id (String)

    The session UUID

  • directory (String, nil) (defaults to: nil)

    Working directory to search in

Returns:

  • (Array<String>)

    Subagent IDs



204
205
206
# File 'lib/claude_agent_sdk.rb', line 204

def self.list_subagents(session_id:, directory: nil)
  Sessions.list_subagents(session_id: session_id, directory: directory)
end

.list_subagents_from_store(session_store:, session_id:, directory: nil) ⇒ Array<String>

List subagent IDs for a session from a SessionStore (requires list_subkeys).

Returns:

  • (Array<String>)


296
297
298
# File 'lib/claude_agent_sdk.rb', line 296

def self.list_subagents_from_store(session_store:, session_id:, directory: nil)
  Sessions.list_subagents_from_store(session_store: session_store, session_id: session_id, directory: directory)
end

.normalize_tool_schema(schema) ⇒ Object

Single source of truth for tool input schemas: prebuilt schemas are normalized (symbol keys, string values); simple { name: :type } hashes become a full JSON Schema with every param required (string keys).



50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/claude_agent_sdk/sdk_mcp_server.rb', line 50

def self.normalize_tool_schema(schema)
  return deep_normalize_schema(schema) if prebuilt_json_schema?(schema)

  if schema.is_a?(Hash)
    properties = schema.to_h { |param, type| [param.to_sym, ruby_type_to_json_schema(type)] }
    result = { type: 'object', properties: properties }
    result[:required] = properties.keys.map(&:to_s) unless properties.empty?
    return result
  end

  { type: 'object', properties: {} }
end

.notify_observers(observers, method, *args) ⇒ Object

Safely call a method on each observer, suppressing any errors. Each observer is invoked through FiberBoundary so that user code runs on a plain thread (no Fiber scheduler) even when called from inside the SDK's Async reactor.



86
87
88
89
90
91
92
93
94
95
# File 'lib/claude_agent_sdk.rb', line 86

def self.notify_observers(observers, method, *args)
  observers.each do |obs|
    FiberBoundary.invoke { obs.send(method, *args) }
  rescue StandardError, ScriptError
    # ScriptError too: NotImplementedError < ScriptError (not
    # StandardError), and a stubbed observer must never mask the original
    # error being notified or abort connect/teardown cleanup.
    nil
  end
end

.observing_prompt_stream(prompt, observers) ⇒ Object

Wrap a streaming-input enumerable so observers get on_user_prompt for each user message before it is written to stdin. Identity when no observers are configured.



150
151
152
153
154
155
156
157
158
159
160
# File 'lib/claude_agent_sdk.rb', line 150

def self.observing_prompt_stream(prompt, observers)
  return prompt if observers.empty?

  Enumerator.new do |yielder|
    prompt.each do |message|
      text = extract_user_prompt_text(message)
      notify_observers(observers, :on_user_prompt, text) if text
      yielder << message
    end
  end
end

.prebuilt_json_schema?(schema) ⇒ Boolean

A prebuilt JSON Schema is detected by type == 'object' (String or Symbol) with properties either ABSENT or a Hash. Properties-present-but-not-Hash falls through to the simple idiom, which uses Symbols as type VALUES — { type: :string, properties: :string } is a legal simple schema with params literally named type/properties. Propertyless object schemas ({ type: 'object', additionalProperties: ... } / oneOf / empty-object accept-anything forms) previously fell into the simple branch and were mangled into nonsense parameter lists ("additionalProperties" as a required string param). A $ref-only schema without type: 'object' remains indistinguishable from a params hash — declare the type alongside $ref.

Returns:

  • (Boolean)


37
38
39
40
41
42
43
44
45
# File 'lib/claude_agent_sdk/sdk_mcp_server.rb', line 37

def self.prebuilt_json_schema?(schema)
  return false unless schema.is_a?(Hash)

  type_val = schema[:type] || schema['type']
  return false unless (type_val.is_a?(String) || type_val.is_a?(Symbol)) && type_val.to_s == 'object'
  return true unless schema.key?(:properties) || schema.key?('properties')

  (schema[:properties] || schema['properties']).is_a?(Hash)
end

.project_key_for_directory(directory = nil) ⇒ String

Derive the SessionStore project_key for a directory (default: cwd). Matches the CLI's project-directory naming so keys align between local-disk and store-mirrored transcripts.

Parameters:

  • directory (String, Pathname, nil) (defaults to: nil)

    Directory to key (nil = cwd)

Returns:

  • (String)

    The project key



259
260
261
# File 'lib/claude_agent_sdk.rb', line 259

def self.project_key_for_directory(directory = nil)
  Sessions.project_key_for_directory(directory)
end

.prompt_text_from_content(content) ⇒ Object

Text from a user-message content payload: the string itself, or the newline-joined non-empty top-level text blocks. Returns nil (never '') when there is no extractable text — on_user_prompt('') would latch OTelObserver's first-prompt buffer while never setting the attribute, permanently suppressing later real prompts.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/claude_agent_sdk.rb', line 131

def self.prompt_text_from_content(content)
  case content
  when String
    content.empty? ? nil : content
  when Array
    texts = content.filter_map do |block|
      next unless block.is_a?(Hash)
      next unless (block[:type] || block['type']) == 'text'

      text = block[:text] || block['text']
      text unless text.to_s.empty?
    end
    texts.empty? ? nil : texts.join("\n")
  end
end

.query(prompt:, options: nil, transport: nil) {|Message| ... } ⇒ Enumerator

Note:

An attempted #next may still spawn the CLI subprocess before failing and leaves the query unusable.

Query Claude Code for one-shot or unidirectional streaming interactions

This function is ideal for simple, stateless queries where you don't need bidirectional communication or conversation management.

Examples:

Simple query

ClaudeAgentSDK.query(prompt: "What is 2 + 2?") do |message|
  puts message
end

With options

options = ClaudeAgentSDK::ClaudeAgentOptions.new(
  allowed_tools: ['Read', 'Bash'],
  permission_mode: 'acceptEdits'
)
ClaudeAgentSDK.query(prompt: "Create a hello.rb file", options: options) do |msg|
  puts msg.text if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
end

Streaming input

messages = Streaming.from_array(['Hello', 'What is 2+2?', 'Thanks!'])
ClaudeAgentSDK.query(prompt: messages) do |message|
  puts message
end

Parameters:

  • prompt (String, Enumerator)

    The prompt to send to Claude, or an Enumerator for streaming input

  • options (ClaudeAgentOptions) (defaults to: nil)

    Optional configuration

Yields:

  • (Message)

    Each message from the conversation

Returns:

  • (Enumerator)

    if no block given. Internal iteration only: consume with #each or each-driven Enumerable methods (#first, #take, #map, #to_a). External iteration (#next, #peek, #rewind) is NOT supported — message delivery runs inside the SDK's Async reactor, which cannot run on the Enumerator's fiber; #next raises or hangs depending on context.

Raises:

  • (ArgumentError)


390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/claude_agent_sdk.rb', line 390

def self.query(prompt:, options: nil, transport: nil, &block)
  # Validate BEFORE the block-less enum_for return so a bad prompt fails at
  # the call site, not on first iteration. Mirrors Client#query: a bare Hash
  # responds to #each and would stream [key, value] pairs' to_s garbage to
  # the CLI; nil/Integer would hang forever waiting for input.
  raise ArgumentError, 'prompt must be a String or an Enumerable of message Hashes/JSONL Strings (got Hash)' if prompt.is_a?(Hash)
  raise ArgumentError, "prompt must be a String or respond to #each (got #{prompt.class})" unless prompt.is_a?(String) || prompt.respond_to?(:each)

  return enum_for(:query, prompt: prompt, options: options, transport: transport) unless block

  options ||= ClaudeAgentOptions.new

  configured_options = options
  if options.can_use_tool
    if prompt.is_a?(String)
      raise ArgumentError,
            'can_use_tool callback requires streaming mode. Please provide prompt as an Enumerator instead of a String.'
    end

    raise ArgumentError, 'can_use_tool callback cannot be used with permission_prompt_tool_name' if options.permission_prompt_tool_name

    configured_options = options.dup_with(permission_prompt_tool_name: 'stdio')
  end

  # Fail fast on invalid session_store combinations before spawning the CLI.
  SessionStores.validate_session_store_options(configured_options)

  # Resolve callable observers into fresh instances (thread-safe for global defaults)
  resolved_observers = ClaudeAgentSDK.resolve_observers(configured_options.observers)

  raise ArgumentError, 'transport must respond to #connect (see ClaudeAgentSDK::Transport)' if transport && !transport.respond_to?(:connect)

  Async do
    materialized = nil
    query_handler = nil
    begin
      if transport.nil?
        # Resume-from-store: when a session_store is set and resume/continue
        # is requested, load the session into a temp CLAUDE_CONFIG_DIR and
        # repoint options at it (env + --resume) BEFORE spawning. Returns
        # options unchanged when no materialization applies. Skipped
        # entirely for an injected transport — the materialized
        # env/--resume only apply to the CLI subprocess (Python parity:
        # client.py skips materialization when a transport is supplied).
        materialized = SessionResume.materialize_resume_session(configured_options)
        configured_options = SessionResume.apply_materialized_options(configured_options, materialized) if materialized

        # Always use streaming mode with control protocol (matches Python
        # SDK). This sends agents via initialize request instead of CLI
        # args, avoiding OS ARG_MAX limits.
        transport = SubprocessCLITransport.new(configured_options)
      end
      # Deliberate deviation from Python: the ensure below also closes an
      # injected transport whose #connect raised (Python leaves it
      # unclosed); Transport#close must be idempotent.
      transport.connect

      # Extract SDK MCP servers
      sdk_mcp_servers = extract_sdk_mcp_servers(configured_options.mcp_servers)

      hooks = nil
      if configured_options.hooks
        hooks = {}
        configured_options.hooks.each do |event, matchers|
          next if matchers.nil? || matchers.empty?

          entries = []
          matchers.each do |matcher|
            config = {
              matcher: matcher.matcher,
              hooks: matcher.hooks
            }
            config[:timeout] = matcher.timeout if matcher.timeout
            entries << config
          end
          hooks[event.to_s] = entries unless entries.empty?
        end
        hooks = nil if hooks.empty?
      end

      # Create Query handler for control protocol
      query_handler = Query.new(
        transport: transport,
        is_streaming_mode: true,
        can_use_tool: configured_options.can_use_tool,
        hooks: hooks,
        agents: configured_options.agents,
        sdk_mcp_servers: sdk_mcp_servers,
        exclude_dynamic_sections: ClaudeAgentSDK.extract_exclude_dynamic_sections(configured_options.system_prompt),
        skills: configured_options.skills
      )

      # Mirror transcripts to the session_store, if configured. Installed
      # before #start so the read loop captures transcript_mirror frames.
      if configured_options.session_store
        query_handler.set_transcript_mirror_batcher(
          SessionResume.build_mirror_batcher(
            store: configured_options.session_store,
            env: configured_options.env,
            on_error: ->(key, message) { query_handler.report_mirror_error(key, message) },
            eager: configured_options.session_store_flush.to_s == 'eager'
          )
        )
      end

      # Start reading messages in background
      query_handler.start

      # Initialize the control protocol (sends agents)
      query_handler.initialize_protocol

      # Send prompt(s) as user messages, then close stdin
      if prompt.is_a?(String)
        ClaudeAgentSDK.notify_observers(resolved_observers, :on_user_prompt, prompt)
        message = {
          type: 'user',
          message: { role: 'user', content: prompt },
          parent_tool_use_id: nil,
          session_id: ''
        }
        transport.write(JSON.generate(message) + "\n")
        # Background-spawn so messages stream to the user block while stdin
        # close waits (without timeout) for the first result; a synchronous
        # call would defer all delivery until the turn completes (mirrors
        # Python's query.spawn_task(query.wait_for_result_and_end_input())).
        query_handler.spawn_task { query_handler.wait_for_result_and_end_input }
      elsif prompt.is_a?(Enumerator) || prompt.respond_to?(:each)
        # Tracked on the Query so close() stops it; an untracked Async task
        # here kept the root reactor alive forever when the read loop died
        # while the user enumerator was still blocked (matches Python's
        # query.spawn_task(query.stream_input(prompt))).
        observed_prompt = ClaudeAgentSDK.observing_prompt_stream(prompt, resolved_observers)
        query_handler.spawn_task { query_handler.stream_input(observed_prompt) }
      end

      # Read and yield messages from the query handler (filters out control messages).
      # User block is invoked through FiberBoundary so ActiveRecord / PG calls
      # inside it don't see the async gem's Fiber scheduler.
      query_handler.receive_messages do |data|
        message = MessageParser.parse(data)
        next unless message

        ClaudeAgentSDK.notify_observers(resolved_observers, :on_message, message)
        signal = FiberBoundary.invoke_iteration(block, message)
        break signal.value if signal.is_a?(FiberBoundary::Break)
      end
    rescue StandardError => e
      # One notify point for every error surfacing from query() — transport
      # connect, initialize, stream errors re-raised from the message queue,
      # parse errors, and user-block errors. StandardError only: Async::Stop
      # is cancellation, not an error. Bare raise preserves the backtrace;
      # the ensure below still fires on_close after on_error.
      ClaudeAgentSDK.notify_observers(resolved_observers, :on_error, e)
      raise
    ensure
      ClaudeAgentSDK.notify_observers(resolved_observers, :on_close)
      # query_handler.close stops the background read task and closes the
      # transport (flushing the mirror batcher first). Fall back to a bare
      # transport close when the handler was never built.
      begin
        if query_handler
          query_handler.close
        elsif transport
          transport.close
        end
      ensure
        # Remove the materialized resume temp dir (which holds a redacted
        # .credentials.json copy) AFTER the subprocess has exited, even when
        # close itself raises — unless the mirror dropped batches: the store
        # copy is then incomplete and the temp dir holds the only copy of
        # the dropped turns, so it is preserved (scrubbed of credentials)
        # with a warning instead of deleted.
        if materialized
          query_handler&.mirror_batches_dropped? ? materialized.preserve_transcripts : materialized.cleanup
        end
      end
    end
  end.wait
end

.rename_session(session_id:, title:, directory: nil) ⇒ Object

Rename a session by appending a custom-title entry

Parameters:

  • session_id (String)

    UUID of the session to rename

  • title (String)

    New session title

  • directory (String, nil) (defaults to: nil)

    Project directory path



224
225
226
# File 'lib/claude_agent_sdk.rb', line 224

def self.rename_session(session_id:, title:, directory: nil)
  SessionMutations.rename_session(session_id: session_id, title: title, directory: directory)
end

.rename_session_via_store(session_store:, session_id:, title:, directory: nil) ⇒ Object

Rename a session in a SessionStore (store-backed counterpart to rename_session). Appends a custom-title entry carrying a fresh uuid + timestamp via SessionStore#append.

Raises:

  • (ArgumentError)

    if session_id is invalid or title is empty



312
313
314
315
# File 'lib/claude_agent_sdk.rb', line 312

def self.rename_session_via_store(session_store:, session_id:, title:, directory: nil)
  SessionMutations.rename_session_via_store(session_store: session_store, session_id: session_id,
                                            title: title, directory: directory)
end

.reset_configurationObject

Reset configuration to defaults (useful for testing)



63
64
65
# File 'lib/claude_agent_sdk/configuration.rb', line 63

def reset_configuration
  @configuration = Configuration.new
end

.resolve_observers(observers) ⇒ Object

Resolve observers array: callables (Proc/lambda) are invoked to produce a fresh instance per query/session (thread-safe); plain objects are used as-is. Array() guards against nil (e.g., when observers: nil is passed explicitly). Anything implementing none of the observer methods is warned about and skipped — most commonly a Class passed instead of an instance, which previously produced silent zero instrumentation (every notify raised NoMethodError, swallowed by notify_observers' error containment).



37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/claude_agent_sdk.rb', line 37

def self.resolve_observers(observers)
  Array(observers).filter_map do |obs|
    resolved = obs.respond_to?(:call) ? obs.call : obs
    if OBSERVER_INTERFACE.none? { |m| resolved.respond_to?(m) }
      label = resolved.is_a?(Module) ? resolved : resolved.class
      hint = resolved.is_a?(Module) ? " — pass an instance (#{resolved}.new) or a factory lambda" : ''
      warn "ClaudeAgentSDK: ignoring observer #{label}: it implements none of #{OBSERVER_INTERFACE.join('/')}#{hint}"
      next nil
    end
    resolved
  end
end

.ruby_type_to_json_schema(type) ⇒ Object



63
64
65
66
67
68
69
70
71
# File 'lib/claude_agent_sdk/sdk_mcp_server.rb', line 63

def self.ruby_type_to_json_schema(type)
  case type
  when :string, String then { type: 'string' }
  when :integer, Integer then { type: 'integer' }
  when :float, Float, :number then { type: 'number' }
  when :boolean, TrueClass, FalseClass then { type: 'boolean' }
  else { type: 'string' } # Default fallback
  end
end

.tag_session(session_id:, tag:, directory: nil) ⇒ Object

Tag a session. Pass nil to clear the tag.

Parameters:

  • session_id (String)

    UUID of the session to tag

  • tag (String, nil)

    Tag string, or nil to clear

  • directory (String, nil) (defaults to: nil)

    Project directory path



232
233
234
# File 'lib/claude_agent_sdk.rb', line 232

def self.tag_session(session_id:, tag:, directory: nil)
  SessionMutations.tag_session(session_id: session_id, tag: tag, directory: directory)
end

.tag_session_via_store(session_store:, session_id:, tag:, directory: nil) ⇒ Object

Tag a session in a SessionStore (store-backed counterpart to tag_session). Pass nil to clear the tag.

Raises:

  • (ArgumentError)

    if session_id is invalid or tag is empty after sanitization



320
321
322
323
# File 'lib/claude_agent_sdk.rb', line 320

def self.tag_session_via_store(session_store:, session_id:, tag:, directory: nil)
  SessionMutations.tag_session_via_store(session_store: session_store, session_id: session_id,
                                         tag: tag, directory: directory)
end