diff --git a/.claude/commands/release.md b/.claude/commands/release.md index 4d88e1ef..3e1f75ea 100644 --- a/.claude/commands/release.md +++ b/.claude/commands/release.md @@ -131,7 +131,7 @@ Draft the release body from the new CHANGELOG section. Prior releases use this s ### What's changed - #NN — @author -**Full Changelog**: https://github.com/ya-luotao/claude-agent-sdk-ruby/compare/v<prev>...vX.Y.Z +**Full Changelog**: https://github.com/rubycatco/claude-agent-sdk-ruby/compare/v<prev>...vX.Y.Z --- @@ -143,7 +143,7 @@ gem install claude-agent-sdk -v X.Y.Z Generate the "What's changed" list from merged PRs since the last tag: ``` -gh pr list --repo ya-luotao/claude-agent-sdk-ruby \ +gh pr list --repo rubycatco/claude-agent-sdk-ruby \ --state merged --search "merged:>=<last-tag-date>" \ --json number,title,author ``` @@ -151,7 +151,7 @@ gh pr list --repo ya-luotao/claude-agent-sdk-ruby \ Create the release. Default: publish immediately (this is what fires `publish.yml`). If the user passed `--draft`, append `--draft` so they can eyeball the notes in GitHub UI and click "Publish release" manually: ``` -gh release create vX.Y.Z --repo ya-luotao/claude-agent-sdk-ruby \ +gh release create vX.Y.Z --repo rubycatco/claude-agent-sdk-ruby \ --title "vX.Y.Z" --notes-file <tmpfile> # add --draft if --draft was passed ``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3b92d48b..e5307d8f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -5,7 +5,7 @@ body: - type: markdown attributes: value: | - Thanks for the report. For a security vulnerability, **don't open an issue**; see [SECURITY.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/SECURITY.md). + Thanks for the report. For a security vulnerability, **don't open an issue**; see [SECURITY.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/SECURITY.md). If the Claude Code CLI itself misbehaves in the same way when you run `claude` directly, report it to [anthropics/claude-code](https://github.com/anthropics/claude-code/issues) instead. - type: textarea diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a1500a1e..d8bdc009 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ blank_issues_enabled: true contact_links: - name: Report a security vulnerability - url: https://github.com/ya-luotao/claude-agent-sdk-ruby/security/advisories/new + url: https://github.com/rubycatco/claude-agent-sdk-ruby/security/advisories/new about: Please report vulnerabilities in this gem privately, not as a public issue. See SECURITY.md. - name: Claude Code CLI bugs url: https://github.com/anthropics/claude-code/issues diff --git a/CHANGELOG.md b/CHANGELOG.md index 04d19b59..0cc06255 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed +- The repository moves to the **rubycatco** GitHub organization: `github.com/rubycatco/claude-agent-sdk-ruby`. The old `ya-luotao/...` URL redirects, and the gem on rubygems.org is unaffected; gem metadata, docs, templates and issue links now point at the new home. - `CLIInstaller::PINNED_CLI_VERSION` moves from 2.1.288 to **2.1.295**, following the CLI the Python SDK bundles. `CLIInstaller.install_pinned` installs it. See the [Claude Code changelog](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md). ## [1.2.1] - 2026-10-06 @@ -24,48 +25,48 @@ An internal release: three refactors that move duplicated or scattered logic int Fixes from a review of the whole SDK, a reference for every option (`docs/options.md`), and Claude Code 2.1.288 as the pinned CLI. Settings the SDK used to drop without an error now take effect — hook outputs and `sandbox:` Hashes in Ruby spelling, a `tools:` String, option Hashes with a Symbol `type` — so read the entries in bold before upgrading: they say what changes for code that relied on the old behavior. ### Added -- **`docs/options.md`**: a reference for every `ClaudeAgentOptions` attribute — its type, its default, and the CLI flag, `initialize` field or environment variable it becomes — plus what an option reads when it is left out, passed as `nil` or given a configured default, and the environment variables the SDK reads. It documents options that had no entry anywhere before (`extra_args`, `include_partial_messages`, `include_hook_events`, `task_budget`, `strict_mcp_config`) and the `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS` ceiling from 1.1.0. A spec checks every row against the code. `docs/client.md` documents the `transport:` argument of `query` / `ask`. ([#155](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/155)) -- `Pathname` where a path goes: `settings:` (a settings file; used to send no `--settings` at all), `SystemPromptFile#path`, the `path` of a `{ type: 'file' }` system prompt Hash, and a plugin path, typed or Hash (these failed at connect with `no implicit conversion of Pathname into String`). `tools:` is signed `String` as well; the RBS signatures are widened accordingly. ([#160](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/160)) +- **`docs/options.md`**: a reference for every `ClaudeAgentOptions` attribute — its type, its default, and the CLI flag, `initialize` field or environment variable it becomes — plus what an option reads when it is left out, passed as `nil` or given a configured default, and the environment variables the SDK reads. It documents options that had no entry anywhere before (`extra_args`, `include_partial_messages`, `include_hook_events`, `task_budget`, `strict_mcp_config`) and the `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS` ceiling from 1.1.0. A spec checks every row against the code. `docs/client.md` documents the `transport:` argument of `query` / `ask`. ([#155](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/155)) +- `Pathname` where a path goes: `settings:` (a settings file; used to send no `--settings` at all), `SystemPromptFile#path`, the `path` of a `{ type: 'file' }` system prompt Hash, and a plugin path, typed or Hash (these failed at connect with `no implicit conversion of Pathname into String`). `tools:` is signed `String` as well; the RBS signatures are widened accordingly. ([#160](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/160)) ### Changed - `docs/cli-installer.md` now says when `CLIInstaller::PINNED_CLI_VERSION` moves: pin-only changes ship with the next gem release rather than one release per CLI bump. It also covers how a gem patch can move your CLI and how to take a newer CLI before a release pins it. The pin-bump workflow keeps a single `[Unreleased]` entry for the pin. - `CLIInstaller::PINNED_CLI_VERSION` moves from 2.1.285 to **2.1.288**, following the CLI the Python SDK bundles. `CLIInstaller.install_pinned` installs it. See the [Claude Code changelog](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md). -- **The Rails guide and both Rails examples turn off the CLI's per-project auto-memory** (`env: { 'CLAUDE_CODE_DISABLE_AUTO_MEMORY' => '1' }`). Every SDK session reads that memory and a `claude_code`-preset session writes it without a permission check, so in a multi-user app one user's "remember …" would reach every other user's session. The generated initializer only documents the switch: it carries the same line commented out, so **a newly generated app is not isolated until you uncomment it** (or set it per call). If you copied the examples, add the line; the value must be `'1'`. ([#158](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/158)) -- **`docs/configuration.md` has a "Session Isolation" section.** Claude Code's per-project auto-memory is on in SDK sessions: every session loads the project's `MEMORY.md` index into its context (`setting_sources: []` does not prevent it), and a session on the `claude_code` preset also writes memory files, which the CLI allows without any permission setup. Servers and multi-tenant hosts should set `env: { 'CLAUDE_CODE_DISABLE_AUTO_MEMORY' => '1' }` (or `settings: { autoMemoryEnabled: false }`); a custom transport has to pass either one through to its CLI itself; `Client#context_usage[:memoryFiles]` shows what a session loaded. ([#155](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/155)) -- **A requested sandbox that is not active now produces a warning.** `sandbox: { enabled: true }` does not guarantee a sandbox: when the CLI cannot start one (a Linux container without `bubblewrap` and `socat`, for example) it prints `⚠ Sandbox disabled: …` to its stderr and runs commands unsandboxed, which a host only saw through a `stderr:` callback. The SDK now repeats that line as a Ruby warning, prefixed `[claude-agent-sdk]`, once per session, when the `sandbox:` option enabled the sandbox. The default is unchanged; set `fail_if_unavailable: true` to make the CLI exit instead. ([#156](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/156)) -- **`OTelObserver` now leaves a span for a session that fails before its first `InitMessage`.** A CLI that could not be found or started, or an `initialize` that failed or timed out, raised to the caller but exported no span at all (0.18.0's "crashed sessions now produce OTel traces with error status" only held once an `InitMessage` had arrived). `on_error` now emits a `claude_agent.session` span with the observer's default attributes, the prompt if one had been sent, the exception event and error status, and ends it at once. The span has no model or `session.id`. Nothing changes once a trace has started. If you count `claude_agent.session` spans, failed starts are now among them. ([#148](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/148)) -- `docs/observability.md` no longer promises "standard `gen_ai.*` semantic conventions … any OTel backend". The attributes follow the Langfuse and OpenInference conventions plus a subset of OTel `gen_ai.*`; the page now lists every attribute, event and status `OTelObserver` sets, and how the `gen_ai.*` names differ from the OTel GenAI semantic conventions — notably that `gen_ai.usage.input_tokens` leaves cache tokens out, and that the inclusive count is `llm.token_count.prompt` on the session span. No attribute changed. ([#148](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/148)) -- **`docs/rails.md` no longer says callbacks inherit the request's state.** A callback runs on a thread or fiber Rails never set up: `Current` attributes, `Time.zone`, log tags, the error context and the `connected_to` role / shard read as their defaults there (a write under `connected_to(shard:)` lands in the default shard), with or without `Railtie.callback_wrapper`; `I18n.locale` depends on the i18n version. The guide now has the table, a per-call recipe that carries the state across, the rule for composing your own wrapper with the Rails one, a section on transactions and the connection pool (a callback is outside the caller's transaction and uses its own connection), and a note that in development an agent run holds the reload lock. ([#158](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/158)) -- `docs/configuration.md` says what the CLI does with an invalid sandbox value: it discards the whole `--settings` value, sandbox and `permissions` rules alike. The SDK does not surface this — `connect` succeeds and nothing appears on stderr; only the CLI's `get_settings` control response lists the error. That holds for every form, the typed classes included. ([#160](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/160)) -- Disk listings (`list_sessions`, `get_session_info`) scan each transcript's head and tail windows as bytes; with non-ASCII text in a window this takes a fraction of the CPU time it did. Results are unchanged. ([#157](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/157)) -- Development only: the gemspec's development dependency on Bundler is `>= 2.0` (was `~> 2.0`), so `bundle install` works in a checkout on Ruby 4.0, which ships Bundler 4. CI now runs the suite and RuboCop on Ruby 4.0, and a keyless smoke runs the pinned CLI on PRs that touch the installer, the transport, `Query` or the message parser; the API-key integration job shows as skipped instead of passed when the key is absent. Runtime dependencies are unchanged. ([#153](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/153)) +- **The Rails guide and both Rails examples turn off the CLI's per-project auto-memory** (`env: { 'CLAUDE_CODE_DISABLE_AUTO_MEMORY' => '1' }`). Every SDK session reads that memory and a `claude_code`-preset session writes it without a permission check, so in a multi-user app one user's "remember …" would reach every other user's session. The generated initializer only documents the switch: it carries the same line commented out, so **a newly generated app is not isolated until you uncomment it** (or set it per call). If you copied the examples, add the line; the value must be `'1'`. ([#158](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/158)) +- **`docs/configuration.md` has a "Session Isolation" section.** Claude Code's per-project auto-memory is on in SDK sessions: every session loads the project's `MEMORY.md` index into its context (`setting_sources: []` does not prevent it), and a session on the `claude_code` preset also writes memory files, which the CLI allows without any permission setup. Servers and multi-tenant hosts should set `env: { 'CLAUDE_CODE_DISABLE_AUTO_MEMORY' => '1' }` (or `settings: { autoMemoryEnabled: false }`); a custom transport has to pass either one through to its CLI itself; `Client#context_usage[:memoryFiles]` shows what a session loaded. ([#155](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/155)) +- **A requested sandbox that is not active now produces a warning.** `sandbox: { enabled: true }` does not guarantee a sandbox: when the CLI cannot start one (a Linux container without `bubblewrap` and `socat`, for example) it prints `⚠ Sandbox disabled: …` to its stderr and runs commands unsandboxed, which a host only saw through a `stderr:` callback. The SDK now repeats that line as a Ruby warning, prefixed `[claude-agent-sdk]`, once per session, when the `sandbox:` option enabled the sandbox. The default is unchanged; set `fail_if_unavailable: true` to make the CLI exit instead. ([#156](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/156)) +- **`OTelObserver` now leaves a span for a session that fails before its first `InitMessage`.** A CLI that could not be found or started, or an `initialize` that failed or timed out, raised to the caller but exported no span at all (0.18.0's "crashed sessions now produce OTel traces with error status" only held once an `InitMessage` had arrived). `on_error` now emits a `claude_agent.session` span with the observer's default attributes, the prompt if one had been sent, the exception event and error status, and ends it at once. The span has no model or `session.id`. Nothing changes once a trace has started. If you count `claude_agent.session` spans, failed starts are now among them. ([#148](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/148)) +- `docs/observability.md` no longer promises "standard `gen_ai.*` semantic conventions … any OTel backend". The attributes follow the Langfuse and OpenInference conventions plus a subset of OTel `gen_ai.*`; the page now lists every attribute, event and status `OTelObserver` sets, and how the `gen_ai.*` names differ from the OTel GenAI semantic conventions — notably that `gen_ai.usage.input_tokens` leaves cache tokens out, and that the inclusive count is `llm.token_count.prompt` on the session span. No attribute changed. ([#148](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/148)) +- **`docs/rails.md` no longer says callbacks inherit the request's state.** A callback runs on a thread or fiber Rails never set up: `Current` attributes, `Time.zone`, log tags, the error context and the `connected_to` role / shard read as their defaults there (a write under `connected_to(shard:)` lands in the default shard), with or without `Railtie.callback_wrapper`; `I18n.locale` depends on the i18n version. The guide now has the table, a per-call recipe that carries the state across, the rule for composing your own wrapper with the Rails one, a section on transactions and the connection pool (a callback is outside the caller's transaction and uses its own connection), and a note that in development an agent run holds the reload lock. ([#158](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/158)) +- `docs/configuration.md` says what the CLI does with an invalid sandbox value: it discards the whole `--settings` value, sandbox and `permissions` rules alike. The SDK does not surface this — `connect` succeeds and nothing appears on stderr; only the CLI's `get_settings` control response lists the error. That holds for every form, the typed classes included. ([#160](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/160)) +- Disk listings (`list_sessions`, `get_session_info`) scan each transcript's head and tail windows as bytes; with non-ASCII text in a window this takes a fraction of the CPU time it did. Results are unchanged. ([#157](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/157)) +- Development only: the gemspec's development dependency on Bundler is `>= 2.0` (was `~> 2.0`), so `bundle install` works in a checkout on Ruby 4.0, which ships Bundler 4. CI now runs the suite and RuboCop on Ruby 4.0, and a keyless smoke runs the pinned CLI on PRs that touch the installer, the transport, `Query` or the message parser; the API-key integration job shows as skipped instead of passed when the key is absent. Runtime dependencies are unchanged. ([#153](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/153)) ### Fixed -- **A hook callback's Hash return value now means what the equivalent typed output means, whatever its key style.** Only five top-level keys were renamed before, and only as Symbols, so `{ hook_specific_output: { hook_event_name: 'PreToolUse', permission_decision: 'deny' } }` reached the CLI under keys it does not read: the decision was ignored and the tool ran. A String-keyed Hash (e.g. after `JSON.parse`) and a `SyncHookJSONOutput` holding a snake_case Hash failed the same way. The fields the typed output classes model are now accepted in snake_case or camelCase, as Symbols or Strings, at the top level and inside `hook_specific_output`; any other key is sent as written, and the keys inside `updated_input`, the tool outputs and a `PermissionRequest` `decision` are not renamed. **If a hook of yours returns snake_case keys, they now take effect.** When one Hash spells a field both ways the CLI's spelling is sent, and a Symbol and a String spelling the same key are written once (json 3.x used to fail the hook with `detected duplicate key`). ([#149](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/149)) -- **A `sandbox:` Hash written in Ruby spelling is now applied.** The typed sandbox classes take snake_case attributes and write the camelCase keys the CLI reads, but a Hash was forwarded as written: `sandbox: { enabled: true, network: { denied_domains: ['evil.example'] }, filesystem: { deny_read: ['~/.aws'] } }` reached the CLI under keys it does not know, and the CLI ignored them without an error — the session ran with `network: {}` and `filesystem: {}`, no deny rule. `SandboxSettings.new(network: { denied_domains: [...] })` lost its rules the same way. The fields of `SandboxSettings`, `SandboxNetworkConfig` and `SandboxFilesystemConfig` are now accepted in snake_case or camelCase, as Symbols or Strings, at the top level and inside `network` / `filesystem`; any other key is sent as written. **If a sandbox Hash of yours has snake_case keys, they now take effect.** A snake_case key is renamed only when its value has the shape the CLI accepts for it (`true` / `false`, an Array of Strings, a port number from 0 to 65535); with any other value it is sent as written and stays without effect, as before — so the rename can never make the CLI discard your settings. When one Hash spells a field both ways the camelCase one is sent; a field holding `nil` is left out (the CLI rejects `null` there); a Symbol and a String spelling the same key are written once. A typed `SandboxNetworkConfig` / `SandboxFilesystemConfig` inside a `sandbox:` Hash is written as the Hash it stands for (it reached the CLI as its `#inspect` text, which made the CLI drop the sandbox). ([#160](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/160)) -- **"Always allow" answers from `can_use_tool` now stick for whole-tool rules.** `PermissionUpdate#to_h` wrote `"ruleContent": null` for a rule without content and omitted a nil `destination`; the CLI accepts neither and drops the **whole** `updatedPermissions` array when one entry is malformed, so `PermissionResultAllow.new(updated_permissions: context.suggestions)` silently persisted nothing for the suggestions the CLI builds for `WebSearch` or MCP tools, and the callback was asked again on every call. `ruleContent` is now omitted when nil, and an update that has a `type` but no `destination` is sent to `'session'` (this run only; no settings file is written). ([#149](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/149)) -- **`tools: 'Read,Grep'` is no longer ignored.** A String had no branch in the command builder, so no `--tools` flag was sent and the session got every built-in tool. It is now passed to `--tools` as written (the CLI's own comma-separated form). **If you pass `tools:` as a String, the session is now restricted to the tools it names.** ([#160](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/160)) -- **`system_prompt`, `tools`, `output_format` and `plugins` Hashes accept a Symbol `type`**, like `thinking` and the MCP server configs already did. `tools: { type: :preset, preset: :claude_code }` sent the Hash's JSON text as a tool name and the session started with **no tools**; `system_prompt: { type: :custom, prompt: '…' }` (and `:file`, `:preset`) sent no flag, so the prompt and its options were dropped; `output_format: { type: :json_schema, schema: {…} }` made the CLI exit; `plugins: [{ type: :local, … }]` raised `Unsupported plugin type`. An `output_format` Hash with `type` and `schema` under different key styles silently turned structured output off; `schema` is now found under either. A Symbol-typed Hash means what the String-typed one means, including its check: `{ type: :custom }` without a `:prompt` String raises the same `ArgumentError`. ([#160](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/160)) -- **Directory-scoped session APIs could read and modify another project's sessions.** For project paths over 200 characters, `list_sessions(directory: B)`, `get_session_messages` and `rename_session` / `tag_session` / `delete_session` / `fork_session` fell back to ANY project directory sharing the first 200 characters, so they could return or change the sessions of a sibling path A. In a directory found by that fallback, only sessions whose own transcript records the requested path as its top-level `cwd` are listed, read or modified (worktrees whose paths share such a directory are each listed). ([#157](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/157)) -- **A relative CLI path could run a different binary than the one the SDK checked.** The version probe runs in the process's working directory but the CLI is spawned in `cwd:`, so `cli_path: 'bin/claude'`, or a `claude` found through a relative `PATH` entry (`bin`, `.`, an empty entry), was checked in one directory and executed in the other — whatever file sat at that path inside the directory the agent was pointed at. The transport now settles one absolute path and uses it for both: a relative `cli_path` is resolved against the process's working directory (a leading `~` is expanded), and a bare name is looked up on `PATH` by the SDK, with relative `PATH` entries resolved against the process's working directory. Discovery no longer runs `which`: the SDK searches the process's `PATH` for `claude` itself, so a `which` found through a relative `PATH` entry can neither run nor redirect the lookup (a host without a `which` program now finds `claude` on `PATH`; with no `PATH` at all, discovery searches the system directories, never the working directory). If you relied on a relative `cli_path` resolving inside `cwd:`, pass an absolute path. A subclass whose `#build_command` runs the CLI through another program (docker exec, ssh) still gets `cli_path` as given, since it names a file on the far side. ([#156](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/156)) -- **`#inspect` no longer prints credentials held outside `env` and `headers`.** `ClaudeAgentOptions#inspect` (and `#to_s`, `pp`, string interpolation) now filters `settings` and `extra_args` the way it filters `env`: a Hash keeps its keys with `"[FILTERED]"` values, and a String `settings` (JSON or a file path) prints as `"[FILTERED]"`. `mcp_servers` given as a String (JSON or a file path) prints as `"[FILTERED]"` too. `McpStdioServerConfig#args` is filtered. The `url` of `McpHttpServerConfig` / `McpSSEServerConfig` prints as scheme, host and port only (`"https://mcp.example.com/[FILTERED]"`), since a token can sit in its userinfo, path or query. Raw Hash server configs are filtered the same way (`type`, `command` and the redacted `url` are shown; `args`, `env`, `headers` and any other key print as `"[FILTERED]"`): inside `mcp_servers`, where they used to rely on the nesting limit, and in `McpServerStatus#config`, where the CLI echoes each server's `headers`, `url` and `args` back in `mcp_status`. Display only: the objects are not modified and nothing sent to the CLI changes. ([#151](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/151)) -- **A materialized resume directory preserved after a mirror failure now holds only `projects/`.** The cleanup removed four known files and left whatever else the CLI had written — including `backups/.claude.json.backup.<ts>`, a full copy of the seeded `.claude.json`, which can hold MCP header secrets. Every entry except `projects/` is now deleted. To do that safely the SDK first moves the directory into a fresh private one next to it (`claude-preserved-resume-*`), so **the preserved transcript is no longer at the temp dir's old path: the warning names the new one.** If what it moved is not the directory it created (the path had been replaced by a symlink or another directory), or it cannot be moved, nothing is deleted and the warning says the scrub was skipped and why; symlinks are never followed, permissions are repaired only on the directory that was checked, an entry that cannot be removed is named in the warning, and an interrupted scrub says which directory to remove once the transcript is imported. Calling `disconnect` again after a teardown that was interrupted while it preserved the directory no longer deletes it. ([#159](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/159)) -- **`ClaudeAgentSDK::Railtie.callback_wrapper` no longer reports to `Rails.error`.** In production it ran callbacks inside `executor.wrap`, which reports whatever passes through it as an unhandled error: a message-block exception was reported from the callback thread with an empty context (and then skipped by the request / job layer as already reported), exceptions the SDK handles itself (hook, `can_use_tool`, SDK MCP tool, observer) were reported although the run continued, and — on Rails 7.2.3, 8.0.2 and later — so were the SDK's own cancellations under `callback_scheduling: :inline`. The wrapper now enters the executor with `run!` / `complete!` (same hooks, no report), so an exception that escapes a callback is reported once, by the layer that called the SDK, with its controller / job context. If you relied on those reports to see hook or tool failures, report them inside the callback (`Rails.error.report`). ([#158](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/158)) -- **A callback that fails with `NotImplementedError`, `LoadError`, `SystemStackError` or `SecurityError` is answered like any other failing callback.** These are not `StandardError`s, so nothing rescued them when a hook, a `can_use_tool` callback, an SDK MCP tool, resource or prompt handler, or the `callback_wrapper` raised one: the CLI never got a response for that request, and the exception stopped the whole Async reactor, taking every other session and task on it down (`query()` raised the raw exception). Now hooks and `can_use_tool` get the error response, a tool handler's failure reaches the model as an `isError: true` result, and a resource or prompt handler gets the JSON-RPC `-32603` error, exactly as for a `RuntimeError`; the session carries on. `SdkMcpServer#call_tool` called directly returns the `isError` result for these exceptions instead of raising. `exit`, `Interrupt`, signals and cancellation behave as before, and `NoMemoryError` is still fatal. ([#150](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/150)) -- **A callback failure whose message is not valid UTF-8 is answered.** When a hook or `can_use_tool` callback raised an exception whose message held invalid bytes (a multibyte character cut by `byteslice`, binary subprocess or HTTP output), building the error response raised `JSON::GeneratorError` and the CLI was never answered. On the process-exit path the same error replaced the `SystemExit` the SDK promises to re-raise: under `:inline` scheduling `exit` did not end the process. An `exit` or signal exception whose message was in an encoding that is not ASCII-compatible (UTF-16) failed the same way, with `Encoding::CompatibilityError`. The error text is now normalized first — invalid bytes become U+FFFD, text in another valid encoding is transcoded — and a response that still cannot be serialized is replaced by a fixed message, so the request is always answered and the original exception is the one re-raised. ([#150](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/150)) -- **`query()` with a streamed (Enumerator) prompt no longer closes stdin right after writing a message.** With hooks, `can_use_tool` or SDK MCP servers, stdin could close before that message's turn had produced a frame, so the turn's hook, permission and SDK MCP requests failed with "Stream closed". It happened when the CLI's `idle` for the previous run and the next message became ready in the same reactor pass: `Query#end_run` stopped the between-turns ceiling task (which yields) before it marked the run ended, and the message joined the run that was about to end. The run is now marked ended first. ([#154](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/154)) -- **A `Client` control method called from a fiber on another thread's reactor no longer breaks the session.** `interrupt`, `set_model`, `set_permission_mode`, `mcp_status`, … called through `Sync { client.interrupt }` on a request thread while the session runs on a background reactor, or from a `Sync` block inside a `:thread`-mode callback: on async 2.10–2.28 the response raised `FiberError: fiber called across threads` in the session's read loop, which ended the session and left the caller waiting for the control-request timeout (20 minutes by default); on async ≥ 2.29 the response could be missed, with the same wait. Such callers now wait the way plain threads already did. ([#154](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/154)) -- **`receive_response` / `receive_messages` no longer hang after the CLI is gone.** After the CLI exited or crashed while a `Client` was connected, the first call raised the stream error (or returned the last turn), the second returned without messages, and every later call hung forever with no error and no timeout (from plain Ruby after the reactor had finished, async 2.10 raised `NoMethodError` instead). Every call after the end of the stream now returns at once; the stream error is still raised once. ([#154](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/154)) -- **A deadline around a `Client` call is no longer swallowed while an observer runs.** With observers configured, a deadline around `Client#receive_response`, `#receive_messages`, `#query` or `#disconnect` (`task.with_timeout`, or `Timeout.timeout` inside a reactor) was silently dropped when it expired while an observer callback was still running: nothing was raised and the turn carried on, calling the message block after the deadline. Only what the observer (or its `callback_wrapper`) raises is contained now. With `callback_scheduling: :inline` the observer runs on the caller's own fiber, where such a deadline cannot be told from the observer's own failure, and it is still contained. ([#154](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/154)) -- **`Client#disconnect`, and the cleanup of a failing `Client#connect`, always tear the session down.** When the call was interrupted while an observer's `on_close` / `on_error` was running — the caller's task stopped (`Async::Stop`), or an inline hook's cooperative timeout fired — the CLI process, the session's read loop and the materialized resume directory (with its credentials copy) were left behind. The teardown now always runs, and the interruption propagates after it. ([#154](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/154)) -- **A `Client#disconnect` from another thread completes the CLI's graceful shutdown.** From an application thread, or from a tool handler, hook or `can_use_tool` callback under the default `callback_scheduling: :thread`, the close was handed to a transient task on the session's reactor; when the session had nothing else left to do the reactor wound down and stopped that task mid-teardown, so the CLI got SIGTERM instead of stdin EOF and its 5-second grace period (the time it has to finish its last session write), and `disconnect` returned before the child was reaped. The close now runs on a task the reactor waits for: such a `disconnect` takes as long as the teardown, like one made on the reactor. ([#161](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/161)) -- **A callback's thread hop no longer returns before the callback has finished.** When an exception (`Async::Stop`, a timeout) had been raised into the waiting fiber just as the previous hop's thread ended, the stale wakeup resumed the next hop, which returned `nil` as its result; with a `timeout:` (session-store adapter calls) the same wakeup was reported as `timed out` although no time had passed. `FiberBoundary.invoke` now waits until the thread has really finished or the deadline has really passed. ([#154](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/154)) -- An error raised by `ClaudeAgentSDK.query` / `.ask` from synchronous code (CLI not found, `ProcessError`, `ResultError`, an exception in your own block) was also logged by Async as `Task may have ended with unhandled exception.`, with the message and a backtrace, even when your code rescued it. The error is now only raised. Inside a reactor the same applied to failures before the first suspension, such as a transport that cannot connect. ([#154](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/154)) -- **A `stderr:` callback that raised `NotImplementedError`, `LoadError` or `SystemStackError` could hang the session.** Only `StandardError` was contained, so those ended the thread that reads the CLI's stderr; once the CLI had written another 64 KiB of stderr it blocked for good. They are now handled like any other callback error: dropped, and the callback is called again for the next line. The same holds for a `debug_stderr:` sink and for the SDK's own sandbox warning when a custom `Warning.warn` or `$stderr` raises one of them. ([#156](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/156)) -- **Store-backed resume now authenticates on macOS with a custom `CLAUDE_CONFIG_DIR`.** There the CLI keeps its OAuth credentials in the Keychain under `Claude Code-credentials-<first 8 hex of SHA-256(config dir)>` and writes no `.credentials.json`; the SDK only bridged the default config dir's entry, so the resumed subprocess reported "Not logged in" (and the failed attempt was mirrored into the store). The Keychain is now consulted for a custom config dir too — only when that directory has no `.credentials.json`, and never under `ANTHROPIC_API_KEY` / `CLAUDE_CODE_OAUTH_TOKEN`. The seeded copy still has the refresh token removed and mode `0600`. ([#159](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/159)) -- **A mirror flush cancelled while it waited behind an in-flight append no longer loses its batch.** The batcher detached the pending entries before queueing on its lock, so a `#flush` whose task was stopped there (a timeout around the caller, `Task#stop`) dropped them: they never reached the store and no `MirrorErrorMessage` was produced. The buffer is now detached inside the lock, so those entries go out with the next flush; a frame that arrives while the final flush is still waiting is delivered rather than counted as dropped (Python [#1289](https://github.com/anthropics/claude-agent-sdk-python/pull/1289)). The mirror's retry backoff uses `Kernel#sleep` instead of the deprecated `Async::Task#sleep`. ([#159](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/159)) -- **`get_session_messages` and `get_subagent_messages` (disk and `session_store:`) no longer drop the results of parallel tool calls.** The CLI parents each `tool_result` on the entry holding its own `tool_use`, so with several tool calls in one assistant turn only the last-written result lay on the path the readers walk; the others were silently missing, leaving `tool_use` blocks without a `tool_result`. They are now returned, after the turn's `tool_use` messages and before the result the conversation continued from; the chain's own result wins, so a call answered again after a rewind is not returned twice, and the result of a branch a rewind dropped is not brought back. Affected sessions return more messages than before, so `offset:`/`limit:` pages over them shift. ([#157](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/157)) -- **Session reads, listings and mutations — other fixes** ([#157](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/157)): +- **A hook callback's Hash return value now means what the equivalent typed output means, whatever its key style.** Only five top-level keys were renamed before, and only as Symbols, so `{ hook_specific_output: { hook_event_name: 'PreToolUse', permission_decision: 'deny' } }` reached the CLI under keys it does not read: the decision was ignored and the tool ran. A String-keyed Hash (e.g. after `JSON.parse`) and a `SyncHookJSONOutput` holding a snake_case Hash failed the same way. The fields the typed output classes model are now accepted in snake_case or camelCase, as Symbols or Strings, at the top level and inside `hook_specific_output`; any other key is sent as written, and the keys inside `updated_input`, the tool outputs and a `PermissionRequest` `decision` are not renamed. **If a hook of yours returns snake_case keys, they now take effect.** When one Hash spells a field both ways the CLI's spelling is sent, and a Symbol and a String spelling the same key are written once (json 3.x used to fail the hook with `detected duplicate key`). ([#149](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/149)) +- **A `sandbox:` Hash written in Ruby spelling is now applied.** The typed sandbox classes take snake_case attributes and write the camelCase keys the CLI reads, but a Hash was forwarded as written: `sandbox: { enabled: true, network: { denied_domains: ['evil.example'] }, filesystem: { deny_read: ['~/.aws'] } }` reached the CLI under keys it does not know, and the CLI ignored them without an error — the session ran with `network: {}` and `filesystem: {}`, no deny rule. `SandboxSettings.new(network: { denied_domains: [...] })` lost its rules the same way. The fields of `SandboxSettings`, `SandboxNetworkConfig` and `SandboxFilesystemConfig` are now accepted in snake_case or camelCase, as Symbols or Strings, at the top level and inside `network` / `filesystem`; any other key is sent as written. **If a sandbox Hash of yours has snake_case keys, they now take effect.** A snake_case key is renamed only when its value has the shape the CLI accepts for it (`true` / `false`, an Array of Strings, a port number from 0 to 65535); with any other value it is sent as written and stays without effect, as before — so the rename can never make the CLI discard your settings. When one Hash spells a field both ways the camelCase one is sent; a field holding `nil` is left out (the CLI rejects `null` there); a Symbol and a String spelling the same key are written once. A typed `SandboxNetworkConfig` / `SandboxFilesystemConfig` inside a `sandbox:` Hash is written as the Hash it stands for (it reached the CLI as its `#inspect` text, which made the CLI drop the sandbox). ([#160](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/160)) +- **"Always allow" answers from `can_use_tool` now stick for whole-tool rules.** `PermissionUpdate#to_h` wrote `"ruleContent": null` for a rule without content and omitted a nil `destination`; the CLI accepts neither and drops the **whole** `updatedPermissions` array when one entry is malformed, so `PermissionResultAllow.new(updated_permissions: context.suggestions)` silently persisted nothing for the suggestions the CLI builds for `WebSearch` or MCP tools, and the callback was asked again on every call. `ruleContent` is now omitted when nil, and an update that has a `type` but no `destination` is sent to `'session'` (this run only; no settings file is written). ([#149](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/149)) +- **`tools: 'Read,Grep'` is no longer ignored.** A String had no branch in the command builder, so no `--tools` flag was sent and the session got every built-in tool. It is now passed to `--tools` as written (the CLI's own comma-separated form). **If you pass `tools:` as a String, the session is now restricted to the tools it names.** ([#160](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/160)) +- **`system_prompt`, `tools`, `output_format` and `plugins` Hashes accept a Symbol `type`**, like `thinking` and the MCP server configs already did. `tools: { type: :preset, preset: :claude_code }` sent the Hash's JSON text as a tool name and the session started with **no tools**; `system_prompt: { type: :custom, prompt: '…' }` (and `:file`, `:preset`) sent no flag, so the prompt and its options were dropped; `output_format: { type: :json_schema, schema: {…} }` made the CLI exit; `plugins: [{ type: :local, … }]` raised `Unsupported plugin type`. An `output_format` Hash with `type` and `schema` under different key styles silently turned structured output off; `schema` is now found under either. A Symbol-typed Hash means what the String-typed one means, including its check: `{ type: :custom }` without a `:prompt` String raises the same `ArgumentError`. ([#160](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/160)) +- **Directory-scoped session APIs could read and modify another project's sessions.** For project paths over 200 characters, `list_sessions(directory: B)`, `get_session_messages` and `rename_session` / `tag_session` / `delete_session` / `fork_session` fell back to ANY project directory sharing the first 200 characters, so they could return or change the sessions of a sibling path A. In a directory found by that fallback, only sessions whose own transcript records the requested path as its top-level `cwd` are listed, read or modified (worktrees whose paths share such a directory are each listed). ([#157](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/157)) +- **A relative CLI path could run a different binary than the one the SDK checked.** The version probe runs in the process's working directory but the CLI is spawned in `cwd:`, so `cli_path: 'bin/claude'`, or a `claude` found through a relative `PATH` entry (`bin`, `.`, an empty entry), was checked in one directory and executed in the other — whatever file sat at that path inside the directory the agent was pointed at. The transport now settles one absolute path and uses it for both: a relative `cli_path` is resolved against the process's working directory (a leading `~` is expanded), and a bare name is looked up on `PATH` by the SDK, with relative `PATH` entries resolved against the process's working directory. Discovery no longer runs `which`: the SDK searches the process's `PATH` for `claude` itself, so a `which` found through a relative `PATH` entry can neither run nor redirect the lookup (a host without a `which` program now finds `claude` on `PATH`; with no `PATH` at all, discovery searches the system directories, never the working directory). If you relied on a relative `cli_path` resolving inside `cwd:`, pass an absolute path. A subclass whose `#build_command` runs the CLI through another program (docker exec, ssh) still gets `cli_path` as given, since it names a file on the far side. ([#156](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/156)) +- **`#inspect` no longer prints credentials held outside `env` and `headers`.** `ClaudeAgentOptions#inspect` (and `#to_s`, `pp`, string interpolation) now filters `settings` and `extra_args` the way it filters `env`: a Hash keeps its keys with `"[FILTERED]"` values, and a String `settings` (JSON or a file path) prints as `"[FILTERED]"`. `mcp_servers` given as a String (JSON or a file path) prints as `"[FILTERED]"` too. `McpStdioServerConfig#args` is filtered. The `url` of `McpHttpServerConfig` / `McpSSEServerConfig` prints as scheme, host and port only (`"https://mcp.example.com/[FILTERED]"`), since a token can sit in its userinfo, path or query. Raw Hash server configs are filtered the same way (`type`, `command` and the redacted `url` are shown; `args`, `env`, `headers` and any other key print as `"[FILTERED]"`): inside `mcp_servers`, where they used to rely on the nesting limit, and in `McpServerStatus#config`, where the CLI echoes each server's `headers`, `url` and `args` back in `mcp_status`. Display only: the objects are not modified and nothing sent to the CLI changes. ([#151](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/151)) +- **A materialized resume directory preserved after a mirror failure now holds only `projects/`.** The cleanup removed four known files and left whatever else the CLI had written — including `backups/.claude.json.backup.<ts>`, a full copy of the seeded `.claude.json`, which can hold MCP header secrets. Every entry except `projects/` is now deleted. To do that safely the SDK first moves the directory into a fresh private one next to it (`claude-preserved-resume-*`), so **the preserved transcript is no longer at the temp dir's old path: the warning names the new one.** If what it moved is not the directory it created (the path had been replaced by a symlink or another directory), or it cannot be moved, nothing is deleted and the warning says the scrub was skipped and why; symlinks are never followed, permissions are repaired only on the directory that was checked, an entry that cannot be removed is named in the warning, and an interrupted scrub says which directory to remove once the transcript is imported. Calling `disconnect` again after a teardown that was interrupted while it preserved the directory no longer deletes it. ([#159](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/159)) +- **`ClaudeAgentSDK::Railtie.callback_wrapper` no longer reports to `Rails.error`.** In production it ran callbacks inside `executor.wrap`, which reports whatever passes through it as an unhandled error: a message-block exception was reported from the callback thread with an empty context (and then skipped by the request / job layer as already reported), exceptions the SDK handles itself (hook, `can_use_tool`, SDK MCP tool, observer) were reported although the run continued, and — on Rails 7.2.3, 8.0.2 and later — so were the SDK's own cancellations under `callback_scheduling: :inline`. The wrapper now enters the executor with `run!` / `complete!` (same hooks, no report), so an exception that escapes a callback is reported once, by the layer that called the SDK, with its controller / job context. If you relied on those reports to see hook or tool failures, report them inside the callback (`Rails.error.report`). ([#158](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/158)) +- **A callback that fails with `NotImplementedError`, `LoadError`, `SystemStackError` or `SecurityError` is answered like any other failing callback.** These are not `StandardError`s, so nothing rescued them when a hook, a `can_use_tool` callback, an SDK MCP tool, resource or prompt handler, or the `callback_wrapper` raised one: the CLI never got a response for that request, and the exception stopped the whole Async reactor, taking every other session and task on it down (`query()` raised the raw exception). Now hooks and `can_use_tool` get the error response, a tool handler's failure reaches the model as an `isError: true` result, and a resource or prompt handler gets the JSON-RPC `-32603` error, exactly as for a `RuntimeError`; the session carries on. `SdkMcpServer#call_tool` called directly returns the `isError` result for these exceptions instead of raising. `exit`, `Interrupt`, signals and cancellation behave as before, and `NoMemoryError` is still fatal. ([#150](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/150)) +- **A callback failure whose message is not valid UTF-8 is answered.** When a hook or `can_use_tool` callback raised an exception whose message held invalid bytes (a multibyte character cut by `byteslice`, binary subprocess or HTTP output), building the error response raised `JSON::GeneratorError` and the CLI was never answered. On the process-exit path the same error replaced the `SystemExit` the SDK promises to re-raise: under `:inline` scheduling `exit` did not end the process. An `exit` or signal exception whose message was in an encoding that is not ASCII-compatible (UTF-16) failed the same way, with `Encoding::CompatibilityError`. The error text is now normalized first — invalid bytes become U+FFFD, text in another valid encoding is transcoded — and a response that still cannot be serialized is replaced by a fixed message, so the request is always answered and the original exception is the one re-raised. ([#150](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/150)) +- **`query()` with a streamed (Enumerator) prompt no longer closes stdin right after writing a message.** With hooks, `can_use_tool` or SDK MCP servers, stdin could close before that message's turn had produced a frame, so the turn's hook, permission and SDK MCP requests failed with "Stream closed". It happened when the CLI's `idle` for the previous run and the next message became ready in the same reactor pass: `Query#end_run` stopped the between-turns ceiling task (which yields) before it marked the run ended, and the message joined the run that was about to end. The run is now marked ended first. ([#154](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/154)) +- **A `Client` control method called from a fiber on another thread's reactor no longer breaks the session.** `interrupt`, `set_model`, `set_permission_mode`, `mcp_status`, … called through `Sync { client.interrupt }` on a request thread while the session runs on a background reactor, or from a `Sync` block inside a `:thread`-mode callback: on async 2.10–2.28 the response raised `FiberError: fiber called across threads` in the session's read loop, which ended the session and left the caller waiting for the control-request timeout (20 minutes by default); on async ≥ 2.29 the response could be missed, with the same wait. Such callers now wait the way plain threads already did. ([#154](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/154)) +- **`receive_response` / `receive_messages` no longer hang after the CLI is gone.** After the CLI exited or crashed while a `Client` was connected, the first call raised the stream error (or returned the last turn), the second returned without messages, and every later call hung forever with no error and no timeout (from plain Ruby after the reactor had finished, async 2.10 raised `NoMethodError` instead). Every call after the end of the stream now returns at once; the stream error is still raised once. ([#154](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/154)) +- **A deadline around a `Client` call is no longer swallowed while an observer runs.** With observers configured, a deadline around `Client#receive_response`, `#receive_messages`, `#query` or `#disconnect` (`task.with_timeout`, or `Timeout.timeout` inside a reactor) was silently dropped when it expired while an observer callback was still running: nothing was raised and the turn carried on, calling the message block after the deadline. Only what the observer (or its `callback_wrapper`) raises is contained now. With `callback_scheduling: :inline` the observer runs on the caller's own fiber, where such a deadline cannot be told from the observer's own failure, and it is still contained. ([#154](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/154)) +- **`Client#disconnect`, and the cleanup of a failing `Client#connect`, always tear the session down.** When the call was interrupted while an observer's `on_close` / `on_error` was running — the caller's task stopped (`Async::Stop`), or an inline hook's cooperative timeout fired — the CLI process, the session's read loop and the materialized resume directory (with its credentials copy) were left behind. The teardown now always runs, and the interruption propagates after it. ([#154](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/154)) +- **A `Client#disconnect` from another thread completes the CLI's graceful shutdown.** From an application thread, or from a tool handler, hook or `can_use_tool` callback under the default `callback_scheduling: :thread`, the close was handed to a transient task on the session's reactor; when the session had nothing else left to do the reactor wound down and stopped that task mid-teardown, so the CLI got SIGTERM instead of stdin EOF and its 5-second grace period (the time it has to finish its last session write), and `disconnect` returned before the child was reaped. The close now runs on a task the reactor waits for: such a `disconnect` takes as long as the teardown, like one made on the reactor. ([#161](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/161)) +- **A callback's thread hop no longer returns before the callback has finished.** When an exception (`Async::Stop`, a timeout) had been raised into the waiting fiber just as the previous hop's thread ended, the stale wakeup resumed the next hop, which returned `nil` as its result; with a `timeout:` (session-store adapter calls) the same wakeup was reported as `timed out` although no time had passed. `FiberBoundary.invoke` now waits until the thread has really finished or the deadline has really passed. ([#154](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/154)) +- An error raised by `ClaudeAgentSDK.query` / `.ask` from synchronous code (CLI not found, `ProcessError`, `ResultError`, an exception in your own block) was also logged by Async as `Task may have ended with unhandled exception.`, with the message and a backtrace, even when your code rescued it. The error is now only raised. Inside a reactor the same applied to failures before the first suspension, such as a transport that cannot connect. ([#154](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/154)) +- **A `stderr:` callback that raised `NotImplementedError`, `LoadError` or `SystemStackError` could hang the session.** Only `StandardError` was contained, so those ended the thread that reads the CLI's stderr; once the CLI had written another 64 KiB of stderr it blocked for good. They are now handled like any other callback error: dropped, and the callback is called again for the next line. The same holds for a `debug_stderr:` sink and for the SDK's own sandbox warning when a custom `Warning.warn` or `$stderr` raises one of them. ([#156](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/156)) +- **Store-backed resume now authenticates on macOS with a custom `CLAUDE_CONFIG_DIR`.** There the CLI keeps its OAuth credentials in the Keychain under `Claude Code-credentials-<first 8 hex of SHA-256(config dir)>` and writes no `.credentials.json`; the SDK only bridged the default config dir's entry, so the resumed subprocess reported "Not logged in" (and the failed attempt was mirrored into the store). The Keychain is now consulted for a custom config dir too — only when that directory has no `.credentials.json`, and never under `ANTHROPIC_API_KEY` / `CLAUDE_CODE_OAUTH_TOKEN`. The seeded copy still has the refresh token removed and mode `0600`. ([#159](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/159)) +- **A mirror flush cancelled while it waited behind an in-flight append no longer loses its batch.** The batcher detached the pending entries before queueing on its lock, so a `#flush` whose task was stopped there (a timeout around the caller, `Task#stop`) dropped them: they never reached the store and no `MirrorErrorMessage` was produced. The buffer is now detached inside the lock, so those entries go out with the next flush; a frame that arrives while the final flush is still waiting is delivered rather than counted as dropped (Python [#1289](https://github.com/anthropics/claude-agent-sdk-python/pull/1289)). The mirror's retry backoff uses `Kernel#sleep` instead of the deprecated `Async::Task#sleep`. ([#159](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/159)) +- **`get_session_messages` and `get_subagent_messages` (disk and `session_store:`) no longer drop the results of parallel tool calls.** The CLI parents each `tool_result` on the entry holding its own `tool_use`, so with several tool calls in one assistant turn only the last-written result lay on the path the readers walk; the others were silently missing, leaving `tool_use` blocks without a `tool_result`. They are now returned, after the turn's `tool_use` messages and before the result the conversation continued from; the chain's own result wins, so a call answered again after a rewind is not returned twice, and the result of a branch a rewind dropped is not brought back. Affected sessions return more messages than before, so `offset:`/`limit:` pages over them shift. ([#157](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/157)) +- **Session reads, listings and mutations — other fixes** ([#157](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/157)): - `get_session_messages` (disk and store) no longer returns `[]` for a session whose last entry is an unanswered meta injection (a slash-command or skill body, a stop-hook message, a system reminder written just before the session was closed). - The message readers no longer raise `Encoding::CompatibilityError` under a non-UTF-8 locale (`LANG=C` / `LC_ALL=C`), nor on a transcript whose last line was cut inside a multibyte character or that holds a raw non-UTF-8 byte (a torn line is skipped; invalid bytes come back as U+FFFD); `import_session_to_store` no longer stops half-way on such a line. Under `LC_ALL=C` a non-ASCII `CLAUDE_CONFIG_DIR`, `directory: Dir.pwd` and a non-ASCII worktree path no longer raise or hide every worktree. - `list_sessions(directory:)` no longer leaves out the directory's own sessions when it is a subdirectory of a repository with several git worktrees; worktree paths git reports in decomposed Unicode form are NFC-normalized. @@ -77,28 +78,28 @@ Fixes from a review of the whole SDK, a reference for every option (`docs/option - `get_session_info(session_store:)` reports the adapter's `mtime` as `last_modified`, as `list_sessions(session_store:)` does; the store subagent readers skip non-String `list_subkeys` values; `rename_session` / `tag_session` raise `ArgumentError` for a title or tag that is not a usable String (`tag: false` used to clear the tag, only `nil` does; a binary String, as `File.binread` returns, is read as UTF-8 instead of failing later with a JSON or encoding error); `list_subagents` on disk returns each id once. - An optional `SessionStore` method that raises `NotImplementedError` when called (a stub behind a delegating wrapper, an adapter declining at run time) is treated as not implemented — the documented fallback is taken and `run_session_store_conformance` skips its contracts — while a required `load` that raises still fails the resume. - `docs/sessions.md`: rename / tag / fork are safe while the session's CLI runs; delete only sessions whose CLI has exited. -- **`CLIInstaller.install` no longer deadlocks when two fibers of one `Async` reactor install into the same directory.** The second fiber blocked the reactor thread in `flock` while the first, which held the lock, was waiting on the network inside the critical section, so neither returned; with the default `version: 'stable'` this happened even when nothing needed installing. A waiting installer now polls the lock (every 50 ms) instead of blocking its thread. Threads and separate processes were never affected, and mutual exclusion is unchanged. ([#152](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/152)) -- **`CLIInstaller.install` / `install_pinned` no longer fail in a read-only install directory when the requested version is already installed.** In an image built as root and run as another user, or on a read-only root filesystem, a boot-time `install_pinned` raised `CLIInstallError` (`EACCES`) because the lock file could not be opened, although the binary was intact. When the lock file cannot be opened (`EACCES`, `EROFS`, `EPERM`), a concrete version that is installed and matches its recorded checksum and platform is now returned without the lock. A dist-tag (`'stable'` / `'latest'`), a different version or a damaged binary still raises there. ([#152](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/152)) -- **`CLIInstaller` honors `HTTPS_PROXY` / `https_proxy`.** `Net::HTTP` only looks at `http_proxy` unless it is told otherwise, even for a TLS connection, so an environment that exported only `HTTPS_PROXY` was bypassed, and where direct egress is blocked the install timed out with no mention of the proxy. The proxy for each request (redirect hops included) is now resolved from the `https` URL and passed explicitly, with its credentials; `NO_PROXY` / `no_proxy` apply as before, and an environment that only sets `http_proxy` keeps working. `ALL_PROXY` is not read, and only `http://` proxy URLs are used. `docs/cli-installer.md` has a new "Proxies and custom CAs" section (`HTTPS_PROXY`, `NO_PROXY`, `SSL_CERT_FILE`). ([#152](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/152)) -- **`CLIInstaller` fsyncs what it publishes.** The downloaded binary and `VERSION` are flushed to disk before they are renamed into place (the binary again after it is made executable, so its mode survives too), and the directory after it (best-effort). Previously a power loss shortly after an install could leave an empty or partial executable `claude` under the published name, which discovery kept selecting until the next `install`. ([#152](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/152)) -- `CLIInstaller.install` with no `dir:` names the default directory (`vendor/claude`) in its error when the process's working directory no longer exists, instead of an empty path. `docs/cli-installer.md` states the locked sequence in the order the code uses (record `VERSION`, then place the binary), and that a failed first install never publishes a binary — it can leave the directory, its lock file and, after a failed final rename, a recorded `VERSION`. ([#152](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/152)) -- **Shorthand tool schemas with `Array` / `Hash` parameters are advertised correctly.** `{ order_id: Integer, tags: Array }` told the model `tags` was a string, so it sent `"fragile,gift"` and the handler got a String; a model that did send an array had the call rejected. `Array` / `:array` now map to `array` and `Hash` / `:object` to `object`, in `tools/list` and in `tools/call` argument validation alike. Other shorthand values are unchanged. `docs/mcp-servers.md` lists the shorthand types. ([#152](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/152)) -- **A tool handler that returns a non-Array `:content` gets a clear error.** `{ content: 'text' }`, a single block Hash, or a present `false` or `nil`, was forwarded unchanged; the CLI rejected it and told the model the server "returned a malformed result", so the text never arrived. It is now an in-band `isError` result naming the tool: `Tool 'x' must return :content as an Array of content blocks (got String)`. A result without a `:content` key keeps the existing message. Return the String itself, or `content: [{ type: 'text', text: ... }]`. ([#152](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/152)) -- **`create_tool(annotations: { maxResultSizeChars: … }, meta: { … })` keeps the size hint.** An explicit `meta:` replaced the `_meta['anthropic/maxResultSizeChars']` entry derived from the annotation, so large results were truncated to a preview again. The two are now merged; a size key set in `meta:` wins. ([#152](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/152)) -- **`OTelObserver` no longer counts an API response's token usage once per content block.** The CLI sends one `AssistantMessage` per content block, and each repeats the response's `message_id` and `usage`. The observer put that usage on every `claude_agent.generation` span, so a response with a thinking block and a tool call reported its input, cache-read and cache-creation tokens twice (N times for N blocks). The `gen_ai.usage.*` token attributes are now set on the first generation span of each `message_id` only. Span count, names and parentage are unchanged, and the session span was already correct. **Sums of `gen_ai.usage.*` over generation spans drop by the duplication factor.** `gen_ai.usage.output_tokens` on a generation span is still the snapshot taken when the response started; `docs/observability.md` now says so, and that the session span carries the authoritative totals. ([#148](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/148)) -- `examples/advanced_hooks_example.rb` and `examples/lifecycle_hooks_example.rb` returned a `*HookSpecificOutput#to_h` as the whole hook output, which leaves `permissionDecision`, `additionalContext`, `retry` and `watchPaths` at the top level where the CLI does not read them — the example's "deny" did not deny. They now wrap the typed output in `SyncHookJSONOutput`. If you copied that pattern, wrap yours the same way (or return `{ hook_specific_output: … }`). ([#149](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/149)) -- `agents:` accepts a Hash for an agent, as its signature says: `agents: { reviewer: { description: '…', prompt: '…' } }` used to raise `NoMethodError` at connect. The Hash is read like `AgentDefinition.new(hash)`, so a misspelled key raises the same `ArgumentError`. ([#149](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/149)) -- `allowed_tools`, `disallowed_tools`, `add_dirs`, `extra_args`, `env` and `load_timeout_ms` can be set back to `nil` after construction (`options.dup_with(allowed_tools: nil)`, `options.extra_args = nil`), as their signatures say. The command builder, the transport and store-backed resume used to raise an anonymous `NoMethodError` at connect; they now read `nil` as the default. ([#149](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/149), [#156](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/156), [#159](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/159)) -- The `sandbox:` option now replaces a `sandbox` section already present in `settings:` instead of being added beside it. Settings given as a JSON String, a file path or a String-keyed Hash produced a `--settings` value with the key twice: under json 3.x connect raised a raw `JSON::GeneratorError: detected duplicate key "sandbox"`, which made the documented `sandbox: false` override unusable; under json 2.x it only worked as long as the CLI kept the last duplicate. ([#149](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/149)) -- Hooks registered for one event under both a String and a Symbol key (`{ 'PreToolUse' => [a], PreToolUse: [b] }`) are all registered, in the order written. The second key used to replace the first silently, so `a` never ran. ([#149](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/149)) -- **`#[]` returns `nil` for a writer's name instead of raising.** `msg[:session_id=]`, `msg['sessionId=']` and `options[:env=]` called the setter without an argument (`ArgumentError: wrong number of arguments`); they now read as `nil`, like any other name that is not an attribute. ([#151](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/151)) -- **`ClaudeAgentOptions` reports `'[]'` as an unknown option.** `ClaudeAgentOptions.new('[]' => 1)` raised `wrong number of arguments (given 1, expected 2)`; it now raises the usual `unknown ClaudeAgentOptions option: "[]"`. The keys `'='`, `'!'` and `'=='` used to resolve to `#==`, `#!=` and `#===` and were dropped without an error; they now raise the same `ArgumentError`. Setters you define on your own `ClaudeAgentOptions` subclass are still accepted. ([#151](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/151)) -- **`Type#inspect` no longer raises on a value it cannot render.** A `BasicObject` attribute raised `NoMethodError` (`nil?`), and a String, Hash or Array subclass whose `#length` / `#size` raises took `#inspect` down with it — inside a logger or `puts`. Such a value now shows as `#<ClassName>` (`#<?>` for a `BasicObject`) and the rest of the object still prints. `Interrupt`, `SystemExit` and other non-`StandardError` exceptions are not rescued. ([#151](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/151)) -- **`cli_path:` given as a `Pathname` failed at connect.** The signature has always allowed it, but the spawn raised `no implicit conversion of Pathname into String`. The transport now converts it once, before the version check and the spawn. ([#156](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/156)) -- No more `Async::Task#sleep is deprecated` warnings under `ruby -w` from the transport: while it waited for the CLI to exit, or for the version check, it printed one every 50 ms. It now uses `Kernel#sleep`, which parks only the calling fiber on a reactor. ([#156](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/156)) -- The Postgres reference adapter (`examples/session_stores/`) stores entries in a `json` column instead of `jsonb`, which rejects the JSON escape for U+0000: one transcript entry with a NUL character (binary tool output) failed the whole `INSERT` and the mirror dropped that batch. An existing table is not changed by `create_schema`; the README gives the migration (`ALTER TABLE … ALTER COLUMN entry TYPE json USING entry::json`). ([#159](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/159)) -- **`.yardopts` is packaged in the gem.** rubydoc.info builds the API reference from the gem's own files; without it YARD ran with its defaults, so the reference listed every `@api private` internal (`Query`, `FiberBoundary`, `MessageParser`, …) next to the public API and did not render `CHANGELOG.md` or `UPGRADING-1.0.md`. Takes effect on rubydoc.info from this release on. ([#155](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/155)) -- **Documentation that contradicted the code** ([#155](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/155)): +- **`CLIInstaller.install` no longer deadlocks when two fibers of one `Async` reactor install into the same directory.** The second fiber blocked the reactor thread in `flock` while the first, which held the lock, was waiting on the network inside the critical section, so neither returned; with the default `version: 'stable'` this happened even when nothing needed installing. A waiting installer now polls the lock (every 50 ms) instead of blocking its thread. Threads and separate processes were never affected, and mutual exclusion is unchanged. ([#152](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/152)) +- **`CLIInstaller.install` / `install_pinned` no longer fail in a read-only install directory when the requested version is already installed.** In an image built as root and run as another user, or on a read-only root filesystem, a boot-time `install_pinned` raised `CLIInstallError` (`EACCES`) because the lock file could not be opened, although the binary was intact. When the lock file cannot be opened (`EACCES`, `EROFS`, `EPERM`), a concrete version that is installed and matches its recorded checksum and platform is now returned without the lock. A dist-tag (`'stable'` / `'latest'`), a different version or a damaged binary still raises there. ([#152](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/152)) +- **`CLIInstaller` honors `HTTPS_PROXY` / `https_proxy`.** `Net::HTTP` only looks at `http_proxy` unless it is told otherwise, even for a TLS connection, so an environment that exported only `HTTPS_PROXY` was bypassed, and where direct egress is blocked the install timed out with no mention of the proxy. The proxy for each request (redirect hops included) is now resolved from the `https` URL and passed explicitly, with its credentials; `NO_PROXY` / `no_proxy` apply as before, and an environment that only sets `http_proxy` keeps working. `ALL_PROXY` is not read, and only `http://` proxy URLs are used. `docs/cli-installer.md` has a new "Proxies and custom CAs" section (`HTTPS_PROXY`, `NO_PROXY`, `SSL_CERT_FILE`). ([#152](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/152)) +- **`CLIInstaller` fsyncs what it publishes.** The downloaded binary and `VERSION` are flushed to disk before they are renamed into place (the binary again after it is made executable, so its mode survives too), and the directory after it (best-effort). Previously a power loss shortly after an install could leave an empty or partial executable `claude` under the published name, which discovery kept selecting until the next `install`. ([#152](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/152)) +- `CLIInstaller.install` with no `dir:` names the default directory (`vendor/claude`) in its error when the process's working directory no longer exists, instead of an empty path. `docs/cli-installer.md` states the locked sequence in the order the code uses (record `VERSION`, then place the binary), and that a failed first install never publishes a binary — it can leave the directory, its lock file and, after a failed final rename, a recorded `VERSION`. ([#152](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/152)) +- **Shorthand tool schemas with `Array` / `Hash` parameters are advertised correctly.** `{ order_id: Integer, tags: Array }` told the model `tags` was a string, so it sent `"fragile,gift"` and the handler got a String; a model that did send an array had the call rejected. `Array` / `:array` now map to `array` and `Hash` / `:object` to `object`, in `tools/list` and in `tools/call` argument validation alike. Other shorthand values are unchanged. `docs/mcp-servers.md` lists the shorthand types. ([#152](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/152)) +- **A tool handler that returns a non-Array `:content` gets a clear error.** `{ content: 'text' }`, a single block Hash, or a present `false` or `nil`, was forwarded unchanged; the CLI rejected it and told the model the server "returned a malformed result", so the text never arrived. It is now an in-band `isError` result naming the tool: `Tool 'x' must return :content as an Array of content blocks (got String)`. A result without a `:content` key keeps the existing message. Return the String itself, or `content: [{ type: 'text', text: ... }]`. ([#152](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/152)) +- **`create_tool(annotations: { maxResultSizeChars: … }, meta: { … })` keeps the size hint.** An explicit `meta:` replaced the `_meta['anthropic/maxResultSizeChars']` entry derived from the annotation, so large results were truncated to a preview again. The two are now merged; a size key set in `meta:` wins. ([#152](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/152)) +- **`OTelObserver` no longer counts an API response's token usage once per content block.** The CLI sends one `AssistantMessage` per content block, and each repeats the response's `message_id` and `usage`. The observer put that usage on every `claude_agent.generation` span, so a response with a thinking block and a tool call reported its input, cache-read and cache-creation tokens twice (N times for N blocks). The `gen_ai.usage.*` token attributes are now set on the first generation span of each `message_id` only. Span count, names and parentage are unchanged, and the session span was already correct. **Sums of `gen_ai.usage.*` over generation spans drop by the duplication factor.** `gen_ai.usage.output_tokens` on a generation span is still the snapshot taken when the response started; `docs/observability.md` now says so, and that the session span carries the authoritative totals. ([#148](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/148)) +- `examples/advanced_hooks_example.rb` and `examples/lifecycle_hooks_example.rb` returned a `*HookSpecificOutput#to_h` as the whole hook output, which leaves `permissionDecision`, `additionalContext`, `retry` and `watchPaths` at the top level where the CLI does not read them — the example's "deny" did not deny. They now wrap the typed output in `SyncHookJSONOutput`. If you copied that pattern, wrap yours the same way (or return `{ hook_specific_output: … }`). ([#149](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/149)) +- `agents:` accepts a Hash for an agent, as its signature says: `agents: { reviewer: { description: '…', prompt: '…' } }` used to raise `NoMethodError` at connect. The Hash is read like `AgentDefinition.new(hash)`, so a misspelled key raises the same `ArgumentError`. ([#149](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/149)) +- `allowed_tools`, `disallowed_tools`, `add_dirs`, `extra_args`, `env` and `load_timeout_ms` can be set back to `nil` after construction (`options.dup_with(allowed_tools: nil)`, `options.extra_args = nil`), as their signatures say. The command builder, the transport and store-backed resume used to raise an anonymous `NoMethodError` at connect; they now read `nil` as the default. ([#149](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/149), [#156](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/156), [#159](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/159)) +- The `sandbox:` option now replaces a `sandbox` section already present in `settings:` instead of being added beside it. Settings given as a JSON String, a file path or a String-keyed Hash produced a `--settings` value with the key twice: under json 3.x connect raised a raw `JSON::GeneratorError: detected duplicate key "sandbox"`, which made the documented `sandbox: false` override unusable; under json 2.x it only worked as long as the CLI kept the last duplicate. ([#149](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/149)) +- Hooks registered for one event under both a String and a Symbol key (`{ 'PreToolUse' => [a], PreToolUse: [b] }`) are all registered, in the order written. The second key used to replace the first silently, so `a` never ran. ([#149](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/149)) +- **`#[]` returns `nil` for a writer's name instead of raising.** `msg[:session_id=]`, `msg['sessionId=']` and `options[:env=]` called the setter without an argument (`ArgumentError: wrong number of arguments`); they now read as `nil`, like any other name that is not an attribute. ([#151](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/151)) +- **`ClaudeAgentOptions` reports `'[]'` as an unknown option.** `ClaudeAgentOptions.new('[]' => 1)` raised `wrong number of arguments (given 1, expected 2)`; it now raises the usual `unknown ClaudeAgentOptions option: "[]"`. The keys `'='`, `'!'` and `'=='` used to resolve to `#==`, `#!=` and `#===` and were dropped without an error; they now raise the same `ArgumentError`. Setters you define on your own `ClaudeAgentOptions` subclass are still accepted. ([#151](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/151)) +- **`Type#inspect` no longer raises on a value it cannot render.** A `BasicObject` attribute raised `NoMethodError` (`nil?`), and a String, Hash or Array subclass whose `#length` / `#size` raises took `#inspect` down with it — inside a logger or `puts`. Such a value now shows as `#<ClassName>` (`#<?>` for a `BasicObject`) and the rest of the object still prints. `Interrupt`, `SystemExit` and other non-`StandardError` exceptions are not rescued. ([#151](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/151)) +- **`cli_path:` given as a `Pathname` failed at connect.** The signature has always allowed it, but the spawn raised `no implicit conversion of Pathname into String`. The transport now converts it once, before the version check and the spawn. ([#156](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/156)) +- No more `Async::Task#sleep is deprecated` warnings under `ruby -w` from the transport: while it waited for the CLI to exit, or for the version check, it printed one every 50 ms. It now uses `Kernel#sleep`, which parks only the calling fiber on a reactor. ([#156](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/156)) +- The Postgres reference adapter (`examples/session_stores/`) stores entries in a `json` column instead of `jsonb`, which rejects the JSON escape for U+0000: one transcript entry with a NUL character (binary tool output) failed the whole `INSERT` and the mirror dropped that batch. An existing table is not changed by `create_schema`; the README gives the migration (`ALTER TABLE … ALTER COLUMN entry TYPE json USING entry::json`). ([#159](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/159)) +- **`.yardopts` is packaged in the gem.** rubydoc.info builds the API reference from the gem's own files; without it YARD ran with its defaults, so the reference listed every `@api private` internal (`Query`, `FiberBoundary`, `MessageParser`, …) next to the public API and did not render `CHANGELOG.md` or `UPGRADING-1.0.md`. Takes effect on rubydoc.info from this release on. ([#155](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/155)) +- **Documentation that contradicted the code** ([#155](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/155)): - `docs/types.md` lists every message and content-block class the SDK can yield (`ToolProgressMessage`, `AuthStatusMessage`, `ToolUseSummaryMessage`, `PromptSuggestionMessage`, `ServerToolUseBlock` and `ServerToolResultBlock` were missing), with a table of the system and progress messages and what `#data` holds; the `SandboxIgnoreViolations` row (removed in 0.12.0) is gone. `docs/errors.md` documents `CLIInstallError`, and that a control request the CLI rejects raises a plain `StandardError`, not a `ClaudeSDKError`. - Hooks, custom tools and permission callbacks do not require `Client` (`docs/client.md`, the README and the bundled skill said they did); `query()` and `ask` have run all three for many releases. - The custom-transport contract is stated once: `connect`, `write`, `read_messages`, `end_input` and `close` are required, `ready?` is optional. `docs/client.md` listed six methods and the bundled skill four (a transport written from the skill's list made `query()` hang). The YARD of `ClaudeAgentSDK::Transport` no longer calls it an internal API. @@ -107,7 +108,7 @@ Fixes from a review of the whole SDK, a reference for every option (`docs/option - Four clarifications, each checked against Claude Code 2.1.287: `sandbox: { enabled: true }` does not make the sandbox a requirement (set `fail_if_unavailable: true`; otherwise the CLI warns on stderr and runs unsandboxed); `setting_sources: []` does not stop the MCP servers of the logged-in claude.ai account (`strict_mcp_config: true` does); `Client#query(prompt, session_id:)` does not start a separate conversation; an explicit `tools:` list must include `'Skill'` for `skills:` to work. Sandbox settings travel inside `--settings`, not a `--sandbox` flag. - The `Client` examples in the guides and the `Client` YARD use `Client.open` (the `connect` … `disconnect` shape they showed leaves the session open when the block raises). - `.claude-plugin/marketplace.json` no longer declares a version for the plugin (it said `0.13.1`); `SECURITY.md` states the supported versions for 1.x; `examples/message_types_example.rb` handles all 28 typed message classes. - - In the pages other PRs of this batch own: the documented Gemfile lines for the OpenTelemetry setup list the `base64` gem, which is no longer a default gem on Ruby 3.4 (`docs/observability.md`, [#148](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/148)); `docs/cli-installer.md` says an unusable `CLAUDE_CLI_PATH` is skipped without a warning and discovery continues, instead of "no discovery at all" ([#156](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/156)); `docs/rails.md` says hooks and custom tools work with `query` too ([#158](https://github.com/ya-luotao/claude-agent-sdk-ruby/pull/158)). + - In the pages other PRs of this batch own: the documented Gemfile lines for the OpenTelemetry setup list the `base64` gem, which is no longer a default gem on Ruby 3.4 (`docs/observability.md`, [#148](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/148)); `docs/cli-installer.md` says an unusable `CLAUDE_CLI_PATH` is skipped without a warning and discovery continues, instead of "no discovery at all" ([#156](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/156)); `docs/rails.md` says hooks and custom tools work with `query` too ([#158](https://github.com/rubycatco/claude-agent-sdk-ruby/pull/158)). ## [1.1.0] - 2026-09-30 @@ -133,39 +134,39 @@ Syncs with Python SDK 0.2.162. Additive only: one new option and a fix to when ` **1.0 is a stability commitment.** From here on the public API — everything documented in `docs/` plus the YARD docs without `@api private`, now also described by the RBS signatures in `sig/` — follows Semantic Versioning: no breaking changes within 1.x. Upgrading from 0.x? Read [UPGRADING-1.0.md](UPGRADING-1.0.md) and run your suite on 0.37 first. -1.0 ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)): three breaking changes, the first two of which 0.37 warns about at the call site. **Read [UPGRADING-1.0.md](UPGRADING-1.0.md) before upgrading** and run your suite on 0.37 with warnings visible first: an app that runs on 0.37 without SDK warnings is unaffected by those two, except that `respond_to?` on a camelCase non-attribute (`msg.respond_to?(:toH)`) silently answered `true` on 0.37 and answers `false` now. +1.0 ([#126](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)): three breaking changes, the first two of which 0.37 warns about at the call site. **Read [UPGRADING-1.0.md](UPGRADING-1.0.md) before upgrading** and run your suite on 0.37 with warnings visible first: an app that runs on 0.37 without SDK warnings is unaffected by those two, except that `respond_to?` on a camelCase non-attribute (`msg.respond_to?(:toH)`) silently answered `true` on 0.37 and answers `false` now. ### Added - **`ClaudeAgentSDK::SessionStoreError`** (a `ClaudeSDKError`), raised by `query`, `ask` and `Client#connect` when resuming from `session_store:` fails: a store call (`#load`, `#list_sessions`, `#list_subkeys`) raised or exceeded `load_timeout_ms` while the SDK materialized the transcript. The message names the store call, and `#cause` holds the adapter's exception (or the timeout). Documented in `docs/errors.md` and `docs/sessions.md`. -- **RBS signatures for the public API** ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). The gem now ships `sig/`, so Steep and other RBS tools type-check code that uses it (through `rbs collection`; `sig/manifest.yaml` declares the one stdlib dependency, `pathname`). The signatures cover the whole public surface (the objects documented in `docs/` and YARD without `@api private`): `ClaudeAgentSDK.query` / `.ask` / `.configure` / `.offload`, the SDK MCP helpers, every session function (including `session_store:` and the deprecated twins), `Client` with its Ruby-style aliases, `ClaudeAgentOptions` (every option as a typed keyword), the message, content-block, hook, permission and MCP types, the error hierarchy, `CLIInstaller`, `Streaming`, `Railtie.callback_wrapper` and the observers. Duck types are interfaces, so any object with the right methods fits: `_Transport`, `_SessionStore` (only `#append` and `#load` are required), and one interface per user callback (`_CanUseTool`, `_HookCallback`, `_ToolHandler`, `_CallbackWrapper`, ...). Hashes follow the documented key rule: `wire_hash` (`Hash[Symbol, untyped]`) for data from the CLI stream, `transcript_hash` (`Hash[String, untyped]`) for transcripts and store data. Internals tagged `@api private` have no signatures. CI validates the signatures (`rake rbs:validate`) and runs the suite under RBS's runtime type checker (`rake rbs:test`), so a signature that disagrees with the code fails the build. `CONTRIBUTING.md` explains how to keep `sig/` in step with API changes. +- **RBS signatures for the public API** ([#126](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)). The gem now ships `sig/`, so Steep and other RBS tools type-check code that uses it (through `rbs collection`; `sig/manifest.yaml` declares the one stdlib dependency, `pathname`). The signatures cover the whole public surface (the objects documented in `docs/` and YARD without `@api private`): `ClaudeAgentSDK.query` / `.ask` / `.configure` / `.offload`, the SDK MCP helpers, every session function (including `session_store:` and the deprecated twins), `Client` with its Ruby-style aliases, `ClaudeAgentOptions` (every option as a typed keyword), the message, content-block, hook, permission and MCP types, the error hierarchy, `CLIInstaller`, `Streaming`, `Railtie.callback_wrapper` and the observers. Duck types are interfaces, so any object with the right methods fits: `_Transport`, `_SessionStore` (only `#append` and `#load` are required), and one interface per user callback (`_CanUseTool`, `_HookCallback`, `_ToolHandler`, `_CallbackWrapper`, ...). Hashes follow the documented key rule: `wire_hash` (`Hash[Symbol, untyped]`) for data from the CLI stream, `transcript_hash` (`Hash[String, untyped]`) for transcripts and store data. Internals tagged `@api private` have no signatures. CI validates the signatures (`rake rbs:validate`) and runs the suite under RBS's runtime type checker (`rake rbs:test`), so a signature that disagrees with the code fails the build. `CONTRIBUTING.md` explains how to keep `sig/` in step with API changes. ### Changed - **Breaking: unknown keys on user-constructed types raise `ArgumentError`.** `.new` and `#[]=` on the value types you build and pass in (option values such as `AgentDefinition`, `SandboxSettings`, the thinking and MCP server configs; `HookMatcher`; hook outputs; `PermissionResultAllow` / `PermissionResultDeny`; `PermissionUpdate`; `PermissionRuleValue`) raise instead of warning, with a message naming the class, the key and the known keys: `ClaudeAgentSDK::HookMatcher: unknown attribute :matchr (known: hooks, matcher, timeout)`. camelCase and String keys and a type's own discriminator are still accepted; `.from_hash`, `.wrap` and every type parsed from CLI output stay lenient. - **Breaking: `Type#[]`, `#[]=` and camelCase methods reach attributes only.** A name that is not an attribute behaves like an undefined one: `msg[:to_h]` is `nil`, `#[]=` ignores it (raises on the strict types above), `msg.toH` raises `NoMethodError` and `respond_to?(:toH)` is `false`. Methods your own code adds to a subclass, mixin or instance still count as attributes. - **Breaking: store-backed resume failures raise `SessionStoreError` instead of `RuntimeError`.** The two SDK-raised `RuntimeError`s (store call failed, store call timed out) become `SessionStoreError`, and a `RuntimeError` raised by the adapter itself, which 0.37 let through unwrapped, is now wrapped too, so `rescue ClaudeSDKError` catches every resume-materialization failure. Code that rescued `RuntimeError` there must rescue `SessionStoreError`. The failure message now includes the adapter exception's class (`... failed during resume materialization: IOError: connection reset`). - `UPGRADING-1.0.md` ships in the gem and the YARD docs, linked from the README. -- The deprecation warning printed by the ten `*_from_store` / `*_via_store` session functions, and their YARD and docs, now say they will be removed in **2.0**. 0.36 and 0.37 said 1.0, but they stay, deprecated, for all of 1.x ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). Nothing else about them changes: each still works and still warns once per process. +- The deprecation warning printed by the ten `*_from_store` / `*_via_store` session functions, and their YARD and docs, now say they will be removed in **2.0**. 0.36 and 0.37 said 1.0, but they stay, deprecated, for all of 1.x ([#126](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)). Nothing else about them changes: each still works and still warns once per process. ## [0.37.0] - 2026-09-23 -The last 0.x release before 1.0 ([roadmap](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). No runtime behaviour changes — only new warnings for things 1.0 will reject. **Run your suite on 0.37 with warnings visible before moving to 1.0:** +The last 0.x release before 1.0 ([roadmap](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)). No runtime behaviour changes — only new warnings for things 1.0 will reject. **Run your suite on 0.37 with warnings visible before moving to 1.0:** - A misspelled key on a value type you build (`HookMatcher`, `AgentDefinition`, `SandboxSettings`, MCP server configs, permission results, hook outputs, …) now warns once, pointing at your call; 1.0 raises `ArgumentError`. - `msg[:name]` / camelCase readers reaching a non-attribute method (e.g. `msg[:to_h]`) warn once; in 1.0 they only reach attributes. - The 1.0 SemVer surface is now visible: SDK internals are tagged `@api private` and hidden from the API docs, and the documented contracts (Hash-key rule, `mcp_status` / `context_usage` return Hashes, `Type#[]`) are written down. ### Changed -- **Docs: pre-1.0 contracts written down** ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). No code changes. `Client#mcp_status` / `#get_mcp_status` and `#context_usage` / `#get_context_usage` return the CLI's raw Hash with camelCase Symbol keys. `docs/types.md` had described `McpStatusResponse` as the response type; it is the optional typed view, via `McpStatusResponse.parse(client.mcp_status)`, and there is no typed context-usage class (`docs/client.md`). A new "Hash keys" section in `docs/types.md` states one rule: Hashes passed through from the live CLI stream (`origin`, `usage`, `model_usage`, hook `tool_input`, `can_use_tool` input, SDK MCP tool args, control responses) have Symbol keys spelled as on the wire, and Hashes read from transcripts or a `SessionStore` (`SessionMessage#message`, `get_subagent_metadata`, store keys and entries) have String keys. The docs, examples and bundled skill drop their `h[:key] || h['key']` fallbacks in favour of the one correct form. `Type#[]`, `#[]=` and the camelCase readers are documented as public API, including that `#[]=` changes the object you received. `docs/sessions.md` documents `ClaudeAgentSDK.project_key_for_directory` and `ClaudeAgentSDK.fold_session_summary` for adapter authors, and `import_session_to_store`'s batching (`batch_size:` defaults to 500 entries, and a batch also ends at about 1 MiB). -- **SDK internals are now tagged `@api private` and hidden from the YARD docs** ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). The tagged namespaces are `Query`, `MessageParser`, `TranscriptMirrorBatcher`, `Sessions`, `SessionMutations`, `SessionResume`, `MaterializedResume`, `SessionSummary`, `SessionStores`, `OptionWarnings` and `FiberBoundary`, including everything nested in them. Some public classes also have internal members, and those are tagged individually: `SubprocessCLITransport`'s methods outside the `Transport` interface and its constants; `CommandBuilder`'s constants (`.new` and `#build` stay public); `CLIInstaller::Http`, `Platform`, `Release` and `Metadata` and the installer's constants other than `PINNED_CLI_VERSION`; `SdkMcpServer#handle_json`, `#handle_message` and `SdkMcpServer::ToolInputSchema`; `Client#query_handler`; and `ClaudeAgentSDK::OBSERVER_INTERFACE`. Their public faces are unchanged: `ClaudeAgentSDK.offload`, `fold_session_summary`, the session functions and `SDKSessionInfo` / `SessionMessage` remain public. Nothing changes at runtime. No method or constant was renamed, removed or made private, so every existing call keeps working. From 1.0, SemVer covers `docs/` and the YARD API without `@api private`; objects tagged `@api private` are not covered and may change in any release. `CONTRIBUTING.md` has a new "What is public API" section. -- `lib/claude_agent_sdk/types.rb` is split into one file per area under `lib/claude_agent_sdk/types/` (messages, hooks, options, ...). The code moved without edits and `types.rb` still loads every part, so `require 'claude_agent_sdk/types'` and every class and constant are unchanged; no API change ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). -- `Type`'s internal machinery is tagged `@api private` and leaves the rendered docs: `Type#dup_for_options`, `Type.deep_dup_for_options`, `Type::OptionValue`, `Type.inspect_filtered`, `Type.inspect_filtered_attributes`, and the new `Type.strict_attributes` / `.strict_attributes?` / `.declare_attributes` / `.attribute?` / `.attribute_names`. They are not part of the SemVer surface. `Type#[]`, `#[]=`, the camelCase readers, `#to_h`, `.wrap` and `.from_hash` stay public ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). +- **Docs: pre-1.0 contracts written down** ([#126](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)). No code changes. `Client#mcp_status` / `#get_mcp_status` and `#context_usage` / `#get_context_usage` return the CLI's raw Hash with camelCase Symbol keys. `docs/types.md` had described `McpStatusResponse` as the response type; it is the optional typed view, via `McpStatusResponse.parse(client.mcp_status)`, and there is no typed context-usage class (`docs/client.md`). A new "Hash keys" section in `docs/types.md` states one rule: Hashes passed through from the live CLI stream (`origin`, `usage`, `model_usage`, hook `tool_input`, `can_use_tool` input, SDK MCP tool args, control responses) have Symbol keys spelled as on the wire, and Hashes read from transcripts or a `SessionStore` (`SessionMessage#message`, `get_subagent_metadata`, store keys and entries) have String keys. The docs, examples and bundled skill drop their `h[:key] || h['key']` fallbacks in favour of the one correct form. `Type#[]`, `#[]=` and the camelCase readers are documented as public API, including that `#[]=` changes the object you received. `docs/sessions.md` documents `ClaudeAgentSDK.project_key_for_directory` and `ClaudeAgentSDK.fold_session_summary` for adapter authors, and `import_session_to_store`'s batching (`batch_size:` defaults to 500 entries, and a batch also ends at about 1 MiB). +- **SDK internals are now tagged `@api private` and hidden from the YARD docs** ([#126](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)). The tagged namespaces are `Query`, `MessageParser`, `TranscriptMirrorBatcher`, `Sessions`, `SessionMutations`, `SessionResume`, `MaterializedResume`, `SessionSummary`, `SessionStores`, `OptionWarnings` and `FiberBoundary`, including everything nested in them. Some public classes also have internal members, and those are tagged individually: `SubprocessCLITransport`'s methods outside the `Transport` interface and its constants; `CommandBuilder`'s constants (`.new` and `#build` stay public); `CLIInstaller::Http`, `Platform`, `Release` and `Metadata` and the installer's constants other than `PINNED_CLI_VERSION`; `SdkMcpServer#handle_json`, `#handle_message` and `SdkMcpServer::ToolInputSchema`; `Client#query_handler`; and `ClaudeAgentSDK::OBSERVER_INTERFACE`. Their public faces are unchanged: `ClaudeAgentSDK.offload`, `fold_session_summary`, the session functions and `SDKSessionInfo` / `SessionMessage` remain public. Nothing changes at runtime. No method or constant was renamed, removed or made private, so every existing call keeps working. From 1.0, SemVer covers `docs/` and the YARD API without `@api private`; objects tagged `@api private` are not covered and may change in any release. `CONTRIBUTING.md` has a new "What is public API" section. +- `lib/claude_agent_sdk/types.rb` is split into one file per area under `lib/claude_agent_sdk/types/` (messages, hooks, options, ...). The code moved without edits and `types.rb` still loads every part, so `require 'claude_agent_sdk/types'` and every class and constant are unchanged; no API change ([#126](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)). +- `Type`'s internal machinery is tagged `@api private` and leaves the rendered docs: `Type#dup_for_options`, `Type.deep_dup_for_options`, `Type::OptionValue`, `Type.inspect_filtered`, `Type.inspect_filtered_attributes`, and the new `Type.strict_attributes` / `.strict_attributes?` / `.declare_attributes` / `.attribute?` / `.attribute_names`. They are not part of the SemVer surface. `Type#[]`, `#[]=`, the camelCase readers, `#to_h`, `.wrap` and `.from_hash` stay public ([#126](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)). ### Deprecated -- **Unknown keys on user-constructed types.** The value types you build and pass in (option values such as `AgentDefinition`, `SandboxSettings`, the thinking configs and the MCP server configs; `HookMatcher`; hook outputs; `PermissionResultAllow` / `PermissionResultDeny`; `PermissionUpdate`; `PermissionRuleValue`) silently dropped a misspelled key, so `HookMatcher.new(matchr: 'Bash')` quietly built a matcher with no `matcher` set. `.new` and `#[]=` now warn once per class and key, at the caller's line: `ClaudeAgentSDK::HookMatcher: unknown attribute :matchr ignored; this will raise ArgumentError in 1.0 (known: hooks, matcher, timeout)`. **In 1.0 they raise `ArgumentError`**, as `ClaudeAgentOptions` already does. camelCase and String keys and a type's own discriminator (`type`, `hook_event_name`, `behavior`) are accepted, so `klass.new(value.to_h)` round-trips silently. Types parsed from CLI output (messages, content blocks, hook inputs) and every construction through `.from_hash` / `.wrap` stay lenient, so a newer CLI's extra fields never warn; permission suggestions from the CLI are now hydrated through `PermissionUpdate.wrap` for that reason. Full list in `docs/types.md` ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). -- **`Type#[]`, `#[]=` and camelCase methods reaching something other than an attribute.** They are public API for a type's attributes (its `attr_*` fields, the backward-compatible `RateLimitEvent#data`, predicates such as `options.forkSession?`, and any method your own code adds to a subclass, mixin or instance), but they resolved any public method: `msg[:to_h]` returned a Hash, `msg['freeze']` froze the message, `msg.toH` called `#to_h`. Such a call still works but warns once per class and name, e.g. `ClaudeAgentSDK::ResultMessage#[]: :to_h is not an attribute; Type#[] will only read attributes in 1.0`. **In 1.0 a name that is not an attribute behaves like an undefined one:** `#[]` returns `nil`, `#[]=` ignores it (raises on the types above), a camelCase call raises `NoMethodError`, and `respond_to?` says `false`. Names that are not methods at all keep today's behaviour and do not warn. `UserMessage#text` / `AssistantMessage#text` are convenience methods, not attributes ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)). +- **Unknown keys on user-constructed types.** The value types you build and pass in (option values such as `AgentDefinition`, `SandboxSettings`, the thinking configs and the MCP server configs; `HookMatcher`; hook outputs; `PermissionResultAllow` / `PermissionResultDeny`; `PermissionUpdate`; `PermissionRuleValue`) silently dropped a misspelled key, so `HookMatcher.new(matchr: 'Bash')` quietly built a matcher with no `matcher` set. `.new` and `#[]=` now warn once per class and key, at the caller's line: `ClaudeAgentSDK::HookMatcher: unknown attribute :matchr ignored; this will raise ArgumentError in 1.0 (known: hooks, matcher, timeout)`. **In 1.0 they raise `ArgumentError`**, as `ClaudeAgentOptions` already does. camelCase and String keys and a type's own discriminator (`type`, `hook_event_name`, `behavior`) are accepted, so `klass.new(value.to_h)` round-trips silently. Types parsed from CLI output (messages, content blocks, hook inputs) and every construction through `.from_hash` / `.wrap` stay lenient, so a newer CLI's extra fields never warn; permission suggestions from the CLI are now hydrated through `PermissionUpdate.wrap` for that reason. Full list in `docs/types.md` ([#126](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)). +- **`Type#[]`, `#[]=` and camelCase methods reaching something other than an attribute.** They are public API for a type's attributes (its `attr_*` fields, the backward-compatible `RateLimitEvent#data`, predicates such as `options.forkSession?`, and any method your own code adds to a subclass, mixin or instance), but they resolved any public method: `msg[:to_h]` returned a Hash, `msg['freeze']` froze the message, `msg.toH` called `#to_h`. Such a call still works but warns once per class and name, e.g. `ClaudeAgentSDK::ResultMessage#[]: :to_h is not an attribute; Type#[] will only read attributes in 1.0`. **In 1.0 a name that is not an attribute behaves like an undefined one:** `#[]` returns `nil`, `#[]=` ignores it (raises on the types above), a camelCase call raises `NoMethodError`, and `respond_to?` says `false`. Names that are not methods at all keep today's behaviour and do not warn. `UserMessage#text` / `AssistantMessage#text` are convenience methods, not attributes ([#126](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)). ## [0.36.0] - 2026-09-23 -The first step on the [road to 1.0](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126): one `session_store:` argument for every session function (the store-specific twins are deprecated), `ClaudeAgentSDK.ask`, String tool results, and the last audit follow-ups (#119–#121). **Read before upgrading:** +The first step on the [road to 1.0](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126): one `session_store:` argument for every session function (the store-specific twins are deprecated), `ClaudeAgentSDK.ask`, String tool results, and the last audit follow-ups (#119–#121). **Read before upgrading:** - An `exit`, `Interrupt` or signal raised inside a hook, `can_use_tool` or SDK MCP handler now **ends the process after the CLI gets its error response**. Since 0.34.0 a tool handler's `exit` was turned into an `isError` result and the process kept running, which also swallowed a real Ctrl-C or SIGTERM in `:inline` mode. - The `*_from_store` / `*_via_store` session functions print a one-time deprecation warning; switch to `list_sessions(session_store: store)` etc. (table under **Deprecated**). - Local-disk session APIs raise `ConfigDirError` (not `ArgumentError`) on hosts without a home directory; a session with no prompt has `first_prompt` `nil` on the disk path too (was `''`). @@ -176,7 +177,7 @@ The first step on the [road to 1.0](https://github.com/ya-luotao/claude-agent-sd - **Ruby-style `Client` method names next to the Python-parity ones**: `client.model = 'haiku'` (`set_model`), `client.permission_mode = 'plan'` (`set_permission_mode`), `client.context_usage` (`get_context_usage`) and `client.mcp_status` (`get_mcp_status`). Each delegates to its parity counterpart, so both spellings send the same control request, raise the same `CLIConnectionError` before `connect`, and an override of the parity method covers both. The parity names stay, so code ported from the Python docs keeps working. (`server_info` / `get_server_info` already existed as a pair.) `docs/client.md` lists both spellings. - **`CLIInstaller.root` — a configurable root for the vendored CLI.** `CLIInstaller.default_dir` (and so `install`, `install_pinned`, `installed_path` without `dir:`, and the transport's discovery of the vendored binary) is `vendor/claude` under `CLIInstaller.root` when it is set, and under the working directory at call time when it is `nil`, the default. A process whose cwd is not the project root, such as a daemonized worker, can now find the vendored binary instead of falling through to `PATH`. The setter takes a String or Pathname, absolutizes it once, and rejects an empty path; `nil` restores the working-directory default. Unset, nothing changes, and an explicit `dir:` still wins. - **The Railtie anchors CLI discovery to `Rails.root`**: an initializer sets `CLIInstaller.root ||= Rails.root` before `config/initializers` run, so an app initializer can override it and a root set in `config/application.rb` is kept. The `claude_agent_sdk:install_cli` task, which does not boot the app, installs under an explicitly set `CLIInstaller.root` if there is one, and under `Rails.root/vendor/claude` as before otherwise. The generated initializer's commented `cli_path:` line no longer describes a cwd workaround. `docs/cli-installer.md` gains a "Where `vendor/claude` is" section. -Groundwork for 1.0 ([#126](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues/126)): the sessions API collapses to one function per operation, and the store-specific twins are deprecated. Nothing is removed; every existing call keeps working. +Groundwork for 1.0 ([#126](https://github.com/rubycatco/claude-agent-sdk-ruby/issues/126)): the sessions API collapses to one function per operation, and the store-specific twins are deprecated. Nothing is removed; every existing call keeps working. - **`session_store:` on every session function.** `list_sessions`, `get_session_info`, `get_session_messages`, `list_subagents`, `get_subagent_metadata`, `get_subagent_messages`, `rename_session`, `tag_session`, `delete_session` and `fork_session` take an optional `session_store:`. Omitted or `nil`, they work on local disk exactly as before; given a store, they run the same code the `*_from_store` / `*_via_store` function did, with the same arguments. Two differences between the paths are documented in `docs/sessions.md`: `directory: nil` means every project on disk but the current working directory with a store (a store cannot enumerate projects), and `include_worktrees:` is disk-only: with `session_store:`, `list_sessions` accepts only the default `true` and raises `ArgumentError` for `false` or `nil` instead of silently ignoring the filter. - **`ClaudeAgentSDK::ConfigDirError`** (a `ClaudeSDKError`), raised by the local-disk session APIs when the Claude config directory cannot be located: `CLAUDE_CONFIG_DIR` is unset and there is no usable home directory for the default `~/.claude`. Its message says to set `CLAUDE_CONFIG_DIR` (#120). - CI: a macOS leg (Ruby 3.4) for the main suite; simplecov coverage (`COVERAGE=1 bundle exec rspec`) on the Linux Ruby 3.4 leg, with line/branch totals in the job summary and the HTML report as an artifact; and a weekly real-CLI integration run (`.github/workflows/integration.yml`) against `CLIInstaller::PINNED_CLI_VERSION`, also triggered by PRs that touch the installer. Dependabot keeps the workflows' actions current. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4aa5d3b6..33ed4347 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ Thanks for helping out. This is a community-maintained Ruby SDK for the Claude C You need Ruby 3.2 or newer. ```bash -git clone https://github.com/ya-luotao/claude-agent-sdk-ruby.git +git clone https://github.com/rubycatco/claude-agent-sdk-ruby.git cd claude-agent-sdk-ruby bundle install bundle exec rake # unit suite + RuboCop, the same checks as CI's main leg diff --git a/README.md b/README.md index 422eb5f7..d988acf5 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,22 @@ -![Claude Agent SDK for Ruby — a ruby connecting code to terminal, tools, and messages](https://raw.githubusercontent.com/ya-luotao/claude-agent-sdk-ruby/main/assets/readme-banner.webp) +![Claude Agent SDK for Ruby — a ruby connecting code to terminal, tools, and messages](https://raw.githubusercontent.com/rubycatco/claude-agent-sdk-ruby/main/assets/readme-banner.webp) # Claude Agent SDK for Ruby [![Gem Version](https://img.shields.io/gem/v/claude-agent-sdk)](https://rubygems.org/gems/claude-agent-sdk) -[![CI](https://github.com/ya-luotao/claude-agent-sdk-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/ya-luotao/claude-agent-sdk-ruby/actions/workflows/ci.yml) +[![CI](https://github.com/rubycatco/claude-agent-sdk-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/rubycatco/claude-agent-sdk-ruby/actions/workflows/ci.yml) [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.2-CC342D)](https://www.ruby-lang.org/) [![Docs](https://img.shields.io/badge/docs-rubydoc.info-blue)](https://rubydoc.info/gems/claude-agent-sdk) -[![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/LICENSE) +[![License: MIT](https://img.shields.io/badge/license-MIT-green)](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/LICENSE) A Ruby SDK for the [Claude Code](https://docs.claude.com/en/docs/claude-code-overview) agent runtime, built for running agents in production Ruby and Rails apps. It has the same capabilities as the official [TypeScript](https://github.com/anthropics/claude-agent-sdk-typescript) and [Python](https://github.com/anthropics/claude-agent-sdk-python) SDKs, plus what a Rails deploy needs around them: a generator and CLI-vendoring rake task, callbacks that are safe to touch ActiveRecord from, a pinned CLI binary, built-in OpenTelemetry tracing, and transcript mirroring to your own storage. -> **Unofficial and community-maintained.** This project is not affiliated with or supported by Anthropic. It tracks the official SDKs release by release; see the [CHANGELOG](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/CHANGELOG.md) for the currently synced version. +> **Unofficial and community-maintained.** This project is not affiliated with or supported by Anthropic. It tracks the official SDKs release by release; see the [CHANGELOG](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/CHANGELOG.md) for the currently synced version. -> **Upgrading from 0.x?** 1.0 raises on unknown keys, limits `#[]` to attributes and adds `SessionStoreError`. [UPGRADING-1.0.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/UPGRADING-1.0.md) has the checklist. +> **Upgrading from 0.x?** 1.0 raises on unknown keys, limits `#[]` to attributes and adds `SessionStoreError`. [UPGRADING-1.0.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/UPGRADING-1.0.md) has the checklist. ## Highlights -- **Rails integration.** `bin/rails generate claude_agent_sdk:install` writes the initializer and `bin/rails claude_agent_sdk:install_cli` vendors the CLI; [docs/rails.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/rails.md) covers jobs, ActionCable streaming, session resumption, and solid_queue fiber workers (`callback_scheduling: :inline`). +- **Rails integration.** `bin/rails generate claude_agent_sdk:install` writes the initializer and `bin/rails claude_agent_sdk:install_cli` vendors the CLI; [docs/rails.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/rails.md) covers jobs, ActionCable streaming, session resumption, and solid_queue fiber workers (`callback_scheduling: :inline`). - **Callbacks that are safe around ActiveRecord.** Tool handlers, hooks, permission callbacks, and message blocks run on a plain thread by default, outside the SDK's fiber scheduler, so thread-keyed libraries (ActiveRecord, `pg`, per-thread caches) behave as they do everywhere else in your app. `ClaudeAgentSDK::Railtie.callback_wrapper` runs them in the Rails executor so connections go back to the pool, without deadlocking development code reloading. - **Hermetic deploys.** `CLIInstaller` vendors a checksum-verified CLI binary, pinned to the version each gem release is tested with, so production never depends on a global `npm install`. - **Built-in OpenTelemetry observer** with Langfuse support; no third-party instrumentation library required. @@ -34,7 +34,7 @@ A Ruby SDK for the [Claude Code](https://docs.claude.com/en/docs/claude-code-ove gem 'claude-agent-sdk', '~> 1.0' ``` -Then `bundle install`, or install directly with `gem install claude-agent-sdk`. To track unreleased changes, point the Gemfile at GitHub: `gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'`. +Then `bundle install`, or install directly with `gem install claude-agent-sdk`. To track unreleased changes, point the Gemfile at GitHub: `gem 'claude-agent-sdk', github: 'rubycatco/claude-agent-sdk-ruby'`. **Prerequisites** @@ -47,7 +47,7 @@ Then `bundle install`, or install directly with `gem install claude-agent-sdk`. ClaudeAgentSDK::CLIInstaller.install_pinned # => "/app/vendor/claude/claude" ``` -`install_pinned` installs `CLIInstaller::PINNED_CLI_VERSION`, so upgrading the gem carries the CLI forward with it; `CLIInstaller.install(version: 'x.y.z')` pins a version of your own. The vendored binary is found ahead of `PATH`, installs are idempotent and concurrency-safe, and a failed upgrade never breaks a working install. See [docs/cli-installer.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/cli-installer.md) for the full behaviour, supported platforms, and the CLI discovery order. +`install_pinned` installs `CLIInstaller::PINNED_CLI_VERSION`, so upgrading the gem carries the CLI forward with it; `CLIInstaller.install(version: 'x.y.z')` pins a version of your own. The vendored binary is found ahead of `PATH`, installs are idempotent and concurrency-safe, and a failed upgrade never breaks a working install. See [docs/cli-installer.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/cli-installer.md) for the full behaviour, supported platforms, and the CLI discovery order. ### Authentication @@ -57,7 +57,7 @@ The SDK holds no credentials of its own. The `claude` process it starts authenti - `CLAUDE_CODE_OAUTH_TOKEN`, a long-lived token for a Claude subscription (`claude setup-token` creates one), set the same way - a login the CLI has already stored for the user your process runs as (`claude auth login`) -A machine with none of them, such as a fresh container or a CI runner, does not fail at startup. The first prompt comes back as an `AssistantMessage` whose `error` is `'authentication_failed'` (its text is "Not logged in · Please run /login", a command an SDK host cannot run), followed by a `ResultMessage` with `is_error` set. `query()` and `ask` then raise `ResultError` with `terminal_reason == 'api_error'`; a `Client` session stays open, so check the result's `is_error` there. A key or token the API rejects ends the same way, with `api_error_status` 401, but only after the CLI has retried: watch for `APIRetryMessage` (ten of them over about three minutes when tested) rather than waiting for the error. [docs/errors.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/errors.md) shows how to handle both. +A machine with none of them, such as a fresh container or a CI runner, does not fail at startup. The first prompt comes back as an `AssistantMessage` whose `error` is `'authentication_failed'` (its text is "Not logged in · Please run /login", a command an SDK host cannot run), followed by a `ResultMessage` with `is_error` set. `query()` and `ask` then raise `ResultError` with `terminal_reason == 'api_error'`; a `Client` session stays open, so check the result's `is_error` there. A key or token the API rejects ends the same way, with `api_error_status` 401, but only after the CLI has retried: watch for `APIRetryMessage` (ten of them over about three minutes when tested) rather than waiting for the error. [docs/errors.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/errors.md) shows how to handle both. ### Rails in a minute @@ -81,7 +81,7 @@ class SummarizeTicketJob < ApplicationJob end ``` -The block runs on a plain thread, so ActiveRecord calls inside it just work. [docs/rails.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/rails.md) continues with multi-turn sessions, ActionCable streaming, and fiber workers. +The block runs on a plain thread, so ActiveRecord calls inside it just work. [docs/rails.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/rails.md) continues with multi-turn sessions, ActionCable streaming, and fiber workers. ## Quick Start @@ -100,7 +100,7 @@ end puts result ``` -It raises the same errors as `query()` (see [docs/errors.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/errors.md)), plus `CLIConnectionError` if the stream ends without a result. +It raises the same errors as `query()` (see [docs/errors.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/errors.md)), plus `CLIConnectionError` if the stream ends without a result. ### `query()` — one-shot and streaming @@ -159,7 +159,7 @@ ensure end ``` -See [docs/client.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/client.md) for `interrupt`, mid-session model and permission switching, MCP status, and custom transports. +See [docs/client.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/client.md) for `interrupt`, mid-session model and permission switching, MCP status, and custom transports. ### Custom tools (SDK MCP servers) @@ -178,7 +178,7 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new( ) ``` -A String return is sent to Claude as a single text block. Return a Hash instead (`{ content: [...], is_error: true }`) to flag an error, attach `structured_content:`, or send several content blocks or images. Arguments are validated against the tool's JSON Schema before your handler runs, and handler exceptions are reported back to the model in-band so it can self-correct. See [docs/mcp-servers.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/mcp-servers.md) for resources, prompts, mixed SDK + external servers, and schema details. +A String return is sent to Claude as a single text block. Return a Hash instead (`{ content: [...], is_error: true }`) to flag an error, attach `structured_content:`, or send several content blocks or images. Arguments are validated against the tool's JSON Schema before your handler runs, and handler exceptions are reported back to the model in-band so it can self-correct. See [docs/mcp-servers.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/mcp-servers.md) for resources, prompts, mixed SDK + external servers, and schema details. ### Hooks and permission callbacks @@ -191,39 +191,39 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new( ) ``` -See [docs/hooks-and-permissions.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/hooks-and-permissions.md) for the full event list and worked examples. +See [docs/hooks-and-permissions.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/hooks-and-permissions.md) for the full event list and worked examples. ## Documentation | Topic | Guide | |-------|-------| -| `Client` advanced features and custom transports | [docs/client.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/client.md) | -| SDK MCP servers: tools, resources, prompts, schema compatibility | [docs/mcp-servers.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/mcp-servers.md) | -| All hook events, typed inputs, permission callbacks | [docs/hooks-and-permissions.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/hooks-and-permissions.md) | -| Structured output, thinking, budget, fallback and advisor models, sandbox, bare mode, session isolation, checkpointing | [docs/configuration.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/configuration.md) | -| Every `ClaudeAgentOptions` attribute: type, default, and the CLI flag or protocol field it becomes; environment variables | [docs/options.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/options.md) | -| Session listing, reading, renaming, tagging, forking, resume-at-message | [docs/sessions.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/sessions.md) | -| Subagent capabilities, event contracts, and minimal example | [docs/subagents.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/subagents.md) | -| OpenTelemetry tracing, Langfuse, custom observers | [docs/observability.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/observability.md) | -| Rails: generator, `install_cli` task, callback wrapper, fiber safety, solid_queue fiber workers, ActionCable, jobs | [docs/rails.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/rails.md) | -| Vendoring a pinned CLI binary and CLI discovery order | [docs/cli-installer.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/cli-installer.md) | -| Hash-key rule, attribute access, and the message, content block, and configuration type reference | [docs/types.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/types.md) | -| Error handling, exception hierarchy, timeouts | [docs/errors.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/errors.md) | +| `Client` advanced features and custom transports | [docs/client.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/client.md) | +| SDK MCP servers: tools, resources, prompts, schema compatibility | [docs/mcp-servers.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/mcp-servers.md) | +| All hook events, typed inputs, permission callbacks | [docs/hooks-and-permissions.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/hooks-and-permissions.md) | +| Structured output, thinking, budget, fallback and advisor models, sandbox, bare mode, session isolation, checkpointing | [docs/configuration.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/configuration.md) | +| Every `ClaudeAgentOptions` attribute: type, default, and the CLI flag or protocol field it becomes; environment variables | [docs/options.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/options.md) | +| Session listing, reading, renaming, tagging, forking, resume-at-message | [docs/sessions.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/sessions.md) | +| Subagent capabilities, event contracts, and minimal example | [docs/subagents.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/subagents.md) | +| OpenTelemetry tracing, Langfuse, custom observers | [docs/observability.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/observability.md) | +| Rails: generator, `install_cli` task, callback wrapper, fiber safety, solid_queue fiber workers, ActionCable, jobs | [docs/rails.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/rails.md) | +| Vendoring a pinned CLI binary and CLI discovery order | [docs/cli-installer.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/cli-installer.md) | +| Hash-key rule, attribute access, and the message, content block, and configuration type reference | [docs/types.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/types.md) | +| Error handling, exception hierarchy, timeouts | [docs/errors.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/errors.md) | API reference: [rubydoc.info/gems/claude-agent-sdk](https://rubydoc.info/gems/claude-agent-sdk). Available built-in tools: [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code/settings#tools-available-to-claude). ## Examples -Runnable scripts live in [`examples/`](https://github.com/ya-luotao/claude-agent-sdk-ruby/tree/main/examples). +Runnable scripts live in [`examples/`](https://github.com/rubycatco/claude-agent-sdk-ruby/tree/main/examples). | Area | Examples | |------|----------| -| Getting started | [quick_start](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/quick_start.rb) · [client](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/client_example.rb) · [streaming_input](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/streaming_input_example.rb) · [message_types](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/message_types_example.rb) · [error_handling](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/error_handling_example.rb) | -| Sessions and output | [session_resumption](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/session_resumption_example.rb) · [structured_output](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/structured_output_example.rb) · [extended_thinking](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/extended_thinking_example.rb) · [session_stores/](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/session_stores/README.md) | -| Tools and MCP | [mcp_calculator](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/mcp_calculator.rb) · [mcp_resources_prompts](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/mcp_resources_prompts_example.rb) · [http_mcp_server](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/http_mcp_server_example.rb) | -| Hooks and permissions | [hooks](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/hooks_example.rb) · [advanced_hooks](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/advanced_hooks_example.rb) · [lifecycle_hooks](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb) · [permission_callback](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/permission_callback_example.rb) | -| Models and limits | [budget_control](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/budget_control_example.rb) · [fallback_model](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/fallback_model_example.rb) · [advisor](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/advisor_example.rb) · [bare_mode](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/bare_mode_example.rb) · [sandbox](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/sandbox_example.rb) | -| Rails, observability, transports | [rails_actioncable](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/rails_actioncable_example.rb) · [rails_background_job](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/rails_background_job_example.rb) · [otel_langfuse](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/otel_langfuse_example.rb) · [e2b_transport](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/e2b_transport_example.rb) | +| Getting started | [quick_start](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/quick_start.rb) · [client](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/client_example.rb) · [streaming_input](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/streaming_input_example.rb) · [message_types](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/message_types_example.rb) · [error_handling](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/error_handling_example.rb) | +| Sessions and output | [session_resumption](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/session_resumption_example.rb) · [structured_output](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/structured_output_example.rb) · [extended_thinking](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/extended_thinking_example.rb) · [session_stores/](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/session_stores/README.md) | +| Tools and MCP | [mcp_calculator](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/mcp_calculator.rb) · [mcp_resources_prompts](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/mcp_resources_prompts_example.rb) · [http_mcp_server](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/http_mcp_server_example.rb) | +| Hooks and permissions | [hooks](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/hooks_example.rb) · [advanced_hooks](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/advanced_hooks_example.rb) · [lifecycle_hooks](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb) · [permission_callback](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/permission_callback_example.rb) | +| Models and limits | [budget_control](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/budget_control_example.rb) · [fallback_model](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/fallback_model_example.rb) · [advisor](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/advisor_example.rb) · [bare_mode](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/bare_mode_example.rb) · [sandbox](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/sandbox_example.rb) | +| Rails, observability, transports | [rails_actioncable](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/rails_actioncable_example.rb) · [rails_background_job](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/rails_background_job_example.rb) · [otel_langfuse](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/otel_langfuse_example.rb) · [e2b_transport](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/e2b_transport_example.rb) | ## Comparison with the official SDKs @@ -256,7 +256,7 @@ Types are plain Ruby classes with `attr_accessor` and keyword arguments, mirrori This repository is also a Claude Code plugin marketplace. The bundled skill teaches Claude Code the gem's APIs and patterns: ```bash -/plugin marketplace add ya-luotao/claude-agent-sdk-ruby +/plugin marketplace add rubycatco/claude-agent-sdk-ruby /plugin install claude-agent-ruby@claude-agent-sdk-ruby ``` @@ -272,12 +272,12 @@ RUN_INTEGRATION=1 bundle exec rspec # also run the real-CLI integration suite ( BUNDLE_GEMFILE=gemfiles/rails_8.gemfile bundle exec rspec --options spec/rails/.rspec # Rails integration specs ``` -CI runs the suite and RuboCop on Ruby 3.2, 3.3, 3.4 and 4.0 on Linux, the suite on macOS, and the Rails specs against Rails 7.1 and 8, validates the RBS signatures and runs the suite under RBS runtime type checking. Weekly, and on PRs that touch the installer, the transport, `Query` or the message parser, it runs a keyless smoke test against the pinned CLI, and the integration suite as well when the repository has an API key. The gem ships RBS signatures for its public API in `sig/`, which Steep and other RBS tools pick up through `rbs collection`. See [CONTRIBUTING.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/CONTRIBUTING.md) for the development setup and [spec/README.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/spec/README.md) for the test layout. +CI runs the suite and RuboCop on Ruby 3.2, 3.3, 3.4 and 4.0 on Linux, the suite on macOS, and the Rails specs against Rails 7.1 and 8, validates the RBS signatures and runs the suite under RBS runtime type checking. Weekly, and on PRs that touch the installer, the transport, `Query` or the message parser, it runs a keyless smoke test against the pinned CLI, and the integration suite as well when the repository has an API key. The gem ships RBS signatures for its public API in `sig/`, which Steep and other RBS tools pick up through `rbs collection`. See [CONTRIBUTING.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/CONTRIBUTING.md) for the development setup and [spec/README.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/spec/README.md) for the test layout. ## Contributing -Bug reports and pull requests are welcome on [GitHub](https://github.com/ya-luotao/claude-agent-sdk-ruby/issues). Please include a failing spec with bug reports where possible, and keep pull requests focused on one change; [CONTRIBUTING.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/CONTRIBUTING.md) has the details. Report security vulnerabilities privately, as described in [SECURITY.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/SECURITY.md). Releases follow [Semantic Versioning](https://semver.org/) and are recorded in the [CHANGELOG](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/CHANGELOG.md). +Bug reports and pull requests are welcome on [GitHub](https://github.com/rubycatco/claude-agent-sdk-ruby/issues). Please include a failing spec with bug reports where possible, and keep pull requests focused on one change; [CONTRIBUTING.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/CONTRIBUTING.md) has the details. Report security vulnerabilities privately, as described in [SECURITY.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/SECURITY.md). Releases follow [Semantic Versioning](https://semver.org/) and are recorded in the [CHANGELOG](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/CHANGELOG.md). ## License -Released under the [MIT License](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/LICENSE). +Released under the [MIT License](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/LICENSE). diff --git a/SECURITY.md b/SECURITY.md index d8c06117..9a19795c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,7 +4,7 @@ Please report vulnerabilities privately. Don't open a public issue or PR. -Use [GitHub's private vulnerability reporting](https://github.com/ya-luotao/claude-agent-sdk-ruby/security/advisories/new) for this repository (Security tab → "Report a vulnerability"). If that form is unavailable, email the maintainer at the address in the gemspec (`luotao@hey.com`). +Use [GitHub's private vulnerability reporting](https://github.com/rubycatco/claude-agent-sdk-ruby/security/advisories/new) for this repository (Security tab → "Report a vulnerability"). If that form is unavailable, email the maintainer at the address in the gemspec (`luotao@hey.com`). Please include the affected gem version, a description of the issue and its impact, and a proof of concept or steps to reproduce if you have them. Follow-up happens on the private advisory thread. Fixes ship in a patch release, and the advisory is published once that release is out. diff --git a/UPGRADING-1.0.md b/UPGRADING-1.0.md index 90b7a66d..a95d0a81 100644 --- a/UPGRADING-1.0.md +++ b/UPGRADING-1.0.md @@ -114,7 +114,7 @@ No breaking changes within 1.x to the public API: everything documented in YARD docs that is not tagged `@api private`. `@api private` objects (`Query`, `MessageParser`, `FiberBoundary`, the `Sessions*` modules, ...) stay callable but can change in any release. See -[CONTRIBUTING.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/CONTRIBUTING.md#what-is-public-api). +[CONTRIBUTING.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/CONTRIBUTING.md#what-is-public-api). A removal is first deprecated in a minor release with a one-time warning that names the replacement, and happens in the next major. diff --git a/claude-agent-sdk.gemspec b/claude-agent-sdk.gemspec index fee68549..ba080774 100644 --- a/claude-agent-sdk.gemspec +++ b/claude-agent-sdk.gemspec @@ -14,13 +14,13 @@ Gem::Specification.new do |spec| 'integration (Railtie, install generator, CLI-vendoring rake task, executor-aware callback ' \ 'wrapper), a pinned CLI installer, OpenTelemetry tracing, and session transcript mirroring. ' \ 'Not affiliated with or officially maintained by Anthropic.' - spec.homepage = 'https://github.com/ya-luotao/claude-agent-sdk-ruby' + spec.homepage = 'https://github.com/rubycatco/claude-agent-sdk-ruby' spec.license = 'MIT' spec.required_ruby_version = '>= 3.2.0' spec.metadata['homepage_uri'] = spec.homepage - spec.metadata['source_code_uri'] = 'https://github.com/ya-luotao/claude-agent-sdk-ruby' - spec.metadata['changelog_uri'] = 'https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/CHANGELOG.md' + spec.metadata['source_code_uri'] = 'https://github.com/rubycatco/claude-agent-sdk-ruby' + spec.metadata['changelog_uri'] = 'https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/CHANGELOG.md' spec.metadata['documentation_uri'] = 'https://rubydoc.info/gems/claude-agent-sdk' spec.metadata['allowed_push_host'] = 'https://rubygems.org' diff --git a/docs/client.md b/docs/client.md index ede54813..0b82dd7e 100644 --- a/docs/client.md +++ b/docs/client.md @@ -158,7 +158,7 @@ Resuming from a `session_store` is not available over a custom transport, throug ### Reference: running `claude` inside an E2B sandbox -[`examples/e2b_transport_example.rb`](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/e2b_transport_example.rb) is a working transport that runs the Claude Code CLI inside an [E2B](https://e2b.dev) Firecracker microVM instead of on your host. The wire protocol stays identical — only the I/O layer changes: +[`examples/e2b_transport_example.rb`](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/e2b_transport_example.rb) is a working transport that runs the Claude Code CLI inside an [E2B](https://e2b.dev) Firecracker microVM instead of on your host. The wire protocol stays identical — only the I/O layer changes: ``` ClaudeAgentSDK::Client (host) diff --git a/docs/configuration.md b/docs/configuration.md index 120e5e6b..f108e5ed 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -37,7 +37,7 @@ end In the `output_format` Hash, `type` may be a String or a Symbol (`type: :json_schema`), and the keys Symbols or Strings. -See [examples/structured_output_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/structured_output_example.rb). +See [examples/structured_output_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/structured_output_example.rb). ## Thinking Configuration @@ -117,7 +117,7 @@ ClaudeAgentSDK.query(prompt: "Explain recursion", options: options) do |message| end ``` -See [examples/budget_control_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/budget_control_example.rb). +See [examples/budget_control_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/budget_control_example.rb). ## Fallback Model @@ -128,7 +128,7 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new( ) ``` -See [examples/fallback_model_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/fallback_model_example.rb). +See [examples/fallback_model_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/fallback_model_example.rb). ## Advisor Model @@ -162,7 +162,7 @@ Notes: - `CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1` (settable via `options.env`) disables the advisor tool entirely; a configured `advisor_model` is then ignored. -See [examples/advisor_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/advisor_example.rb) and the +See [examples/advisor_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/advisor_example.rb) and the [advisor documentation](https://code.claude.com/docs/en/advisor). ## Beta Features @@ -260,7 +260,7 @@ sandbox = ClaudeAgentSDK::SandboxSettings.new(enabled: true, fail_if_unavailable Without it, the sign is the CLI's warning on stderr ("Sandbox disabled: ... Commands will run WITHOUT sandboxing. Network and filesystem restrictions will NOT be enforced."), and stderr reaches your code only through the `stderr` (or `debug_stderr`) option. This is the CLI's own description of its behavior: the fallback has not been reproduced in this SDK's testing, where the sandbox was always available. -See [examples/sandbox_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/sandbox_example.rb). +See [examples/sandbox_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/sandbox_example.rb). When the `sandbox:` option enabled the sandbox and the CLI reports `Sandbox disabled: …` on its stderr, the SDK repeats that line as a Ruby warning prefixed `[claude-agent-sdk]`, once per session, whether or not you set `stderr:`. @@ -293,7 +293,7 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new( **What still works:** skills (via `/skill-name`), explicit `--add-dir` CLAUDE.md, `--settings`, `--mcp-config`, `--agents`, `--plugin-dir`, API key from `ANTHROPIC_API_KEY` env var. -See [examples/bare_mode_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/bare_mode_example.rb). +See [examples/bare_mode_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/bare_mode_example.rb). ## Verbatim Prompts diff --git a/docs/errors.md b/docs/errors.md index 51760bc6..bae0ec07 100644 --- a/docs/errors.md +++ b/docs/errors.md @@ -18,7 +18,7 @@ ClaudeAgentSDK.query(prompt: "Hello") do |message| end ``` -See [examples/error_handling_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/error_handling_example.rb). +See [examples/error_handling_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/error_handling_example.rb). ## Exception Handling @@ -196,4 +196,4 @@ end | `CLIJSONDecodeError` | JSON parsing issues — including stdout ending mid-frame (a truncated final message; `line` holds the partial frame) | | `MessageParseError` | Message parsing issues | -See [lib/claude_agent_sdk/errors.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/lib/claude_agent_sdk/errors.rb) for all error types. +See [lib/claude_agent_sdk/errors.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/lib/claude_agent_sdk/errors.rb) for all error types. diff --git a/docs/hooks-and-permissions.md b/docs/hooks-and-permissions.md index f0fc3067..85f04617 100644 --- a/docs/hooks-and-permissions.md +++ b/docs/hooks-and-permissions.md @@ -21,7 +21,7 @@ All hook input objects include common fields like `session_id`, `transcript_path `tool_input` (and the `input` a [permission callback](#permission-callbacks) receives) is the CLI's Hash passed through unchanged, so its keys are Symbols spelled as on the wire: `tool_input[:command]`, `input[:file_path]`. See [Hash keys](types.md#hash-keys). -All 27 hook events have typed input classes. See [`ClaudeAgentSDK::HOOK_EVENTS`](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/lib/claude_agent_sdk/types/hooks.rb) and [examples/lifecycle_hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb). +All 27 hook events have typed input classes. See [`ClaudeAgentSDK::HOOK_EVENTS`](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/lib/claude_agent_sdk/types/hooks.rb) and [examples/lifecycle_hooks_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb). `background_tasks` and `session_crons` are optional arrays of raw CLI hashes. `nil` means the CLI did not provide a snapshot; `[]` means it provided an empty @@ -76,7 +76,7 @@ Async do end.wait ``` -See [examples/hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/hooks_example.rb), [examples/advanced_hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/advanced_hooks_example.rb), and [examples/lifecycle_hooks_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb). +See [examples/hooks_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/hooks_example.rb), [examples/advanced_hooks_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/advanced_hooks_example.rb), and [examples/lifecycle_hooks_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/lifecycle_hooks_example.rb). ### Hook output @@ -148,7 +148,7 @@ Async do end.wait ``` -See [examples/permission_callback_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/permission_callback_example.rb). +See [examples/permission_callback_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/permission_callback_example.rb). ### Permission request cancellation diff --git a/docs/mcp-servers.md b/docs/mcp-servers.md index bc3d6f34..5c9e73d7 100644 --- a/docs/mcp-servers.md +++ b/docs/mcp-servers.md @@ -222,4 +222,4 @@ are covered here too. For naming the exception class, and then the exception propagates as Ruby normally would. -See [examples/mcp_calculator.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/mcp_calculator.rb) and [examples/mcp_resources_prompts_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/mcp_resources_prompts_example.rb) for complete examples. +See [examples/mcp_calculator.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/mcp_calculator.rb) and [examples/mcp_resources_prompts_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/mcp_resources_prompts_example.rb) for complete examples. diff --git a/docs/observability.md b/docs/observability.md index 121c1262..310233ff 100644 --- a/docs/observability.md +++ b/docs/observability.md @@ -204,4 +204,4 @@ end options = ClaudeAgentSDK::ClaudeAgentOptions.new(observers: [MyObserver.new]) ``` -See [examples/otel_langfuse_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/otel_langfuse_example.rb) for a complete multi-tool example. +See [examples/otel_langfuse_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/otel_langfuse_example.rb) for a complete multi-tool example. diff --git a/docs/rails.md b/docs/rails.md index 23af8641..81eab7a5 100644 --- a/docs/rails.md +++ b/docs/rails.md @@ -356,7 +356,7 @@ end Preconditions, spelled out: `:inline` is only correct when the process satisfies the same requirements as solid_queue's fiber workers — `isolation_level = :fiber`, Rails 7.2+ for AR, and no thread-keyed libraries used inside callbacks without a fiber-aware wrapper. The SDK warns once if it detects `:inline` under `isolation_level == :thread`. Everything else (Puma request threads, threaded Sidekiq/solid_queue workers) should stay on the default `callback_scheduling: :thread`. -Note that `SessionStore` adapter calls (`#append` / `#load`) stay on threads by default even in `:inline` mode — their timeouts are hard bounds (`Thread#join`) so a wedged store adapter can never stall the reactor. The exception is an adapter that declares itself fiber-native via an optional `callback_scheduling` method returning `:inline` (see "Fiber-native adapters" in [docs/sessions.md](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/sessions.md)); its calls then run on the reactor under a cooperative timeout. In both cases a configured `callback_wrapper` composes around the adapter call inside the bound. +Note that `SessionStore` adapter calls (`#append` / `#load`) stay on threads by default even in `:inline` mode — their timeouts are hard bounds (`Thread#join`) so a wedged store adapter can never stall the reactor. The exception is an adapter that declares itself fiber-native via an optional `callback_scheduling` method returning `:inline` (see "Fiber-native adapters" in [docs/sessions.md](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/sessions.md)); its calls then run on the reactor under a cooperative timeout. In both cases a configured `callback_wrapper` composes around the adapter call inside the bound. ## ActionCable Streaming @@ -541,7 +541,7 @@ end Then every `ClaudeAgentSDK.query` and `Client` session automatically gets traced — no per-call wiring needed. The lambda factory ensures each request gets its own observer with isolated span state, safe for concurrent Puma/Sidekiq workers. See: -- [examples/rails_actioncable_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/rails_actioncable_example.rb) -- [examples/rails_background_job_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/rails_background_job_example.rb) -- [examples/session_resumption_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/session_resumption_example.rb) -- [examples/http_mcp_server_example.rb](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/http_mcp_server_example.rb) +- [examples/rails_actioncable_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/rails_actioncable_example.rb) +- [examples/rails_background_job_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/rails_background_job_example.rb) +- [examples/session_resumption_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/session_resumption_example.rb) +- [examples/http_mcp_server_example.rb](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/http_mcp_server_example.rb) diff --git a/docs/sessions.md b/docs/sessions.md index 8bef3662..1c7783ba 100644 --- a/docs/sessions.md +++ b/docs/sessions.md @@ -398,7 +398,7 @@ end `InMemorySessionStore#append` is a working reference. Copy-in reference adapters for **S3, Redis, and Postgres** live in -[`examples/session_stores/`](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/session_stores/README.md), each with a +[`examples/session_stores/`](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/session_stores/README.md), each with a production checklist. #### Fiber-native adapters diff --git a/docs/subagents.md b/docs/subagents.md index 1fec2737..e2c67d5f 100644 --- a/docs/subagents.md +++ b/docs/subagents.md @@ -158,7 +158,7 @@ See [hook fields and permission cancellation](hooks-and-permissions.md). ## Minimal example -[`examples/subagent_status_example.rb`](https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/examples/subagent_status_example.rb) +[`examples/subagent_status_example.rb`](https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/examples/subagent_status_example.rb) defines a tool-free reviewer, registers lifecycle hooks, reads metadata, and prints task events and forwarded child text without building a status model: diff --git a/docs/types.md b/docs/types.md index 85e7203f..e549b190 100644 --- a/docs/types.md +++ b/docs/types.md @@ -1,6 +1,6 @@ # Types Reference -See [lib/claude_agent_sdk/types/](https://github.com/ya-luotao/claude-agent-sdk-ruby/tree/main/lib/claude_agent_sdk/types) for complete type definitions (one file per area; `types.rb` loads them all). +See [lib/claude_agent_sdk/types/](https://github.com/rubycatco/claude-agent-sdk-ruby/tree/main/lib/claude_agent_sdk/types) for complete type definitions (one file per area; `types.rb` loads them all). ## Hash keys diff --git a/lib/generators/claude_agent_sdk/install/install_generator.rb b/lib/generators/claude_agent_sdk/install/install_generator.rb index 69748dfe..2dfa964e 100644 --- a/lib/generators/claude_agent_sdk/install/install_generator.rb +++ b/lib/generators/claude_agent_sdk/install/install_generator.rb @@ -46,7 +46,7 @@ def show_next_steps 2. Provide credentials to the CLI, e.g. ANTHROPIC_API_KEY in the environment. 3. Review config/initializers/claude_agent_sdk.rb. - Rails guide: https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/rails.md + Rails guide: https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/rails.md MSG end diff --git a/lib/generators/claude_agent_sdk/install/templates/claude_agent_sdk.rb.tt b/lib/generators/claude_agent_sdk/install/templates/claude_agent_sdk.rb.tt index 31952cbd..2d51a97c 100644 --- a/lib/generators/claude_agent_sdk/install/templates/claude_agent_sdk.rb.tt +++ b/lib/generators/claude_agent_sdk/install/templates/claude_agent_sdk.rb.tt @@ -2,7 +2,7 @@ # Claude Agent SDK defaults, merged into every ClaudeAgentSDK.query and # ClaudeAgentSDK::Client session; per-call ClaudeAgentOptions override them. -# Guide: https://github.com/ya-luotao/claude-agent-sdk-ruby/blob/main/docs/rails.md +# Guide: https://github.com/rubycatco/claude-agent-sdk-ruby/blob/main/docs/rails.md # Uncomment together with `observers:` below (needs the opentelemetry-sdk gem). # require 'claude_agent_sdk/instrumentation' diff --git a/plugins/claude-agent-ruby/.claude-plugin/plugin.json b/plugins/claude-agent-ruby/.claude-plugin/plugin.json index 8009a3b4..8bd9b436 100644 --- a/plugins/claude-agent-ruby/.claude-plugin/plugin.json +++ b/plugins/claude-agent-ruby/.claude-plugin/plugin.json @@ -6,8 +6,8 @@ "name": "ya-luotao", "email": "ya.luotao@gmail.com" }, - "homepage": "https://github.com/ya-luotao/claude-agent-sdk-ruby", - "repository": "https://github.com/ya-luotao/claude-agent-sdk-ruby", + "homepage": "https://github.com/rubycatco/claude-agent-sdk-ruby", + "repository": "https://github.com/rubycatco/claude-agent-sdk-ruby", "license": "MIT", "keywords": [ "ruby",