Module: ClaudeAgentSDK::SessionStores
- Defined in:
- lib/claude_agent_sdk/session_store.rb
Overview
Internal SessionStore support functions (path mapping, option validation).
Class Method Summary collapse
-
.file_path_to_session_key(file_path, projects_dir) ⇒ Object
Derive a SessionKey from an absolute transcript file path.
-
.projects_dir(env_override = nil) ⇒ Object
Path to the rel-from base where session transcripts live, honoring a CLAUDE_CONFIG_DIR override passed to the subprocess via options.env.
-
.validate_session_store_options(options) ⇒ Object
Raise ArgumentError for invalid session_store option combinations.
Class Method Details
.file_path_to_session_key(file_path, projects_dir) ⇒ Object
Derive a SessionKey from an absolute transcript file path.
Main: <projects_dir>/<project_key>/<session_id>.jsonl
Subagent: <projects_dir>/<project_key>/<session_id>/subagents/agent-<id>.jsonl
Returns nil if file_path is not under projects_dir or has an unrecognized shape.
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/claude_agent_sdk/session_store.rb', line 261 def file_path_to_session_key(file_path, projects_dir) # A frame with a missing/non-String filePath would make Pathname.new raise # TypeError (not the ArgumentError handled below), which propagates out of # do_flush and drops the entire coalesced drain batch. Treat it as # "not under projects_dir" so only the bad frame is skipped. return nil unless file_path.is_a?(String) && !file_path.empty? begin rel = Pathname.new(file_path).relative_path_from(Pathname.new(projects_dir)).to_s rescue ArgumentError # Different drives on Windows — treat as "not under projects_dir". return nil end parts = rel.split('/') # Reject paths that escape projects_dir: a leading ".." *segment* (exact # match, so a legitimate dir like "..foo" still maps), the "." self-ref, # or an absolute path. Comparing parts[0] rather than rel.start_with?("..") # avoids the "..foo" false positive that would silently drop valid frames. return nil if parts.empty? || parts[0] == '..' || rel == '.' || Pathname.new(rel).absolute? return nil if parts.length < 2 project_key = parts[0] second = parts[1] # Main transcript: <project_key>/<session_id>.jsonl return { 'project_key' => project_key, 'session_id' => second.delete_suffix('.jsonl') } if parts.length == 2 && second.end_with?('.jsonl') # Subagent transcript: <project_key>/<session_id>/subagents/.../agent-<id>.jsonl if parts.length >= 4 subpath_parts = parts[2..] subpath_parts[-1] = subpath_parts[-1].delete_suffix('.jsonl') # Subpaths are always /-joined so keys are portable across platforms. return { 'project_key' => project_key, 'session_id' => second, 'subpath' => subpath_parts.join('/') } end nil end |
.projects_dir(env_override = nil) ⇒ Object
Path to the rel-from base where session transcripts live, honoring a CLAUDE_CONFIG_DIR override passed to the subprocess via options.env. Mirrors Sessions#config_dir but consults an explicit env override first.
Presence is detected by KEY, not value: the transport treats an explicit nil value as “unset the var for the child”, so the CLI then writes under the default ~/.claude — not under the parent’s CLAUDE_CONFIG_DIR. Empty strings get the same treatment (the Node CLI treats “” as unset).
342 343 344 345 346 347 348 349 350 351 |
# File 'lib/claude_agent_sdk/session_store.rb', line 342 def projects_dir(env_override = nil) if env_override.respond_to?(:key?) && (env_override.key?('CLAUDE_CONFIG_DIR') || env_override.key?(:CLAUDE_CONFIG_DIR)) override = env_override['CLAUDE_CONFIG_DIR'] || env_override[:CLAUDE_CONFIG_DIR] override = nil if override.respond_to?(:empty?) && override.empty? return File.join(override || File.('~/.claude'), 'projects') end File.join(Sessions.config_dir, 'projects') end |
.validate_session_store_options(options) ⇒ Object
Raise ArgumentError for invalid session_store option combinations. Called before subprocess spawn so misconfiguration fails fast.
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/claude_agent_sdk/session_store.rb', line 302 def () store = .session_store return if store.nil? # #append/#load are required, but a subclass inheriting the base stubs # would only fail at first use — with NotImplementedError, a ScriptError # that rescue StandardError layers don't catch. Fail fast here instead. %i[append load].each do |method| raise ArgumentError, "session_store must implement ##{method}" unless SessionStore.implements?(store, method) end flush = .session_store_flush.to_s unless SESSION_STORE_FLUSH_MODES.include?(flush) raise ArgumentError, "invalid session_store_flush: #{.session_store_flush.inspect} " \ "(expected one of #{SESSION_STORE_FLUSH_MODES.join(', ')})" end # When resume is explicitly set, list_sessions is provably never called # (resume wins over continue), so a minimal store is fine. if .continue_conversation && .resume.nil? && !SessionStore.implements?(store, :list_sessions) raise ArgumentError, 'continue_conversation with session_store requires the store to implement #list_sessions' end return unless .enable_file_checkpointing raise ArgumentError, 'session_store cannot be combined with enable_file_checkpointing ' \ '(checkpoints are local-disk only and would diverge from the mirrored transcript)' end |