Repository navigation
v0.20.0
Behavioral fix batch (Batch B) from the 2026-07-03 full-codebase audit (AUDIT-2026-07-03.md). Every fix aligns code with a documented contract or Python SDK behavior and changes behavior only for inputs that previously produced wrong results, hangs, or crashes. Minor (not patch) because two fixes raise where the SDK previously reported success: a signal-killed CLI now raises ProcessError, and assistant messages without message.model now raise MessageParseError.
Fixed
- A CLI process killed by a signal (OOM-kill SIGKILL, SIGSEGV, ...) now raises
ProcessError("Command terminated by signal N",exit_code: -N— Python returncode parity) instead of reporting a truncated response as clean end-of-stream success. - A trailing title-clearing entry (
{"customTitle":""}, or whitespace-only) no longer silently drops the whole session fromlist_sessions/get_session_infodisk listings: blank values now fall through the summary/title fallback chains exactly like the SessionStore path and Python (orsemantics). continue_conversation: truewith a SessionStore adapter that reports mtimes as Strings (the natural SQL-timestamp-through-JSON shape) no longer silently resumes the oldest session: String mtimes (ISO-8601 or numeric) are coerced and ordered chronologically, and mixed Integer/String lists no longer raise a bareArgumentError.- One malformed line in a transcript head (non-Hash entry, string
message, non-stringtext) no longer drops the whole session from disk listings — the guards ported from Python skip just the bad line. An assistant line whose tool_use input embeds"type":"user"can no longer donate its text as the session's first prompt. - A user message block that leaks
StopIteration(e.g..nexton an exhausted Enumerator) no longer silently ends message reception mid-turn — previously theResultMessagewas dropped andreceive_response/query()returned as if the turn had completed; the error now propagates. - Top-level
query()now validates the prompt likeClient#queryand fails fast at the call site: a bare Hash (which would stream[key, value]garbage to the CLI) and non-String/non-#eachprompts (which hung forever) raiseArgumentError. - One-shot
query()now sendsexclude_dynamic_sectionsfrom a preset system prompt in the initialize request (it was silently dropped;Clientand Python both send it). ClaudeAgentOptions#dup_withnow deep-duplicates nested containers: mutating a derived variant (variant.allowed_tools << 'Bash') no longer bleeds into the base options and every sibling variant — including the security-relevant allow/deny lists. Leaf objects (callbacks, SDK MCP server instances, store adapters) keep identity. Container deep-dup now also preserves Hash/Array subclasses — a RailsHashWithIndifferentAccessconfig no longer flattens into a plain Hash whose symbol lookups silently return nil.- Sandbox gating in command building now matches Python's
sandbox is not None:sandbox: true(the boolean toggle) no longer crashes withNoMethodError, and an explicitsandbox: false/{}is forwarded to the CLI instead of silently dropped — sosandbox: falsecan actually override a sandbox enabled in the settings JSON. output_format: { type: 'json_schema' }with a nil/absent schema no longer emits--json-schema null(which the CLI rejects at spawn); the flag is skipped, matching Python'sschema is not Noneguard.- A non-Hash
messagefield in a user/assistant CLI message now raises the documentedMessageParseErrorinstead of a rawTypeError. - Assistant messages missing
message.modelnow raiseMessageParseError(Python parity) instead of silently constructingAssistantMessage(model: nil).
What's changed
- #42 Apply audit Batch B: 12 behavioral fixes aligning with contracts/Python — @ya-luotao
Full Changelog: v0.19.1...v0.20.0
gem install claude-agent-sdk -v 0.20.0