Skip to content

Sync with Python SDK 0.2.147 (0.2.137 + 0.2.140 feature releases) - #55

Merged
ya-luotao merged 18 commits into
mainfrom
feature/sync-python-sdk-0.2.147
Aug 28, 2026
Merged

ya-luotao merged 18 commits into
mainfrom
feature/sync-python-sdk-0.2.147

Conversation

@ya-luotao

Copy link
Copy Markdown
Collaborator

Syncs the gem from Python SDK 0.2.134 to 0.2.147. The intervening releases 0.2.135/136/138/139/141–147 only bump the CLI binary Python vendors; this gem does not ship a CLI, so they carry no Ruby-side change. All functional work lives in 0.2.137 and 0.2.140.

Built in three parallel worktrees (messages / errors / sessions), each reviewed by two independent adversarial reviewers before merge. Merged with zero conflicts.

Ported from Python

Python PR Change
#1196 ConversationResetMessage for the conversation_reset frame
#1199 origin on UserMessage / ResultMessage
#1198 resume_drops_turn; enriched error for in-flight control requests
#1197 Seed settings.json / cowork_settings.json on store-backed resume
#1206 forward_subagent_text
#1205 ResultError with the structured error payload
#1204 can_use_tool with a String prompt; stdin held open for it
#1207 Recover parent_tool_use_id / add parent_agent_id for subagent transcripts

Two live defects fixed that predate this sync

  • can_use_tool permission requests were cut off when it was the only bidirectional feature in use. The check deciding whether the CLI may still send control requests needing a reply looked only at SDK MCP servers and hooks, so an Enumerator prompt using can_use_tool with neither closed stdin as soon as input ended — later permission requests then failed CLI-side with "Stream closed". This broke an already-supported path.
  • A refused resume reported a bare "exit code 1" to the in-flight initialize, discarding the CLI's actual reason. Pending control requests were signalled with the raw exception before the enriched one was computed.

Ruby-only hardening (from adversarial review, no Python counterpart)

These came out of the review rounds; reviewers will not find them in any Python PR.

  • An unusable subagent metadata sidecar no longer breaks every later resume. JSON.parse is lenient about illegal bytes inside a UTF-8-tagged document, returning a Hash whose values only fail later at generate time. Session import persisted that as an agent_metadata entry, and every subsequent resume through the store then died with an opaque JSON::GeneratorError — permanently, until the entry was repaired externally. Python degrades gracefully here because decode("utf-8") raises up front.
  • Regular-file guards before opening optional config/sidecar files, so a FIFO cannot hang a resume or get_subagent_messages forever with no exception for the best-effort rescue to catch.
  • settings.json with a lone surrogate escape is no longer copied through unstripped. Ruby's JSON.parse rejects lone surrogates, which fell back to byte-for-byte passthrough — so enabledPlugins survived and the resumed CLI kept network-installing marketplaces on every resume, the exact misbehavior the seeding transform exists to prevent. Python's json.loads accepts them, so it never had this hole.
  • Redacting .credentials.json no longer aborts a resume on a serialization failure; the rescue covered only parse failures.

Compatibility

ConversationResetMessage widens the Message union with a frame that was previously dropped silently. Code that raises on an unrecognized message class (case msg ... else raise) will now see it, on the first /clear of a long-lived session.

ResultError subclasses ProcessError, so existing rescue ProcessError handlers keep working — rescue ResultError first to reach the structured fields.

Verification

  • bundle exec rake on the integrated branch: 1372 examples, 0 failures; RuboCop 66 files, no offenses. Baseline on main was 1296.
  • Each of the three fixes for a pre-existing defect was reverse-verified: backing the fix out turns its test red.
  • Real-CLI smokes re-run against the integrated tree (this machine uses subscription auth, so RUN_INTEGRATION=1 self-skips on the missing ANTHROPIC_API_KEY): String prompt + can_use_tool full permission round-trip (callback invoked, decision written back over stdin, file written, clean result), and a live ResultError carrying error_max_turns.
  • spec/integration/ now has can_use_tool control-protocol coverage, which the repo previously lacked entirely.

Not in this PR

