claude-agent-sdk 0.16.9 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4cecc40526ae74334f69cf7f62b2362f14b223e31f040001c65dd285ee59dc31
4
- data.tar.gz: cfa4deba728a02e5e4fa6909056db0a4855336d927fa0266adce005fe22d1e56
3
+ metadata.gz: ebf4d55c080946d0311e12f6afb60f595da80945b19579aa84f7d58536e84792
4
+ data.tar.gz: 1e9dc70ab01a74b4326ce6d1bf375162e5f3cc1ab5a7ac6102ef78b5635a2552
5
5
  SHA512:
6
- metadata.gz: 148d0a96b29a71793a3242ee75dc2ecc8052c96a76265133638202dcc930673aa783942688c28d9ee00a0b67830349b35ab6003d5b3cc5ce1290ae53acbb9978
7
- data.tar.gz: 1c2a8b0297130b1b2f5b7342b487a75dbec500cad41299d3cf9a506b2618c8a12ed0289eaa9ae679dea0ebd15003d0b4219421ffe10af15579d59c776b89e27b
6
+ metadata.gz: 2a14aef3b12d83964e8487f56d80d2c35ab7ebab39255272e9789ee132aa73cf99dc5f31e6082ffc38c65c8cc2a519e4a4290944fbd00db83df6798cfcdaee34
7
+ data.tar.gz: 4d1c7552f077549723955c06fb357cdff4ee9b9f0bd0402de5471539679be4fab6639b42b269dba66c46a148b27af69e6f5d33c4d43484be38dd3ac47268146a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ 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
+
10
18
  ## [0.16.9] - 2026-05-25
11
19
 
12
20
  ### Fixed
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.9'
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 }
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ClaudeAgentSDK
4
- VERSION = '0.16.9'
4
+ VERSION = '0.16.10'
5
5
  end
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.9
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-05-25 00:00:00.000000000 Z
11
+ date: 2026-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async