Skip to content

v1.1.0

Choose a tag to compare

@ya-luotao ya-luotao released this 30 Sep 18:47
· 299 commits to main since this release

Syncs with Python SDK 0.2.162. Additive only: one new option and a fix to when query() closes stdin.

Added

  • ClaudeAgentOptions#verbatim_prompts (default false; Python SDK #1269). When true, every user message the SDK writes carries client_composed: true: String prompts and each message of a streamed prompt, through query, Client#connect and Client#query. Claude Code then delivers the text as written, with no @path file expansion and no slash-command dispatch. Use it when a prompt includes text your end user did not type. tools: [] and disallowed_tools do not stop @path expansion, which reads the file before the model runs. While the option is on, a caller's client_composed key is overwritten, and caller Hashes are never mutated. A streamed JSONL String is parsed and re-serialized; one that is not a single JSON object raises ArgumentError rather than going out unmarked. Client reads the option once, at connect. On current CLIs such a turn also skips the turn-start attachment pass (nested CLAUDE.md and rules files, skill and tool listings). Requires Claude Code 2.1.248 or later; the SDK warns when it connects to an older CLI with the option on. See docs/configuration.md.
  • Development only, nothing ships in the gem: TLA+ models in formal/tla/ of the two most concurrency-sensitive designs. CLIInstaller.tla covers CLIInstaller.install's publish order, the in-lock dist-tag resolve and the stale-temp sweep. ControlProtocol.tla covers the outbound control-request waiter protocol in Query. formal/tla/run.sh model-checks both with TLC. It confirms that the shipped design passes and that each alternative the source comments reject, including the historical rename-then-record order, produces a counterexample.

Changed

  • CLIInstaller::PINNED_CLI_VERSION moves from 2.1.280 (the 1.0.0 pin) to 2.1.285, the CLI the Python SDK 0.2.162 bundles. CLIInstaller.install_pinned installs it. It honors both client_composed (verbatim_prompts) and CLAUDE_CODE_SDK_READS_SESSION_STATE (the fix below).

Fixed

  • query() with hooks, can_use_tool or SDK MCP servers no longer closes stdin while a follow-up turn is still owed (Python SDK #1279, fixing Python issue #1190). A background subagent that finished just before the turn's result was already off the in-flight ledger, so stdin closed at that result, and the follow-up turn its completion triggered had every hook, permission and SDK MCP request fail with "Stream closed" (the model reported the tool as refused). The transport now sets CLAUDE_CODE_SDK_READS_SESSION_STATE=1 unless options.env or the environment already names it (in any case; a nil value in options.env unsets it). The CLI then sends session_state_changed frames marked sdk_host_only, which the SDK drops from the message stream for query() and Client alike. query() keeps stdin open until the CLI reports idle after a result:
    • The wait between turns is bounded by CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS, read from options.env and then the environment; the default is 10 minutes and 0 means no limit. The clock restarts at each result and at each running. A main-thread turn, the CLI reporting requires_action, a tracked background agent and a hook, permission or SDK MCP request the SDK is still answering each stop it.
    • With an Enumerable prompt, each message written waits for its own run. Work the CLI takes up after the run ended reopens it.
    • A CLI that sends no state (2.1.282 and earlier) gets the old behavior: stdin closes at the first result with no tracked task in flight.
    • Background agents, bounded monitors and MCP tasks can now keep a one-shot run open, up to the ceiling. Background shells, persistent monitors and remote agents do not, because the CLI leaves them out of its running report.
    • SessionStateChangedMessage reaches your code only if you set CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1 yourself; the SDK never sets it.
    • A custom transport must pass CLAUDE_CODE_SDK_READS_SESSION_STATE=1 to the CLI itself to get this behavior (see docs/client.md); the E2B example transport now does.

Usage

# Prompt text your end user did not type can no longer pull local files in via @path
options = ClaudeAgentSDK::ClaudeAgentOptions.new(verbatim_prompts: true)
ClaudeAgentSDK.query(prompt: untrusted_text, options: options) { |message| ... }

What's changed

  • #145 Sync with Python SDK 0.2.162: verbatim_prompts + stdin open until idle — @ya-luotao
  • #144 Bump PINNED_CLI_VERSION to 2.1.285 — @github-actions
  • #143 Bump PINNED_CLI_VERSION to 2.1.284 — @github-actions
  • #142 Bump PINNED_CLI_VERSION to 2.1.283 — @github-actions
  • #141 Add TLA+ models of the CLI installer and control-request protocol — @ya-luotao

Full Changelog: v1.0.0...v1.1.0


gem install claude-agent-sdk -v 1.1.0