Python #1218 (mcp 2.x support) is deliberately deferred — Ruby's mcp gem is a different ecosystem with no 2.x break, but the PR carries four embedded behaviors Ruby genuinely lacks (hand-rolled dispatch instead of the gem's own for everything but tools/call, a hardcoded protocolVersion, no notifications/cancelled handling, no cancellation of in-flight calls on disconnect). Ruby's mcp gem exposes no in-memory transport, so the fix cannot mirror Python's approach and does not belong in a version-sync batch. Filed separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_019dUsxYfSqkXCVwTUbNoMyB

ya-luotao and others added 18 commits August 28, 2026 10:18
The CLI announces a mid-session conversation reset (`/clear`, or any other
flow that discards the transcript) with a top-level `conversation_reset`
frame. The Ruby parser had no branch for it, so it was silently dropped
through the forward-compat fallthrough and apps never saw the reset — nor
had a signal to snapshot running totals before subsequent ResultMessages
zero them.

- types.rb: ConversationResetMessage (new_conversation_id, uuid, session_id)
- message_parser.rb: parse `conversation_reset`; a missing required field
  raises MessageParseError rather than yielding a half-built message

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UuAH421iQ9oMARmFsMFfP
Port of Python SDK PR #1197 (b4d65f5).

A SessionStore-backed resume runs the CLI under a temporary
CLAUDE_CONFIG_DIR seeded from the caller's real one. The seed was
`.credentials.json` (refreshToken redacted) and `.claude.json` only —
user `settings.json` was left behind, and with it `apiKeyHelper` (a
fourth auth mechanism alongside the credentials file, the macOS Keychain
and env vars) plus the user's `env`, `hooks` and `permissions`. A host
authenticating solely via `apiKeyHelper` failed with "Not logged in" the
moment it resumed from the store, with nothing in the error pointing at
why.

`copy_auth_files` now also seeds `settings.json` and
`cowork_settings.json` (the alternate filename the CLI reads in
cowork-plugins mode) from the caller's config dir, through
`strip_settings_for_resume`, which drops the keys that only misbehave
under a redirected config dir:

- `enabledPlugins` / `extraKnownMarketplaces` — they reconcile against
  the always-empty temp plugin cache and would network-install every
  declared marketplace on each resume;
- `env.CLAUDE_CONFIG_DIR` — it would point the subprocess's config reads
  away from the temp dir.

A UTF-8 BOM (PowerShell-written settings) is tolerated; content that
doesn't parse as a JSON object is passed through byte-for-byte; nothing
is re-serialized when no key was actually stripped; a settings file that
JSON.generate refuses to emit (a spec-valid `1e999` parses to Infinity)
falls back to the original bytes. Output is written 0600 inside the 0700
temp dir.

`read_file_if_present` becomes `read_if_present`: it stats first and
requires a regular file (a directory raises, a FIFO would hang the
resume forever), returns raw bytes, and logs-and-skips any non-ENOENT
failure. `copy_if_present` gains an optional transform and the same skip
policy, removing a partially-written destination rather than leaving
truncated JSON for the subprocess to misparse — these files are
best-effort enrichment and must not abort a resume that would otherwise
succeed.

`MaterializedResume#preserve_transcripts` scrubs the two new files too:
their `env` blocks routinely carry API keys, so the preserve path would
otherwise leak secrets it was written to remove.
In streaming-input mode one connection interleaves the turns the
application sends with turns the session injects on its own — background
task notifications, fired scheduled-task prompts, MCP channel messages,
messages relayed from peer sessions. The CLI attributes these with an
`origin` object on user messages and forwards the triggering message's
origin on each result, so a consumer can tell "this result answers my
prompt" from a task-notification follow-up. The Ruby parser dropped it.

- types.rb: `origin` on UserMessage and ResultMessage, documented as a
  pass-through Hash whose keys keep the wire spelling (`:fromSession`
  stays camelCase), with the known kinds/subkinds listed as documentation
  rather than validation
- message_parser.rb: `parse_origin` passes the CLI's Hash through
  untouched when it has a String `:kind`, so newer kinds/fields stay
  visible; anything else reads as absent. The result path merges the
  validated value over the raw field so a malformed origin is not
  surfaced verbatim by the base-class attribute assignment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UuAH421iQ9oMARmFsMFfP
