claude-agent-sdk 0.16.8 → 0.16.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +1 -1
- data/lib/claude_agent_sdk/command_builder.rb +2 -0
- data/lib/claude_agent_sdk/subprocess_cli_transport.rb +84 -0
- data/lib/claude_agent_sdk/types.rb +25 -4
- data/lib/claude_agent_sdk/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ebf4d55c080946d0311e12f6afb60f595da80945b19579aa84f7d58536e84792
|
|
4
|
+
data.tar.gz: 1e9dc70ab01a74b4326ce6d1bf375162e5f3cc1ab5a7ac6102ef78b5635a2552
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2a14aef3b12d83964e8487f56d80d2c35ab7ebab39255272e9789ee132aa73cf99dc5f31e6082ffc38c65c8cc2a519e4a4290944fbd00db83df6798cfcdaee34
|
|
7
|
+
data.tar.gz: 4d1c7552f077549723955c06fb357cdff4ee9b9f0bd0402de5471539679be4fab6639b42b269dba66c46a148b27af69e6f5d33c4d43484be38dd3ac47268146a
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.16.10] - 2026-06-04
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `ClaudeAgentOptions#strict_mcp_config` — forwarded as `--strict-mcp-config`. When `true`, the CLI uses **only** the MCP servers passed via `mcp_servers`, ignoring project `.mcp.json`, user/global settings, and plugin-provided servers, for a fully deterministic server set. Defaults to `false`. (Parity with [Python SDK #915](https://github.com/anthropics/claude-agent-sdk-python/pull/915))
|
|
14
|
+
- `ClaudeAgentOptions#include_hook_events` — forwarded as `--include-hook-events`. When `true`, the CLI emits hook lifecycle events (PreToolUse, PostToolUse, Stop, etc.) into the message stream. The parser already maps these to `HookStartedMessage` / `HookProgressMessage` / `HookResponseMessage` (the CLI simply never emitted them without this flag). Defaults to `false`. (Parity with [Python SDK #917](https://github.com/anthropics/claude-agent-sdk-python/pull/917))
|
|
15
|
+
- `SandboxNetworkConfig#denied_domains` (`deniedDomains`) and `#allow_mach_lookup` (`allowMachLookup`) — completes the sandbox network allowlist field set. `denied_domains` blocks domains even when matched by `allowed_domains`; `allow_mach_lookup` is a macOS-only list of XPC/Mach service names to allow (supports a trailing wildcard). Completes [Python SDK #893](https://github.com/anthropics/claude-agent-sdk-python/pull/893) — `denied_domains` and `allow_mach_lookup` were the last two fields the Ruby port had not yet landed.
|
|
16
|
+
- **Orphaned-subprocess cleanup**: `SubprocessCLITransport` now tracks live CLI subprocesses in a class-level, mutex-guarded `Set` and registers an `at_exit` handler that sends `SIGTERM` to any still running when the parent Ruby process exits. This prevents leaked `claude` processes when callers crash or exit before reaching `#close`. The handler skips already-exited processes (`Process::Waiter#alive?`) and swallows errors so it never interrupts interpreter shutdown. (Parity with [Python SDK #916](https://github.com/anthropics/claude-agent-sdk-python/pull/916))
|
|
17
|
+
|
|
18
|
+
## [0.16.9] - 2026-05-25
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `Types.normalize_name` no longer mutates the frozen string returned by `Symbol#to_s` under Ruby 3.4+. The previous `name.dup.to_s` order dup'd the Symbol (a no-op) and then took `.to_s`, which Ruby 3.4 stages to return a frozen string in 4.0 — emitting a deprecation warning on every `gsub!`/`tr!`/`downcase!`. Swapped to `name.to_s.dup`. (#35, @chagel)
|
|
22
|
+
|
|
10
23
|
## [0.16.8] - 2026-05-15
|
|
11
24
|
|
|
12
25
|
### Added
|
data/README.md
CHANGED
|
@@ -68,7 +68,7 @@ Add this line to your application's Gemfile:
|
|
|
68
68
|
gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
|
|
69
69
|
|
|
70
70
|
# Or use a stable version from RubyGems
|
|
71
|
-
gem 'claude-agent-sdk', '~> 0.16.
|
|
71
|
+
gem 'claude-agent-sdk', '~> 0.16.10'
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
Then `bundle install`, or install directly: `gem install claude-agent-sdk`.
|
|
@@ -277,6 +277,8 @@ module ClaudeAgentSDK
|
|
|
277
277
|
cmd.push("--include-partial-messages") if @options.include_partial_messages
|
|
278
278
|
cmd.push("--fork-session") if @options.fork_session
|
|
279
279
|
cmd.push("--bare") if @options.bare
|
|
280
|
+
cmd.push("--include-hook-events") if @options.include_hook_events
|
|
281
|
+
cmd.push("--strict-mcp-config") if @options.strict_mcp_config
|
|
280
282
|
end
|
|
281
283
|
|
|
282
284
|
def append_plugins(cmd)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'open3'
|
|
5
|
+
require 'set'
|
|
5
6
|
require 'timeout'
|
|
6
7
|
require_relative 'transport'
|
|
7
8
|
require_relative 'errors'
|
|
@@ -15,6 +16,75 @@ module ClaudeAgentSDK
|
|
|
15
16
|
MINIMUM_CLAUDE_CODE_VERSION = '2.0.0'
|
|
16
17
|
RECENT_STDERR_LINES_LIMIT = 20
|
|
17
18
|
|
|
19
|
+
# Track live CLI subprocesses so we can terminate them when the parent Ruby
|
|
20
|
+
# process exits. Mirrors the Python (PR #916, a `set[Process]`) and
|
|
21
|
+
# TypeScript SDKs' parent-exit cleanup, preventing orphaned `claude`
|
|
22
|
+
# processes from leaking when callers crash or exit before reaching #close.
|
|
23
|
+
# A Set keyed by object identity (like Python's set) keeps the hot path
|
|
24
|
+
# off `#pid` — only #kill_active_processes touches `#pid`/`#alive?`, at exit.
|
|
25
|
+
# Guarded by a mutex because #close can run on a FiberBoundary worker thread
|
|
26
|
+
# while #connect runs on the reactor fiber.
|
|
27
|
+
# Stored in CONSTANTS (not class instance variables) so the registry is a
|
|
28
|
+
# single shared instance across this class and any subclass: constants
|
|
29
|
+
# resolve through the ancestor chain, whereas class ivars are NOT inherited
|
|
30
|
+
# — a `SubprocessCLITransport` subclass instance calling
|
|
31
|
+
# `self.class.register_active_process` would otherwise reach a nil mutex and
|
|
32
|
+
# raise mid-#connect, orphaning the just-spawned child. The base-class
|
|
33
|
+
# at_exit handler must be able to see every subprocess, a subclass's too.
|
|
34
|
+
ACTIVE_PROCESSES = Set.new
|
|
35
|
+
ACTIVE_PROCESSES_MUTEX = Mutex.new
|
|
36
|
+
|
|
37
|
+
class << self
|
|
38
|
+
# Public readers (the test suite uses `described_class.active_processes`);
|
|
39
|
+
# they return the shared constants so subclasses observe the same objects.
|
|
40
|
+
def active_processes
|
|
41
|
+
ACTIVE_PROCESSES
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def active_processes_mutex
|
|
45
|
+
ACTIVE_PROCESSES_MUTEX
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# +wait_thr+ is the Process::Waiter returned by Open3.popen3.
|
|
49
|
+
def register_active_process(wait_thr)
|
|
50
|
+
return unless wait_thr
|
|
51
|
+
|
|
52
|
+
active_processes_mutex.synchronize { active_processes.add(wait_thr) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def deregister_active_process(wait_thr)
|
|
56
|
+
return unless wait_thr
|
|
57
|
+
|
|
58
|
+
active_processes_mutex.synchronize { active_processes.delete(wait_thr) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Best-effort SIGTERM to every still-running child. Registered with
|
|
62
|
+
# at_exit at the bottom of this file. Never reaps (a blocking wait could
|
|
63
|
+
# hang interpreter shutdown) — the OS reparents and reaps orphans.
|
|
64
|
+
#
|
|
65
|
+
# Deliberately does NOT take active_processes_mutex: at interpreter
|
|
66
|
+
# shutdown Ruby runs at_exit handlers *before* terminating other threads,
|
|
67
|
+
# and Mutex is unfair, so blocking here while a still-live worker churns
|
|
68
|
+
# register/deregister can starve this handler and hang the process. A
|
|
69
|
+
# lock-free read is safe — a torn snapshot at worst misses or repeats a
|
|
70
|
+
# SIGTERM, both harmless. The outer rescue guarantees the handler never
|
|
71
|
+
# raises (e.g. ThreadError if reached from a trap context, or a
|
|
72
|
+
# concurrent-modification error from the unlocked read), honoring the
|
|
73
|
+
# "never interrupt interpreter shutdown" contract.
|
|
74
|
+
def kill_active_processes
|
|
75
|
+
active_processes.to_a.each do |wait_thr|
|
|
76
|
+
next unless wait_thr.alive?
|
|
77
|
+
|
|
78
|
+
Process.kill('TERM', wait_thr.pid)
|
|
79
|
+
rescue StandardError
|
|
80
|
+
# Process already gone (Errno::ESRCH), not permitted, or invalid pid.
|
|
81
|
+
end
|
|
82
|
+
active_processes.clear
|
|
83
|
+
rescue StandardError
|
|
84
|
+
# Never let cleanup interfere with interpreter shutdown.
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
18
88
|
def initialize(options_or_prompt = nil, options = nil)
|
|
19
89
|
# Support both new single-arg form and legacy two-arg form
|
|
20
90
|
@options = options.nil? ? options_or_prompt : options
|
|
@@ -104,6 +174,7 @@ module ClaudeAgentSDK
|
|
|
104
174
|
opts = { chdir: @cwd&.to_s }.compact
|
|
105
175
|
|
|
106
176
|
@stdin, @stdout, @stderr, @process = Open3.popen3(process_env, *cmd, opts)
|
|
177
|
+
self.class.register_active_process(@process)
|
|
107
178
|
|
|
108
179
|
# Always drain stderr to prevent pipe buffer deadlock.
|
|
109
180
|
# Without this, --verbose output fills the OS pipe buffer (~64KB),
|
|
@@ -273,6 +344,7 @@ module ClaudeAgentSDK
|
|
|
273
344
|
warn "Claude SDK: Cleanup warnings: #{cleanup_errors.join(', ')}"
|
|
274
345
|
end
|
|
275
346
|
|
|
347
|
+
self.class.deregister_active_process(@process)
|
|
276
348
|
@process = nil
|
|
277
349
|
@stdout = nil
|
|
278
350
|
# @stdin already nilled under the mutex above.
|
|
@@ -403,6 +475,13 @@ module ClaudeAgentSDK
|
|
|
403
475
|
returncode = nil
|
|
404
476
|
end
|
|
405
477
|
|
|
478
|
+
# The child has exited and been reaped; drop it from the parent-exit
|
|
479
|
+
# registry now rather than waiting for #close, which a caller may never
|
|
480
|
+
# reach (e.g. a Client abandoned without #disconnect, or direct transport
|
|
481
|
+
# use). Idempotent — #close's own deregister becomes a harmless no-op, and
|
|
482
|
+
# #close still sees @process (left set here) for its termination logic.
|
|
483
|
+
self.class.deregister_active_process(@process)
|
|
484
|
+
|
|
406
485
|
if returncode && returncode != 0
|
|
407
486
|
# Wait briefly for stderr thread to finish draining
|
|
408
487
|
@stderr_task&.join(1)
|
|
@@ -457,3 +536,8 @@ module ClaudeAgentSDK
|
|
|
457
536
|
end
|
|
458
537
|
end
|
|
459
538
|
end
|
|
539
|
+
|
|
540
|
+
# Terminate any CLI subprocess still live when the parent Ruby process exits.
|
|
541
|
+
# Registered once at require time (require is idempotent). Best-effort: the
|
|
542
|
+
# handler swallows all errors so it never interferes with interpreter shutdown.
|
|
543
|
+
at_exit { ClaudeAgentSDK::SubprocessCLITransport.kill_active_processes }
|
|
@@ -127,7 +127,7 @@ module ClaudeAgentSDK
|
|
|
127
127
|
end
|
|
128
128
|
|
|
129
129
|
def normalize_name(name)
|
|
130
|
-
name = name.dup
|
|
130
|
+
name = name.to_s.dup
|
|
131
131
|
name.gsub!(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, "_")
|
|
132
132
|
name.tr!("-", "_")
|
|
133
133
|
name.downcase!
|
|
@@ -1344,17 +1344,19 @@ module ClaudeAgentSDK
|
|
|
1344
1344
|
|
|
1345
1345
|
# Sandbox network configuration
|
|
1346
1346
|
class SandboxNetworkConfig < Type
|
|
1347
|
-
attr_accessor :allowed_domains, :allow_managed_domains_only,
|
|
1347
|
+
attr_accessor :allowed_domains, :denied_domains, :allow_managed_domains_only,
|
|
1348
1348
|
:allow_unix_sockets, :allow_all_unix_sockets, :allow_local_binding,
|
|
1349
|
-
:http_proxy_port, :socks_proxy_port
|
|
1349
|
+
:allow_mach_lookup, :http_proxy_port, :socks_proxy_port
|
|
1350
1350
|
|
|
1351
1351
|
def to_h
|
|
1352
1352
|
result = {}
|
|
1353
1353
|
result[:allowedDomains] = @allowed_domains if @allowed_domains
|
|
1354
|
+
result[:deniedDomains] = @denied_domains if @denied_domains
|
|
1354
1355
|
result[:allowManagedDomainsOnly] = @allow_managed_domains_only unless @allow_managed_domains_only.nil?
|
|
1355
1356
|
result[:allowUnixSockets] = @allow_unix_sockets unless @allow_unix_sockets.nil?
|
|
1356
1357
|
result[:allowAllUnixSockets] = @allow_all_unix_sockets unless @allow_all_unix_sockets.nil?
|
|
1357
1358
|
result[:allowLocalBinding] = @allow_local_binding unless @allow_local_binding.nil?
|
|
1359
|
+
result[:allowMachLookup] = @allow_mach_lookup if @allow_mach_lookup
|
|
1358
1360
|
result[:httpProxyPort] = @http_proxy_port if @http_proxy_port
|
|
1359
1361
|
result[:socksProxyPort] = @socks_proxy_port if @socks_proxy_port
|
|
1360
1362
|
result
|
|
@@ -1477,13 +1479,16 @@ module ClaudeAgentSDK
|
|
|
1477
1479
|
:betas, :tools, :sandbox, :append_allowed_tools,
|
|
1478
1480
|
:thinking, :effort, :observers, :task_budget
|
|
1479
1481
|
attr_reader :bare, :fork_session, :enable_file_checkpointing,
|
|
1480
|
-
:include_partial_messages, :continue_conversation
|
|
1482
|
+
:include_partial_messages, :continue_conversation,
|
|
1483
|
+
:include_hook_events, :strict_mcp_config
|
|
1481
1484
|
|
|
1482
1485
|
def initialize(attributes = {})
|
|
1483
1486
|
self.fork_session = false
|
|
1484
1487
|
self.continue_conversation = false
|
|
1485
1488
|
self.include_partial_messages = false
|
|
1486
1489
|
self.enable_file_checkpointing = false
|
|
1490
|
+
self.include_hook_events = false
|
|
1491
|
+
self.strict_mcp_config = false
|
|
1487
1492
|
|
|
1488
1493
|
super(merge_with_defaults(attributes || {}))
|
|
1489
1494
|
|
|
@@ -1543,6 +1548,22 @@ module ClaudeAgentSDK
|
|
|
1543
1548
|
@continue_conversation = coerce_boolean(value)
|
|
1544
1549
|
end
|
|
1545
1550
|
|
|
1551
|
+
def include_hook_events?
|
|
1552
|
+
!!include_hook_events
|
|
1553
|
+
end
|
|
1554
|
+
|
|
1555
|
+
def include_hook_events=(value)
|
|
1556
|
+
@include_hook_events = coerce_boolean(value)
|
|
1557
|
+
end
|
|
1558
|
+
|
|
1559
|
+
def strict_mcp_config?
|
|
1560
|
+
!!strict_mcp_config
|
|
1561
|
+
end
|
|
1562
|
+
|
|
1563
|
+
def strict_mcp_config=(value)
|
|
1564
|
+
@strict_mcp_config = coerce_boolean(value)
|
|
1565
|
+
end
|
|
1566
|
+
|
|
1546
1567
|
private
|
|
1547
1568
|
|
|
1548
1569
|
# Strict key validation: unlike other Type subclasses (which silently drop
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude-agent-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.16.
|
|
4
|
+
version: 0.16.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Community Contributors
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-06-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: async
|