Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
210 commits
Select commit Hold shift + click to select a range
9eaa2ef
End the run before stopping its ceiling sleeper
ya-luotao Oct 2, 2026
944c5dd
Contain callback failures that are not StandardErrors
ya-luotao Oct 2, 2026
743cac0
Return every result of parallel tool calls from the session readers
ya-luotao Oct 2, 2026
078f02a
List a subdirectory's own sessions when the repo has several worktrees
ya-luotao Oct 2, 2026
d70f5c0
Answer a callback whose error message is not valid UTF-8
ya-luotao Oct 2, 2026
bf9f293
NFC-normalize the worktree paths git reports
ya-luotao Oct 2, 2026
f80384f
Poll the install lock so fibers of one reactor cannot deadlock
ya-luotao Oct 2, 2026
b442bc2
Report each API response's token usage on one generation span
ya-luotao Oct 2, 2026
b05facd
Keep the resume specs off the developer's macOS Keychain
ya-luotao Oct 2, 2026
98561f9
Pick the control-request waiter by reactor ownership
ya-luotao Oct 2, 2026
4815b48
Read transcripts as UTF-8 bytes in the message readers
ya-luotao Oct 2, 2026
b4a0622
Filter settings, extra_args and MCP urls/args in #inspect
ya-luotao Oct 2, 2026
80b1261
Let install's offline shortcut work in a read-only directory
ya-luotao Oct 2, 2026
b906729
Scrub invalid UTF-8 when importing a transcript into a store
ya-luotao Oct 2, 2026
8852a5a
Stop specs leaking doubles into the at-exit process registry
ya-luotao Oct 2, 2026
f727329
Return nil from #[] for a setter-shaped name; reject '[]' as an option
ya-luotao Oct 2, 2026
1d3c31b
Leave a span for a session that fails before its first InitMessage
ya-luotao Oct 2, 2026
dec5eda
Honor HTTPS_PROXY in the CLI installer
ya-luotao Oct 2, 2026
3b83fd5
Keep the end-of-stream marker for later receive calls
ya-luotao Oct 2, 2026
3afd64a
Bridge the Keychain for a custom CLAUDE_CONFIG_DIR on resume
ya-luotao Oct 2, 2026
770d496
Say which conventions the OTel observer's attributes follow
ya-luotao Oct 2, 2026
13b7302
Keep Type#inspect from raising on values it cannot render
ya-luotao Oct 2, 2026
f408355
Test CLIInstaller::Http's HTTPS-only rule, redirects and failures
ya-luotao Oct 2, 2026
9ef80da
Keep only projects/ when preserving a materialized resume dir
ya-luotao Oct 2, 2026
9403b85
Say which exceptions a direct call_tool lets propagate
ya-luotao Oct 2, 2026
150bd72
Document that constructor keyword signatures are not type-checked
ya-luotao Oct 2, 2026
8e6cde6
Read a conversation that ends on an unanswered meta entry
ya-luotao Oct 2, 2026
7d8cb81
Let a caller's deadline through while an observer runs
ya-luotao Oct 2, 2026
8df3e6c
Name project dirs as the CLI does for paths with non-BMP characters
ya-luotao Oct 2, 2026
4d93021
Advertise Array and Hash shorthand tool parameters correctly
ya-luotao Oct 2, 2026
4ec5f1f
Accept the Ruby 4.0 answer to a real SIGTERM; run CI on Ruby 4.0
ya-luotao Oct 2, 2026
5330264
Report a non-Array tool :content as an in-band error
ya-luotao Oct 2, 2026
3686e18
Verify a long-path project dir before using its prefix match
ya-luotao Oct 2, 2026
536afa2
Detach the mirror buffer inside the lock, not before queueing on it
ya-luotao Oct 2, 2026
76bab3a
Back off between mirror retries with Kernel#sleep
ya-luotao Oct 2, 2026
accd655
Merge create_tool's meta: with the size hint from annotations
ya-luotao Oct 2, 2026
7b63834
Let the session mutations work on a removed project directory
ya-luotao Oct 2, 2026
9f193cb
Resume from a store when load_timeout_ms or env is nil
ya-luotao Oct 2, 2026
6c8b530
Describe the credential filtering in Type#inspect's YARD
ya-luotao Oct 2, 2026
e46f5c5
Take created_at from a top-level timestamp on the disk path
ya-luotao Oct 2, 2026
4ede5bf
Store entries as json in the Postgres reference adapter
ya-luotao Oct 2, 2026
dcca6a1
Document that a store-backed resume has no project auto-memory
ya-luotao Oct 2, 2026
c48b5ff
Fsync the installed CLI and its VERSION before publishing them
ya-luotao Oct 2, 2026
ec57f15
Neutralize ids and account state in the recorded CLI fixture
ya-luotao Oct 2, 2026
f8f0a5c
Correct installer and SDK MCP comments that contradict the code
ya-luotao Oct 2, 2026
bf61528
Look for the first prompt past the 64 KiB head on the disk path
ya-luotao Oct 2, 2026
c0fa174
Correct two comments about what the disk listing sees
ya-luotao Oct 2, 2026
0a78faa
List sessions under a config dir whose path has glob characters
ya-luotao Oct 2, 2026
f8e8a58
Convert ISO timestamps to epoch milliseconds without a Float
ya-luotao Oct 2, 2026
2900353
Skip subkeys that are not Strings in the store subagent readers
ya-luotao Oct 2, 2026
747e8d7
Report the adapter's mtime from get_session_info with a store
ya-luotao Oct 2, 2026
9163249
Make a hang fail the suite and CI instead of stalling them
ya-luotao Oct 2, 2026
bea4521
Wait in FiberBoundary.invoke until the worker really finished
ya-luotao Oct 2, 2026
acbe26f
Reject a title or tag that is not a usable String with ArgumentError
ya-luotao Oct 2, 2026
db2ee69
Filter the server config the CLI echoes in McpServerStatus#inspect
ya-luotao Oct 2, 2026
c76d3ed
Filter mcp_servers given as a String in #inspect
ya-luotao Oct 2, 2026
8684167
Answer a process exit whose message is not ASCII-compatible
ya-luotao Oct 2, 2026
4ca8434
Treat NotImplementedError from an optional store method as absent
ya-luotao Oct 2, 2026
cb101ae
Do not log a failed query() / ask() as an unhandled task
ya-luotao Oct 2, 2026
7e5401d
Describe the empty-stream stdin close as the trade-off it is
ya-luotao Oct 2, 2026
2f0e77c
Derive a fork's title by the listing's rule on both paths
ya-luotao Oct 2, 2026
8803808
Stub the Keychain bridge suite-wide, not only in one spec file
ya-luotao Oct 2, 2026
b5cf871
Scan the 64 KiB listing windows as bytes
ya-luotao Oct 2, 2026
34153bf
Return each subagent id once from list_subagents on disk
ya-luotao Oct 2, 2026
af66495
Pin the typed input of every hook event
ya-luotao Oct 2, 2026
c8998d5
Pin the SDK MCP JSON-RPC exchange over the control channel
ya-luotao Oct 2, 2026
ed9a559
Pin the fail-closed answer to a can_use_tool callback gone wrong
ya-luotao Oct 2, 2026
4344a94
Normalize Hash hook outputs at both levels, for any key style
ya-luotao Oct 2, 2026
33a14c4
Send permission updates in a shape the CLI accepts
ya-luotao Oct 2, 2026
a149cd3
Accept agents given as Hashes
ya-luotao Oct 2, 2026
d04a3b1
Read git's worktree list as UTF-8 whatever the locale
ya-luotao Oct 2, 2026
0da5c14
Build the command when list options were set back to nil
ya-luotao Oct 2, 2026
e98d7cf
Write one sandbox key when settings already carry a sandbox section
ya-luotao Oct 2, 2026
ef430e2
Merge hooks registered under both key types of one event
ya-luotao Oct 2, 2026
c0b3a5e
Say which typed output holds a hook-specific one; pin frozen returns
ya-luotao Oct 2, 2026
f3be445
Narrow the hook output docs to what the conversion leaves alone
ya-luotao Oct 2, 2026
f0edb33
Return hook outputs the CLI reads in the two hook examples
ya-luotao Oct 2, 2026
d4a3cd6
Pin four behaviors of the disk session readers
ya-luotao Oct 2, 2026
2e6ed17
Test the transport against a real child process: a fake CLI
ya-luotao Oct 2, 2026
6ac34e1
Say what a failed first install can really leave behind
ya-luotao Oct 2, 2026
df68b77
Tell a missing tool :content key from a present non-Array value
ya-luotao Oct 2, 2026
a5ffba8
Call the optional store methods with their own argument in a spec
ya-luotao Oct 2, 2026
1a96912
Pin the control-request deadline for a caller on another reactor
ya-luotao Oct 2, 2026
68b1a2e
Take the scheduler out of the real-child grace examples
ya-luotao Oct 2, 2026
071e03d
Synchronize query_run_end_spec on barriers, not on sleeps
ya-luotao Oct 2, 2026
21549ea
Pin two run-end decisions that no example asserted
ya-luotao Oct 2, 2026
c5365ee
Pin that strict attributes are inherited by subclasses
ya-luotao Oct 2, 2026
22b1546
Add a keyless real-CLI smoke; show the keyed run as skipped
ya-luotao Oct 2, 2026
4d650d2
Tear the session down even when an observer wait is interrupted
ya-luotao Oct 2, 2026
284c883
Describe the test suite as it is in spec/README.md
ya-luotao Oct 2, 2026
c5f2aab
Preserve only inside the resume dir the SDK created
ya-luotao Oct 2, 2026
3433f84
Report what preserve_transcripts could not remove
ya-luotao Oct 2, 2026
3c2f764
Pin the root check before each deletion in preserve_transcripts
ya-luotao Oct 2, 2026
bb3d478
Pin that the configured interval reaches Kernel#sleep on retry
ya-luotao Oct 2, 2026
4c56a64
Unlink a dangling root symlink in the preserve spec's teardown
ya-luotao Oct 2, 2026
51205d2
Verify long-path identity per transcript, from parsed lines only
ya-luotao Oct 2, 2026
a08470a
Keep the chain's own tool result; re-attach at most one per call
ya-luotao Oct 2, 2026
6e128c8
Wrap only the list_subkeys call in optional_call during resume
ya-luotao Oct 2, 2026
00a07f2
Take created_at from parsed complete lines, never a raw match
ya-luotao Oct 2, 2026
b8ecabd
Follow a dangling symlink when canonicalizing a missing path
ya-luotao Oct 2, 2026
60aec84
Let the bundle install on Ruby 4.0; run the test matrix on it
ya-luotao Oct 2, 2026
43abc0d
Tag paths as UTF-8 before normalizing them under a C locale
ya-luotao Oct 2, 2026
f1652c1
Say that delete_session is for sessions whose CLI has exited
ya-luotao Oct 2, 2026
23b92f0
Pin the required-load resume failure through query() as well
ya-luotao Oct 2, 2026
5162cd0
Remember the end of the stream instead of re-queueing its marker
ya-luotao Oct 2, 2026
9ccdb73
Arm the connect deadline only around the on_error notification
ya-luotao Oct 2, 2026
6032141
Move a preserved resume dir out of reach before scrubbing it
ya-luotao Oct 2, 2026
af35a98
List each worktree's sessions from a shared fallback directory
ya-luotao Oct 2, 2026
c06b5e6
Follow a link before the `..` after it in a missing path
ya-luotao Oct 2, 2026
81966be
Ship .yardopts in the gem
ya-luotao Oct 2, 2026
a8fed24
Document session isolation and the auto-memory switch
ya-luotao Oct 2, 2026
ceca808
Bring docs/types.md in line with the message and block classes
ya-luotao Oct 2, 2026
21c0a17
Document CLIInstallError; sandbox settings travel in --settings
ya-luotao Oct 2, 2026
675f6b2
Add docs/options.md, a reference for every ClaudeAgentOptions attribute
ya-luotao Oct 2, 2026
3a41eeb
Make the README's links absolute; guard relative links in the gem
ya-luotao Oct 2, 2026
02ae31b
README: install the pinned CLI instead of a stale literal
ya-luotao Oct 2, 2026
4c71f29
Drop the stale version from the marketplace plugin entry
ya-luotao Oct 2, 2026
2f48cd9
SECURITY.md: state the supported versions for 1.x
ya-luotao Oct 2, 2026
f241144
message_types example: handle all 28 message classes
ya-luotao Oct 2, 2026
26d4b8a
Docs: hooks, tools and permission callbacks do not require Client
ya-luotao Oct 2, 2026
b2593c5
State one custom-transport contract: five methods, ready? optional
ya-luotao Oct 2, 2026
3ee9f1a
Skill: drop ENV.fetch from initializers; say what CLAUDE_CLI_PATH does
ya-luotao Oct 2, 2026
00c8577
docs/errors.md: a rejected control request raises StandardError
ya-luotao Oct 2, 2026
52bd0f0
README: correct the Python column of the comparison table
ya-luotao Oct 2, 2026
1667f3b
README: how the CLI authenticates and what a missing login looks like
ya-luotao Oct 2, 2026
4ad59b5
Docs: four things options do not do, each checked against the CLI
ya-luotao Oct 2, 2026
2a70758
Docs: use Client.open in the examples that connected by hand
ya-luotao Oct 2, 2026
d42a40f
Skill: list docs/options.md and session isolation in the docs map
ya-luotao Oct 2, 2026
f28dd19
docs/types.md: InitMessage arrives at the start of every turn
ya-luotao Oct 2, 2026
f33a96b
docs/client.md: can_use_tool over query(transport:) needs the stdio flag
ya-luotao Oct 2, 2026
3bed43a
Link guard: no relative link to an unpackaged file, without exceptions
ya-luotao Oct 2, 2026
e19bfb7
Session isolation: a custom transport must pass both switches through
ya-luotao Oct 2, 2026
91135db
docs/types.md: say when SystemMessage#data is not the whole frame
ya-luotao Oct 2, 2026
8d468d7
Make the SystemMessage#data guard tolerate line wrapping
ya-luotao Oct 2, 2026
1fde393
docs/options.md: exact types and nil semantics, checked row by row
ya-luotao Oct 2, 2026
68091ee
Guard the custom-transport paragraph of "Turning auto-memory off"
ya-luotao Oct 2, 2026
9992540
docs/options.md: callback_scheduling rejects what does not convert
ya-luotao Oct 2, 2026
516c405
docs/types.md: a nil or false data also gives the whole frame
ya-luotao Oct 2, 2026
c5824f7
Reset configured defaults around the configuration guard
ya-luotao Oct 2, 2026
001d3d2
List the base64 gem in the documented Gemfile lines
ya-luotao Oct 2, 2026
6f5e262
Take a Pathname again when resolving a missing path
ya-luotao Oct 2, 2026
99b0b37
Resolve a missing path as bytes
ya-luotao Oct 2, 2026
b724e0f
Send a sandbox Hash under the keys the CLI reads, in any spelling
ya-luotao Oct 2, 2026
774081a
Honor a Symbol type in system_prompt, tools and output_format Hashes
ya-luotao Oct 2, 2026
837715b
Resolve the CLI executable once, to an absolute path
ya-luotao Oct 2, 2026
081d245
Pin a Pathname cli_path with real-spawn specs
ya-luotao Oct 2, 2026
96e952b
Keep draining stderr when the stderr callback raises a ScriptError
ya-luotao Oct 2, 2026
32cb525
Tolerate env and extra_args set back to nil in the transport
ya-luotao Oct 2, 2026
271ad57
Poll with Kernel#sleep instead of the deprecated Async::Task#sleep
ya-luotao Oct 2, 2026
733c001
Warn when a requested sandbox is not active
ya-luotao Oct 2, 2026
719bffe
Keep the new transport specs from hanging on Ruby 3.2 or a loaded host
ya-luotao Oct 2, 2026
15cc8db
Spawn a wrapping subclass's argv as built
ya-luotao Oct 2, 2026
e3e3959
Note the wrapped-argv exception in CLAUDE.md; plain name in a fake
ya-luotao Oct 2, 2026
95577e5
Anchor CLI paths without normalizing; a falsy cli_path still discovers
ya-luotao Oct 2, 2026
ef9b815
Settle the CLI path again when the transport connects
ya-luotao Oct 2, 2026
9dcef90
Harden the CLI path and sandbox warning specs
ya-luotao Oct 2, 2026
6ef5356
Search PATH for the default claude instead of running which
ya-luotao Oct 2, 2026
25a9cd9
Move the sandbox warning note below the example link
ya-luotao Oct 2, 2026
abc00c7
Pin discovery's empty and ~ PATH entry rules; document the ~ one
ya-luotao Oct 2, 2026
ae5599b
Say what an unusable CLAUDE_CLI_PATH does: skipped, no warning
ya-luotao Oct 2, 2026
fcad99e
Keep a preserved resume dir when cleanup runs after it
ya-luotao Oct 2, 2026
2caa30a
Merge the docs branch (#155) for phase 2
ya-luotao Oct 2, 2026
c85d62f
Scrub a preserved resume dir without recursing
ya-luotao Oct 2, 2026
919ebbb
Let a close from another thread finish its graceful teardown
ya-luotao Oct 2, 2026
cd706ff
Disarm the preserve spec's wrappers before its teardown
ya-luotao Oct 2, 2026
218eec7
Stop the Railtie wrapper reporting SDK-handled errors to Rails.error
ya-luotao Oct 2, 2026
23cc31c
Document that request state does not follow into SDK callbacks
ya-luotao Oct 2, 2026
f40a1d6
Document where a custom wrapper restores state around the Rails one
ya-luotao Oct 2, 2026
29d1e5e
Document that callbacks run outside the caller's transaction
ya-luotao Oct 2, 2026
86058cb
Turn off CLI auto-memory in the Rails guide and examples
ya-luotao Oct 2, 2026
c885b0a
Note that an agent run holds the reload lock in development
ya-luotao Oct 2, 2026
f047860
Name forwarded blocks in the Rails specs; correct a version note
ya-luotao Oct 2, 2026
5d39fbf
Restore I18n.locale in the recipe; correct the i18n table row
ya-luotao Oct 2, 2026
6da4d7b
Turn off auto-memory in every options set of the Rails guide
ya-luotao Oct 2, 2026
e7f0b94
Describe i18n 1.14.8's thread-shared locale in the Rails guide
ya-luotao Oct 2, 2026
ca925f9
Qualify when ActiveJob reports an exception escaping a callback
ya-luotao Oct 2, 2026
c396b8f
Read log tags from the tagged logger in the documented recipe
ya-luotao Oct 2, 2026
1b11f17
Say what :thread scheduling does not fix on i18n 1.14.8
ya-luotao Oct 2, 2026
5b20387
Check the guide's auto-memory switches in parsed code, not text
ya-luotao Oct 2, 2026
1c2cb77
Fail closed when the guide spec cannot follow a **splat
ya-luotao Oct 2, 2026
bac1813
Say in the Rails guide what query covers and where base64 comes from
ya-luotao Oct 2, 2026
eac02f1
Cut the lite-reader fixture's windows inside a character for any path
ya-luotao Oct 2, 2026
2421298
Rename a snake_case sandbox key only when the CLI accepts its value
ya-luotao Oct 2, 2026
56e28c4
Count only the caller's own liveness checks in the close spec
ya-luotao Oct 2, 2026
3e94b53
Say what to remove when preserving a resume dir is interrupted
ya-luotao Oct 2, 2026
fbaf269
Accept tools: as the CLI's String and Pathnames where a path goes
ya-luotao Oct 2, 2026
e3f1b0c
Write a typed network or filesystem inside a sandbox Hash as a Hash
ya-luotao Oct 2, 2026
1685eff
Honor a Symbol type in a plugins Hash
ya-luotao Oct 2, 2026
8d74996
Document the accepted spellings of Hash-form options
ya-luotao Oct 2, 2026
bec5d87
CHANGELOG entries for the October exploration fixes
ya-luotao Oct 2, 2026
690b5cb
Let rmdir decide when a scrubbed directory is empty
ya-luotao Oct 2, 2026
c50c5ed
Rename a snake_case proxy port only when it is a port number
ya-luotao Oct 2, 2026
8c80bd3
Say where the CLI reports a rejected sandbox value
ya-luotao Oct 2, 2026
40a6214
Rename a spec helper that shadowed RSpec's receive
ya-luotao Oct 2, 2026
400ed0c
Fit materialize_resume_session under the method-length limit
ya-luotao Oct 2, 2026
ae84e27
Merge remote-tracking branch 'origin/test/harden-suite-and-ci' into HEAD
ya-luotao Oct 2, 2026
4fc9e27
Merge remote-tracking branch 'origin/fix/otel-observer-usage-and-fail…
ya-luotao Oct 2, 2026
6a7f278
Merge remote-tracking branch 'origin/fix/hook-output-and-wire-contrac…
ya-luotao Oct 2, 2026
5ffbc63
Merge remote-tracking branch 'origin/fix/callback-failure-containment…
ya-luotao Oct 2, 2026
62ff18b
Merge remote-tracking branch 'origin/fix/type-inspect-and-attribute-r…
ya-luotao Oct 2, 2026
02c0571
Merge remote-tracking branch 'origin/fix/installer-and-sdk-mcp' into …
ya-luotao Oct 2, 2026
e7057e3
Merge remote-tracking branch 'origin/fix/runtime-lifecycle' into HEAD
ya-luotao Oct 2, 2026
90efdea
Merge remote-tracking branch 'origin/docs/packaging-and-accuracy' int…
ya-luotao Oct 2, 2026
f8a9123
Merge remote-tracking branch 'origin/fix/cli-path-and-stderr' into HEAD
ya-luotao Oct 2, 2026
77e1f68
Merge remote-tracking branch 'origin/fix/session-reads' into HEAD
ya-luotao Oct 2, 2026
d8c989d
Merge remote-tracking branch 'origin/fix/railtie-wrapper-and-rails-do…
ya-luotao Oct 2, 2026
5834b72
Merge remote-tracking branch 'origin/fix/store-backed-resume' into HEAD
ya-luotao Oct 2, 2026
eada4ac
Merge remote-tracking branch 'origin/fix/hash-form-option-spellings' …
ya-luotao Oct 2, 2026
48db999
Merge remote-tracking branch 'origin/fix/foreign-thread-disconnect' i…
ya-luotao Oct 2, 2026
87d5c83
Merge remote-tracking branch 'origin/chore/changelog-exploration-fixe…
ya-luotao Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"name": "claude-agent-ruby",
"source": "./plugins/claude-agent-ruby",
"description": "Build AI agents in Ruby using the claude-agent-sdk gem. Covers query() one-shot calls, Client sessions, hooks, MCP servers, streaming, structured output, sandbox settings, session management, and Rails integration.",
"version": "0.13.1",
"author": {
"name": "ya-luotao"
}
Expand Down
23 changes: 17 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@ on:
pull_request:
branches: [main]

# Every job has a time limit. This suite is built on subprocesses, pipes and
# a reactor, and the regressions it guards against tend to hang rather than
# fail; without a limit a stuck example would hold its job for GitHub's
# default of six hours and name nothing. A healthy job finishes in about a
# minute, so 15 leaves ample room for a cold bundle install.
jobs:
# Main suite + lint on every supported Ruby (Linux). The Ruby 3.4 Linux leg
# also collects coverage (simplecov, COVERAGE=1): the total goes to the job
# summary and the HTML report is uploaded as an artifact. No threshold gate.
# One macOS leg runs the suite on the newest Ruby: the transport depends on
# One macOS leg runs the suite on Ruby 3.4: the transport depends on
# signals, pipes and TERM/KILL escalation, whose behavior differs per OS.
test:
name: test (${{ matrix.os }}, Ruby ${{ matrix.ruby-version }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
ruby-version: ['3.2', '3.3', '3.4']
ruby-version: ['3.2', '3.3', '3.4', '4.0']
include:
- os: ubuntu-latest
ruby-version: '3.4'
Expand Down Expand Up @@ -80,6 +86,7 @@ jobs:
rbs-test:
name: rbs runtime type check (Ruby 3.4)
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- uses: actions/checkout@v4
Expand All @@ -94,19 +101,22 @@ jobs:
run: bundle exec rake rbs:test

# Dependency resolutions the main Gemfile can't reach: its RuboCop pin holds
# json at 2.x. `latest` forces the newest allowed majors (json 3.x, mcp 1.x);
# `floor` pins the oldest supported runtime dependencies, on the oldest and
# newest supported Ruby. rspec only — lint runs on the main leg. See
# gemfiles/*.gemfile.
# json at 2.x. `latest` forces the newest allowed majors (json 3.x, mcp 1.x),
# on Ruby 3.4 and on Ruby 4.0; `floor` pins the oldest supported runtime
# dependencies, on Ruby 3.2 and 3.4. rspec only — lint runs on the main leg.
# See gemfiles/*.gemfile.
deps:
name: deps (${{ matrix.gemfile }}, Ruby ${{ matrix.ruby-version }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- gemfile: latest
ruby-version: '3.4'
- gemfile: latest
ruby-version: '4.0'
- gemfile: floor
ruby-version: '3.2'
- gemfile: floor
Expand Down Expand Up @@ -138,6 +148,7 @@ jobs:
rails:
name: rails (${{ matrix.gemfile }}, Ruby ${{ matrix.ruby-version }})
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
Expand Down
99 changes: 76 additions & 23 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
# Real-CLI integration run: installs the CLI pinned by
# CLIInstaller::PINNED_CLI_VERSION and runs spec/integration against it with
# live, budget-capped API calls (each example sets max_budget_usd).
# Real-CLI checks. Each job installs the CLI pinned by
# CLIInstaller::PINNED_CLI_VERSION (rake claude_agent_sdk:install_cli) and
# drives it from the SDK:
#
# Triggers: weekly, on demand, and on PRs that touch the installer (which is
# where cli-pin-bump.yml moves the pin), so a CLI bump is checked against the
# real binary before it merges.
# - keyless-smoke needs no secret and runs on every trigger: the initialize
# handshake, and a prompt the CLI cannot send for lack of credentials,
# which must come back as a typed ResultError rather than a hang
# (spec/integration/keyless_smoke_spec.rb). It checks the installer, the
# transport, the control protocol, the message parser and the error path
# against the real binary. It reaches no model.
# - real-cli runs spec/integration with live, budget-capped API calls (each
# example sets max_budget_usd). It needs an ANTHROPIC_API_KEY repository
# secret: the gate job checks for it, and without one real-cli shows as
# skipped, not as passed. Fork and Dependabot PRs never receive secrets,
# so for them it is always skipped.
#
# Requires an ANTHROPIC_API_KEY repository secret. Without it the job skips
# with a notice instead of failing. Fork and Dependabot PRs never receive
# secrets, so they always take the skip path; so do pin-bump PRs opened with
# the default GITHUB_TOKEN, which don't trigger workflows at all (see
# cli-pin-bump.yml; dispatch this workflow on that branch instead).
# Triggers: weekly, on demand, and on PRs that touch the installer (which is
# where cli-pin-bump.yml moves the pin), the transport, Query or the message
# parser, so a CLI bump or a wire-protocol change meets the real binary
# before it merges. A pin-bump PR opened with the default GITHUB_TOKEN gets
# its runs only once someone approves them (see cli-pin-bump.yml).
name: Integration (real CLI)

on:
Expand All @@ -23,6 +31,9 @@ on:
branches: [main]
paths:
- lib/claude_agent_sdk/cli_installer.rb
- lib/claude_agent_sdk/subprocess_cli_transport.rb
- lib/claude_agent_sdk/query.rb
- lib/claude_agent_sdk/message_parser.rb
- spec/integration/**
- .github/workflows/integration.yml

Expand All @@ -34,31 +45,76 @@ concurrency:
cancel-in-progress: true

jobs:
real-cli:
name: real CLI (pinned version, Ruby 3.4)
keyless-smoke:
name: keyless smoke (pinned CLI, no credentials, Ruby 3.4)
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 10

steps:
- uses: actions/checkout@v4

- name: Set up Ruby 3.4
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true

# As in real-cli below: the vendored binary, put on PATH.
- name: Install pinned Claude Code CLI
run: |
bundle exec rake claude_agent_sdk:install_cli
echo "$GITHUB_WORKSPACE/vendor/claude" >> "$GITHUB_PATH"

- name: Show CLI version
run: claude --version

- name: Run the keyless smoke
env:
RUN_KEYLESS_SMOKE: '1'
run: bundle exec rspec spec/integration/keyless_smoke_spec.rb --format json --out "$RUNNER_TEMP/keyless-smoke.json"

# Without RUN_KEYLESS_SMOKE the spec filters its examples out, and rspec
# passes a run that executed none. Make sure this one ran them.
- name: Check that the smoke ran
run: >-
ruby -rjson -e 'summary = JSON.parse(File.read(ARGV[0])).fetch("summary");
abort("the keyless smoke did not run its examples: #{summary}") unless
summary["example_count"] >= 2 && summary["pending_count"].zero? && summary["failure_count"].zero?'
"$RUNNER_TEMP/keyless-smoke.json"

# secrets.* can't appear in an `if:`, so this job checks for the key and
# real-cli keys off its output.
gate:
name: ANTHROPIC_API_KEY available
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
run: ${{ steps.check.outputs.run }}

steps:
# secrets.* can't appear in an `if:`, so a step checks for the key and
# the rest of the job keys off its output.
- name: Check for ANTHROPIC_API_KEY
id: gate
id: check
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "::notice title=Integration run skipped::The ANTHROPIC_API_KEY secret is not available (not configured, or a fork/Dependabot PR). Add it as a repository secret to enable this run."
echo "::notice title=Keyed integration run skipped::The ANTHROPIC_API_KEY secret is not available (not configured, or a fork/Dependabot PR), so the real-cli job is skipped. Add it as a repository secret to enable that run; keyless-smoke runs either way."
echo "run=false" >> "$GITHUB_OUTPUT"
else
echo "run=true" >> "$GITHUB_OUTPUT"
fi

real-cli:
name: real CLI (pinned version, Ruby 3.4)
needs: gate
if: needs.gate.outputs.run == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- uses: actions/checkout@v4
if: steps.gate.outputs.run == 'true'

- name: Set up Ruby 3.4
if: steps.gate.outputs.run == 'true'
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
Expand All @@ -69,17 +125,14 @@ jobs:
# the CLI up on PATH and skip without it, so put it there and fail
# loudly here if it doesn't run.
- name: Install pinned Claude Code CLI
if: steps.gate.outputs.run == 'true'
run: |
bundle exec rake claude_agent_sdk:install_cli
echo "$GITHUB_WORKSPACE/vendor/claude" >> "$GITHUB_PATH"

- name: Show CLI version
if: steps.gate.outputs.run == 'true'
run: claude --version

- name: Run integration specs
if: steps.gate.outputs.run == 'true'
env:
RUN_INTEGRATION: '1'
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
Expand Down
Loading
Loading