Port of Python SDK PR #1205 (sha 90ab957).

The CLI ends a failed run by emitting a `result` frame with is_error=true
(yielded as a ResultMessage) and *then* exiting non-zero on purpose, for
shell-script consumers. The read loop already replaced the bare "exit code 1"
ProcessError with the result's error text, but callers got only a string —
and the text itself was wrong for API failures: those arrive as
subtype "success" + is_error true + empty errors[] with the prose in
`result`, so the old `errors.join('; ') || subtype` chain printed the
self-contradictory "Claude Code returned an error result: success".

- errors.rb: ResultError < ProcessError carrying subtype / errors / result /
  api_error_status / terminal_reason / session_id / data, each type-narrowed
  (a non-String subtype, a non-Integer api_error_status, ... read back nil).
  ResultError.normalize_errors tolerates a bare String, treats anything else
  as empty, and drops non-String/blank entries so the structured field and
  the exception text can never disagree; ResultError.field reads both key
  forms for the same reason (wire payloads are symbolized, a replayed one is
  not). Existing `rescue ProcessError` handlers are unaffected.
- Ruby's #cause is only set for an exception raised inside a rescue block,
  and the read loop hands this one to the message queue instead of raising
  it there — so the replaced ProcessError is exposed explicitly as
  #original_error rather than relying on #cause (Python chains __cause__).
- query.rb: @last_error_result_text becomes @last_error_result and keeps the
  whole payload; the text is derived at raise time by #error_result_text,
  whose order is errors[] -> result -> non-success subtype -> HTTP status ->
  "unknown error". Reset logic unchanged. stderr is still carried over
  (Ruby has always done so; the original error is reachable anyway).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ojRwGumL91UH627ZPAnjZ
By default only a subagent's tool_use / tool_result blocks enter the
message stream — enough for a progress heartbeat. With
forward_subagent_text the subagent's text and thinking blocks are
forwarded the same way, so consumers can render the full nested
transcript. Matches the TypeScript SDK's forwardSubagentText.

Sent as an initialize capability rather than a CLI flag, and only when
enabled, so an older CLI never sees an unknown key on the common path.
Plumbed through both Query construction sites (query() and
Client#connect).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UuAH421iQ9oMARmFsMFfP
Port of Python SDK PR #1198's query.py half (sha be2d0df).

The read loop's rescue signaled every pending control request with the raw
exception *before* computing the ResultError replacement, so requests still
in flight when the CLI died got "Command failed with exit code 1" while the
message stream got the actionable error.

That is exactly the resume-rejection path: a refused resume (a nonexistent
session, a failed --resume-drops-turn guard) is reported as an error result
on stdout followed by exit 1 *before* the CLI answers the SDK's `initialize`,
so the in-flight initialize swallowed the real reason.

Fix is the ordering: compute the replacement first, then signal the pending
requests with it. The level-trigger invariant (store the result before
signaling) and the `||=` first-writer-wins semantics are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ojRwGumL91UH627ZPAnjZ
Port of Python SDK PR #1207 (2bbdce6).

`get_subagent_messages` / `get_subagent_messages_from_store` returned
SessionMessages whose `parent_tool_use_id` was always nil, so callers had
no way to tell which Agent `tool_use` block in the parent session spawned
a given subagent. The information was sitting right next to the
transcript: the `agent-<id>.meta.json` sidecar carries `toolUseId` and
`parentAgentId`, and on the SessionStore path the same data arrives as a
synthetic `{"type": "agent_metadata", ...}` entry that the reader was
dropping on the floor.

SessionMessage gains `parent_agent_id` (the id of the subagent that
spawned this one; only set for nested subagents). Both ids are stamped on
every message of a subagent transcript — they describe the transcript,
not the individual message.

The sidecar naming convention was open-coded in three places (disk read,
session import, resume materialization); it collapses into four shared
helpers on Sessions:

- `agent_metadata_sidecar_path` — the single definition of
  `agent-<id>.jsonl` -> `agent-<id>.meta.json`
- `read_agent_metadata_sidecar` — missing / unparseable / non-object
  sidecars degrade to "absent"; other IO errors propagate
- `split_agent_metadata` — separates the synthetic entry from transcript
  lines, last metadata entry winning (it is rewritten on resume)
- `parent_ids_from_agent_metadata` — String-narrowed id extraction

The disk reader wraps its sidecar read in a `rescue SystemCallError` so a
sidecar that exists but can't be read (a directory, EACCES) degrades to
"no metadata" rather than raising out of a best-effort read. Session
import now also survives a corrupt sidecar instead of aborting midway
with a JSON::ParserError after the transcripts were already appended; it
keeps merging the synthetic `agent_metadata` marker last, so a stray
`type` key in the CLI-owned sidecar can never shadow it.
Half of Python #1198 — `resume_session_at` already exists here; this adds
its validation companion. `--resume-drops-turn=<prompt uuid>` declares
which user prompt's turn a truncating resume intends to discard; the CLI
then validates at load time that every transcript entry past the fork
point is attributable to that turn and refuses the resume otherwise. That
lets a caller rewind to "before my last prompt" without silently dropping
a queued message or task notification the session absorbed mid-turn that
the caller never observed. A refusal arrives as an
`error_during_execution` result whose message starts with
`Resume rejected by --resume-drops-turn:`.

Emitted in equals form so the value can never parse as a separate flag,
and gated on `.nil?` rather than truthiness: an empty string is forwarded
for the CLI to reject as a malformed declaration, since dropping it here
would silently disarm the guard the caller believes is armed. No SDK-side
validation of the option combination — like the TypeScript and Python
SDKs, that is the CLI's call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UuAH421iQ9oMARmFsMFfP
Port of the stdin-lifecycle half of Python SDK PR #1204 (sha fcdae22).

wait_for_result_and_end_input decided whether the CLI might still send a
control_request needing a reply by looking only at @sdk_mcp_servers and
@hooks. A permission callback is served over the very same channel, so an
Enumerator prompt with can_use_tool and neither hooks nor SDK MCP servers
closed stdin the moment input ended — every later permission control_request
then failed CLI-side with "Stream closed". That combination is a supported
usage that has been broken.

Extracted the check as #bidirectional_needs? (the TS SDK's
hasBidirectionalNeeds, de-prefixed per Ruby naming) covering all three.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ojRwGumL91UH627ZPAnjZ
Port of the entry-point half of Python SDK PR #1204 (sha fcdae22).

query() and Client#connect each carried their own copy of the can_use_tool
validation, and query() refused a String prompt outright with "can_use_tool
callback requires streaming mode". That restriction was never real: the SDK
is always streaming internally — a String prompt is written to stdin as a
user message like any other — so with stdin held open for the turn (the
previous commit) the permission round-trip works fine.

- ClaudeAgentSDK.configure_can_use_tool(options), next to the other helpers
  shared by both entry points: returns options unchanged with no callback;
  otherwise enforces mutual exclusion with permission_prompt_tool_name,
  emits the shadowing advisory, and returns a dup_with
  permission_prompt_tool_name: 'stdio'.
- Both entry points now call it; the streaming-mode raise and the duplicated
  checks are gone.
- Ruby's wrote_message guard in stream_input is deliberately kept as-is: it
  already covers what Python added here (a `written` counter), and it is
  better — a stream that never produced a complete message closes stdin
  instead of waiting for a result that can never come. Python still documents
  that path as a limitation.
- Documented Python's remaining known limitation on
  wait_for_result_and_end_input: the condition is one-shot and unaware of
  prompts still queued CLI-side, so a multi-message Enumerator prompt
  releases the hold at the first turn boundary. Not fixed here.

The new query() specs drive a permission-gated fake transport that models
the real CLI contract (permission request only after the user message,
assistant/result only after the verdict, writes after end_input raise, and
stdin EOF ends the process) for both String and Enumerator prompts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ojRwGumL91UH627ZPAnjZ
Follow-up to the lane's four feature commits: all three fixes are
documentation-only, no behavior change.

- MessageOrigin: the shape/known-kinds prose sat in a free-floating
  comment block anchored to no object, so it rendered nowhere and both
  `@see` tags dangled. Folded into UserMessage#origin (full text) and
  ResultMessage#origin (usage + key form), which YARD now renders on the
  readers. The load-bearing sentence — keys are Symbols with the wire
  spelling preserved, so you index `origin[:fromSession]`, and a
  Python-style `origin["kind"]` silently returns nil and makes every
  attributed turn look unattributed — appears in both.
- resume_drops_turn: had only a bare attr_accessor; the prose lived on a
  private command_builder method. Documented on the option itself, per
  Python #1198: how to choose the fork point, why forking at an assistant
  UUID is refused under structured output / end-turn MCP tools, the
  `Resume rejected by --resume-drops-turn:` contract and that a refusal is
  deterministic (clear the fork target, don't retry), and the empty-string
  pass-through.
- forward_subagent_text: the docstring was on the writer only, so the
  reader rendered empty; moved to a documented attr_reader. Also dropped
  the claim that it "only applies in streaming/Client mode" — wrong here,
  since ClaudeAgentSDK.query runs the control protocol too and sends the
  capability on both paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UuAH421iQ9oMARmFsMFfP
Addresses adversarial review of the two preceding commits.

1. An `agent-<id>.meta.json` sidecar holding illegal UTF-8 bytes broke
   every later resume. `read_agent_metadata_sidecar` promised that a
   corrupt sidecar degrades to an absent one, but Ruby's `JSON.parse`
   accepts illegal bytes inside an otherwise well-formed UTF-8-tagged
   document and hands back a Hash whose values only blow up at
   `JSON.generate` time (Python's `read_text(encoding="utf-8")` raises
   instead, so it never got that far). `import_session_to_store` therefore
   persisted an `agent_metadata` entry built from it, and every subsequent
   resume through that store died re-serializing the sidecar in
   `write_subagent_files` — the error propagated out of
   `materialize_resume_session` past callers that don't rescue it, failing
   query/Client with an opaque encoding message until the store was
   repaired by hand. The reader now rejects invalid encoding up front,
   using the same `valid_encoding?` gate `parse_settings_bytes` already
   applies to settings bytes.

2. A FIFO where the sidecar is expected hung `get_subagent_messages`
   forever: `File.read` blocks with no writer, so the caller's best-effort
   `rescue SystemCallError` never fires because nothing is raised. This
   diff introduced the hang by opening a sidecar on a path that never
   opened one before. The reader now stats first and reads only a regular
   file, the same guard `read_if_present` uses on the resume seed files.

3. `write_redacted_credentials` could abort a resume, and its comment
   described behavior it did not have. For the same lenient-parse reason
   as (1), a `.credentials.json` with illegal bytes and a `refreshToken`
   key parses fine, redacts fine, then raises `JSON::GeneratorError` —
   which the `rescue JSON::ParserError` did not cover — so whether the
   resume aborted or passed the file through hinged on whether the file
   happened to carry a `refreshToken`. The redaction now lives in
   `redacted_credentials`, which returns the bytes to write or nil to
   write nothing: unparseable content still passes through, but a
   redaction that cannot be re-serialized seeds no credentials file at
   all. Writing the original bytes through would put the un-redacted
   refresh token back into the temp dir — the one thing this function
   exists to prevent — and raising would abort a resume every other seed
   file is careful not to abort. The comment now matches.

4. A lone surrogate escape (`"\ud800"`) in settings.json made the strip a
   no-op. Ruby's JSON parser rejects lone surrogates with no option to
   relax it, so the file fell through to a byte-for-byte copy carrying
   `enabledPlugins` — leaving exactly the network-install-every-resume
   behavior PR #1197 removes (Python's parser accepts them, so it strips
   normally). `parse_settings_bytes` now retries through
   `mask_surrogate_escapes`, which swaps `\uD800`-`\uDFFF` escapes for
   opaque ASCII tokens before parsing and restores them verbatim after
   re-serialization. Backslash parity is respected, so an escaped
   backslash followed by literal `uXXXX` text is left alone. The masking
   runs only as a fallback after a plain parse has already failed.

Each fix has a test reproducing the reported scenario; all four were
confirmed to fail against the previous commits.
…e helpers

Review round on the four Lane B commits.

必修 1 — can_use_tool: false was internally inconsistent. Query#bidirectional_
needs? tested `!@can_use_tool.nil?` while ClaudeAgentSDK.configure_can_use_tool
tests truthiness, so a config layer writing `can_use_tool: enabled ? cb : false`
got neither the stdio routing nor the mutual-exclusion check, yet still had
stdin held open for a reply the CLI would never be asked to produce —
stream_input parks forever. Now truthy on both sides, matching Python's
`... or self.can_use_tool`. The new spec is time-bounded: the regression hangs
rather than fails, and the ensure in wait_for_result_and_end_input calls
end_input even on timeout, so "did not time out" is the real assertion.

必修 2 — both new methods had been inserted *inside* track_task_lifecycle's
doc comment, splitting "Only delegated agent work is tracked ... A" from
"background *shell* is also reported ...", which then read as documentation
for error_result_text. Comment restored; bidirectional_needs? moved below
track_task_lifecycle.

建议修 3 — ResultError.normalize_errors / .field were public class methods
only because query.rb reached for them. Both now live in a private_constant
Payload module, and the text chain moves to ResultError.error_text, next to
the fields it must agree with (that agreement is the whole reason they share
readers). query.rb calls the single public entry point; error_result_text is
gone. .error_text stays public deliberately: it is how the read loop builds
the message, and the documented way to summarize a raw is_error result frame
you already hold.

Also taking the two recorded suggestions:
- A pending-control-request case driven by `initialize` rather than
  `interrupt` — the literal shape of a resume refused before the handshake
  is answered, which is where Lane A meets this path.
- The two hand-run smokes landed as integration specs: can_use_tool over the
  control protocol for both String and Enumerator prompts, and the typed
  ResultError on a CLI exit after an error result (budget cap is the cheapest
  deterministic trigger). They follow the file's self-skip convention. The
  permission specs assert the callback fired and the run still reached a
  non-error result — whether the model picks Write or Bash is not the SDK's
  business, and the unit specs pin the exact wire exchange.

Verified against the real CLI: all three new integration examples pass, and
the String-prompt one fails ("expected the CLI to ask can_use_tool at least
once") with the bidirectional_needs? fix reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ojRwGumL91UH627ZPAnjZ
Adds docs/ coverage for everything the three sync lanes introduced — the
lanes deliberately left README/CHANGELOG/docs alone so the release notes
could be written once, against the integrated result.

- docs/types.md: ConversationResetMessage, and a Message Origin section
  spelling out that Ruby's origin Hash is Symbol-keyed with the CLI's
  camelCase preserved (origin[:fromSession]) — the Python SDK's equivalent
  is string-keyed, so porting origin["kind"] literally reads nil and
  misclassifies every injected turn as unattributed.
- docs/errors.md: ResultError, its field set, the message-text precedence,
  and the rescue-before-ProcessError ordering.
- docs/sessions.md: resume_drops_turn, the fork-point rule, the deterministic
  refusal contract; subagent parent ids.
- docs/configuration.md: forward_subagent_text.
- examples/message_types_example.rb: handle ConversationResetMessage.
- README: message-type count 24 -> 25.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dUsxYfSqkXCVwTUbNoMyB
The conversation_reset frame was previously dropped by the parser's
forward-compat fallthrough, so consumers that raise on an unrecognized
message class start seeing it — on the first /clear of a long-lived
session. Python #1196 flagged the same widening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dUsxYfSqkXCVwTUbNoMyB
@ya-luotao
ya-luotao merged commit bafb168 into main Aug 28, 2026
3 checks passed
@ya-luotao
ya-luotao deleted the feature/sync-python-sdk-0.2.147 branch August 28, 2026 03:44
ya-luotao added a commit that referenced this pull request Aug 28, 2026
version.rb and CHANGELOG were bumped in #55; this covers the remaining
lockstep sites (README pin, plugin.json) and the skill trees.

Two skill claims went stale with #55 and are corrected rather than merely
extended: options.md and troubleshooting.md both said can_use_tool is
unsupported by ClaudeAgentSDK.query, which is exactly what #55 changed.
Also documents resume_drops_turn, forward_subagent_text,
ConversationResetMessage, message origin (Symbol keys, camelCase kept —
the Python SDK's is string-keyed), and ResultError on the resume path;
and records that settings-file rules auto-approve invisibly, since the
SDK's shadowing warning cannot see them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dUsxYfSqkXCVwTUbNoMyB
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant