# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.1.310] - 2026-10-23
Re-baseline against Claude CLI **2.1.310**. Two optional nested fields; no new
message types and no removals. `--help` and the embedded model-id set are
unchanged.
### Added
- `UnifiedWindowUsage::status` — the status the server sent for each
`rate_limit_info.unifiedWindows` entry (`allowed_warning` past a warning
threshold, `rejected` at or past the window's limit). A rejected window alone
does not mean requests are refused; the top-level status still decides that.
- `UnifiedWindowUsage::surpassed_threshold` — the highest warning threshold
passed for the window, as a fraction of its limit (1.0 once rejected).
### Breaking
- The new public fields on `UnifiedWindowUsage` break struct-literal
construction.
## [2.1.309] - 2026-10-22
Re-baseline against Claude CLI **2.1.309**. One optional field; no new message
types and no removals. `--help` and the embedded model-id set are unchanged.
### Added
- `ResultMessage::model_usage_since_last_result` — per-model usage and cost
of the calls counted since the previous result, keyed and shaped like
`modelUsage` (`ModelUsageEntry`). Sum it across a conversation's results
instead of subtracting `modelUsage` totals. Carried on both success and
error results (`@internal`).
### Breaking
- The new public field on `ResultMessage` breaks struct-literal construction.
## [2.1.308] - 2026-10-22
Re-baseline against Claude CLI **2.1.308**. The changes below arrived in CLI
2.1.307; 2.1.308's extracted wire schemas are byte-identical to 2.1.307's.
Optional fields only; no new message types and no removals. `--help` and the
embedded model-id set are unchanged.
### Added
- `CommandLifecycleMessage::wakeup_source` (`CommandWakeupSource`:
`loop_wakeup` / `schedule_wakeup`, open set) and
`CommandLifecycleMessage::scheduled_for` — on `started` frames for scheduler
fires.
- `UserMessage::requeue_if_interrupted_within_ms` — input-only request to
requeue a prompt when an interrupt crosses with the turn taking it in.
- `RateLimitInfo::admin_request_offer` (`AdminRequestOffer`,
`AdminRequestOfferState`) — the IDE limit banner's offer to ask an admin for
a higher spend limit (`@internal`).
- `RemedyFeatureCause::UnsupportedRuntime` (`unsupported_runtime`).
- `empty_completion` documented as an `api_error` kind.
### Breaking
- New public fields on `UserMessage`, `CommandLifecycleMessage`, and
`RateLimitInfo` break struct-literal construction, and the new
`RemedyFeatureCause` variant breaks exhaustive matches.
## [2.1.306] - 2026-10-20
Re-baseline against Claude CLI **2.1.306**. No wire changes: the key, enum,
and literal set-diffs between the 2.1.304 and 2.1.306 schema extracts are
empty, the per-block diff shows only minifier renames, and `--help` and the
embedded model-id set are unchanged.
## [2.1.305] - 2026-10-20
Crate-side release; still tested against Claude CLI **2.1.304**.
### Added
- `ClaudeOutput::ControlCancelRequest(ControlCancelRequest)` for the
`control_cancel_request` frame (#489). The CLI sends it to withdraw one of
its own in-flight `control_request`s, such as a `can_use_tool` prompt after
the turn was interrupted or after another client answered it. The CLI
ignores any later `control_response` for that `request_id`, and the cancel
itself gets no reply. Fields: `request_id`, plus an optional `session_id`
(stamped only by the remote bridge). Also adds
`is_control_cancel_request()` and `as_control_cancel_request()`.
Previously this frame failed to deserialize.
### Breaking
- `ClaudeOutput` gains a variant, so exhaustive `match`es on it need a new arm.
## [2.1.304] - 2026-10-19
Re-baseline against Claude CLI **2.1.304**.
### Added
- `RateLimitInfo.usage_credit_offer: Option<UsageCreditOffer>` (wire
`usageCreditOffer`, `@internal`): a limited-time usage-credit offer for the
IDE extension's limit banner, with `kind` (`UsageCreditOfferKind::PctOff` /
`ExtraCredit`, open set), `headline_pct`, `days_left` and `label`. Present
only on rejected frames from VS Code extension sessions.
### Notes
- The `get_context_usage` control reply's category rows gained an optional
`id` (`system_prompt`, `messages`, `free_space`, …). The crate does not model
that reply. The assistant `context_usage` rows (`ContextCategory`) are
unchanged.
- The `system/init.cloud_session` docs list a new `device-mcp-policy` notice
key (an open set). `cloud_session` is carried as an opaque `Value`, so no
type change is needed.
- `--help` changes are wording only (no new or removed flags), and the
model-id set is identical to 2.1.303.
## [2.1.303] - 2026-10-19
Re-baseline against Claude CLI **2.1.303**. No stream-json drift: the
key, enum and literal sets of every extracted schema are identical to
2.1.302, the identifier-normalized schema extract is byte-identical, and
the `.describe()` strings, model-id set and `--help` output are unchanged.
## [2.1.302] - 2026-10-16
Re-baseline against Claude CLI **2.1.302**. No stream-json drift: the
key, enum and literal sets of every extracted schema are identical to
2.1.301, and a label-keyed block diff shows only minifier renames. The
model-id set, `--help` output and registered CLI flags are unchanged.
### Changed
- `SubagentStats::requested` rustdoc now lists when the `run_in_background`
parameter is not offered (background tasks disabled, or subagents forced
into the background in a terminal, the desktop app, the VS Code extension,
or with `CLAUDE_CODE_FORK_SUBAGENT=1`), matching the CLI's updated
schema description.
## [2.1.301] - 2026-10-15
Re-baseline against Claude CLI **2.1.301**. Models the 2.1.300 → 2.1.301
stream-json drift: three optional fields, no new message types and no
removals. The model-id set, `--help` output and registered CLI flags are
unchanged.
### Added
- `AssistantMessage::refused_message_id`: on the assistant message that
reports a refusal, the API `msg_*` id of the refused response (the
message's own `message.id` is a local UUID). Mirrors the existing
`UserMessage::refused_message_id`.
- `InitMessage::original_cwd`: the folder the session's file permission
checks are anchored at. Unlike `cwd` it does not follow the shell.
- `InitMessage::additional_directories`: absolute paths of folders added to
the session (`--add-dir`, SDK option, settings, grants). `None` means not
told; `Some(vec![])` means none.
- `AssistantUsage::speed` (`"standard"` / `"fast"`). Already in the CLI's
usage schema and on `UsageInfo`; the live wrapping audit caught the API
now sending it on per-message usage.
### Changed
- Stream-json schema snapshot refreshed to CLI 2.1.301.
- Live tool-approval tests now pass `--permission-mode default`. Headless
sessions on accounts with the auto-mode gate enabled default to `auto`,
which lets the classifier approve the edits/reads these tests expect to be
prompted for (seen on both 2.1.300 and 2.1.301, so not a CLI-version change).
## [2.1.300] - 2026-10-14
Re-baseline against Claude CLI **2.1.300**. Models the 2.1.296 → 2.1.300
stream-json drift: optional fields only, no new message types and no
removals. The model-id set, alias table and `--model` argument list are
unchanged; `--help` differs only in the `--restricted` prose (not modeled by
this crate).
### Added
- `AssistantMessage::api_error_origin` and `ResultMessage::api_error_origin`.
`first_party` when the API error came straight from Anthropic's own API
endpoint; absent means unknown origin. Open set.
- `ToolUseMeta::server_id` (claude.ai `mcpsrv_` connector id),
`ToolUseMeta::server_id_stamp` (an http entry's `X-MCP-Server-ID` claim)
and `ToolUseMeta::tool_name` (the tool's name without the `mcp__` prefix).
- `LocalCommandRun::plugin`: the plugin that registered the command, or the
plugins whose `command.run` hooks answered it.
- `CommandLifecycleMessage::ran_ahead`: an `immediate` command's plugin hooks
answered ahead of its turn.
- `ResultMessage::interrupted_by_shutdown`: tells a multi-tenant worker
shutdown apart from a client's Stop.
- `ResultMessage::handoff_intake_phases_ms`: how a `turn_handoff` request's
wait before it was read splits up.
- `ResultMessage` process counters, sent only by Anthropic-hosted workers:
`process_rss_mib_turn_start`, `process_anon_mib_turn_start`,
`process_rss_mib_result`, `process_anon_mib_result`,
`process_peak_rss_mib`, `process_cpu_ms_between_turns`,
`process_wall_ms_between_turns`, `process_cpu_ms_turn` and
`process_wall_ms_turn`.
- `InputAttachmentsDetail::touch_compare` and the new `TouchCompareCounts`
type: how the `changed_files` producer's compares ended.
- `InformationalMessage::prompt_submit_outcome` with the new
`PromptSubmitOutcome` type and open-set `PromptSubmitOutcomeKind` enum:
how a submitted prompt ended before a model call (blocked, stopped or
dropped by a hook, not passed on, or no verdict).
- `TurnHandoffAvailableMessage::shared_message_id`: capability marker for a
worker that accepts consecutive assistant messages sharing a message id.
- `UiInvalidateMessage::prompts_screened`: a mod may change what is recorded
of a typed line.
- `UserMessage::text_not_typed`: no person typed this message's text.
### Changed
- Tested against Claude CLI **2.1.300** (was 2.1.296); stream-json snapshot
refreshed. The crate version now matches the tested CLI again.
- `MessageOrigin` absorbs the new `peer` origin keys (`flagged`, `members`)
and the `task-notification` `taskKind: "mcp-task"` through its open `extra`
map; no type change needed.
## [2.1.297] - 2026-10-10
Re-baseline against Claude CLI **2.1.296**. Models the 2.1.295 → 2.1.296
stream-json drift: two optional fields, no removals. `--help` and the
model-id set are unchanged.
### Added
- `AssistantMessage::thinking_display`. The `thinking.display` value Claude
Code put in the API request body for the response that streamed this
frame's thinking block (same meaning as
`StreamEventMessage::thinking_display`). Sent only on a frame whose one
content block is a streamed thinking block.
- `TurnHandoffAvailableMessage::hydrates_carried_lines`. Capability marker
for a worker that rebuilt the conversation from the stored transcript
before reading any request, so a client may re-send it a tool-call-free
`turn_handoff` an earlier worker accepted.
### Changed
- Tested against Claude CLI **2.1.296** (was 2.1.295); stream-json snapshot
refreshed.
- `TurnHandoffAvailableMessage::home_files` docs list the
`unchecked_announcement` and `resent_announcement` noops.
## [2.1.296] - 2026-10-08
Re-baseline against Claude CLI **2.1.295**. Models the 2.1.293 → 2.1.295
stream-json drift (2.1.294 included): three
optional fields, no removals. `--help` and the model-id set are unchanged.
### Added
- `StreamEventMessage::api_message_id`. The API's `msg_…` id of the
response an event belongs to, set on every event from `message_start` to
`message_stop` so a consumer that missed the `message_start` can still
tell responses apart.
- `StreamEventMessage::text_runs_joined`. Set on a response's
`message_start` when `CLAUDE_CODE_WEBSEARCH_CITATIONS` is on in a
non-interactive session; adjacent text blocks then arrive as one joined
assistant text block with `span_start`/`span_end` citation offsets.
- `StatusMessage::compact_output_chars`. Characters of the compaction
summary streamed so far on an automatic compaction's `compacting`
statuses.
### Changed
- Tested against Claude CLI **2.1.295** (was 2.1.293); stream-json snapshot
refreshed.
## [2.1.295] - 2026-10-07
Re-baseline against Claude CLI **2.1.293**. Models the 2.1.292 → 2.1.293
stream-json drift (optional fields and enum values added, one internal
`result` field removed) and adds the new Haiku 5.5 model. `--help` is
byte-identical.
### Added
- `ClaudeModel::Haiku55` (`claude-haiku-5-5`, "Haiku 5.5"). The `haiku`
alias now resolves to it first-party; Bedrock, Vertex, Foundry and the
gateway still resolve `haiku` to `claude-haiku-4-5`.
- `TaskStartedMessage::awaited` and `BackgroundTaskInfo::awaited`. These
flag a run of a resumed subagent that the resuming tool call waits for.
Its report goes into that call's result, so no follow-up turn comes.
- `BackgroundTaskInfo::subagent_type`. This is the agent type of a
`local_agent` task, or `main-session` for a backgrounded main session.
- `ToolResultMeta::permission_decision` (`ToolPermissionDecision`). It
reports how a tool call's permission check ended: an open-set
`PermissionDecisionOutcome` (`accept` / `reject` / `cancelled`), the
`source` that decided, and an optional open-set
`PermissionDecisionReasonType` (`rule`, `mode`, `subcommandResults`,
`permissionPromptTool`, `hook`, `asyncAgent`, `sandboxOverride`,
`workingDir`, `safetyCheck`, `classifier`, `other`).
- `StartupFailureReason::OrgConfigRequiredUnavailable` and
`StartupFailureReason::OrgConfigRefused`. Both mean the organization's
required policy limits and managed settings were unavailable or refused
at startup.
### Changed
- **Breaking:** `ClaudeModel` gains the `Haiku55` variant, which breaks
exhaustive downstream matches.
- **Breaking:** `StartupFailureReason` gains two variants, which breaks
exhaustive downstream matches.
- `MissingFileAttachment::reason` documents the new `not_found` code.
- `tests/schemas/claude_stream_json_snapshot.txt` re-snapshotted against
CLI 2.1.293.
### Removed
- **Breaking:** `ResultMessage::system_prompt_detail` and
`SystemPromptDetail`. CLI 2.1.293 no longer defines or emits the field.
If an older CLI still sends it, it is now ignored.
## [2.1.294] - 2026-10-06
Re-baseline against Claude CLI **2.1.292**. Models the 2.1.291 → 2.1.292
stream-json drift: one new `system` subtype and a set of optional fields,
all additive on the wire. `--help` and the model-id set are byte-identical.
### Added
- `system/permission_check_status` (`SystemSubtype::PermissionCheckStatus`,
`PermissionCheckStatusMessage`, open-set `PermissionCheckStatus`
`checking`/`done`, `SystemMessage::as_permission_check_status`,
`KnownSystemEvent::PermissionCheckStatus`) — a tool call has been waiting
on its auto-mode permission check for longer than usual (`tool_use_id`,
optional `agent_id`).
- `AssistantMessage::agent_id` — the subagent `task_id` that produced the
message.
- `UserMessage::agent_id` — the same id on the prompt echoed into a
subagent. The CLI's user-message schema does not list it; the live
wrapping audit (`live_subagent_session_is_fully_wrapped`) found it.
- `ApiErrorParams::rate_limit_info` (`RateLimitInfo`) for the new
`usage_limit_reached` `api_error` kind.
- `RateLimitInfo::limit_scope` and open-set `RateLimitScope` (`service`,
`channel`, `group_pool`). The CLI had sent `limitScope` on
`rate_limit_event` before 2.1.292; the crate had not modeled it.
- `ResultMessage::safety_stops` — running count of safety-system stops in
the session.
- `StreamEventMessage::abandoned_blocks` and `AbandonedBlocks`
(`api_message_id`, `from_block_index`) — on a CLI-produced `message_stop`,
the streamed blocks that will never get an assistant message.
- `run_id` on `TaskStartedMessage`, `TaskUpdatedMessage`,
`TaskProgressMessage`, `TaskNotificationMessage` and `BackgroundTaskInfo`.
- `parent_task_id` on `TaskStartedMessage` and `BackgroundTaskInfo`.
- `TaskNotificationMessage::handback` (open-set `TaskHandback`: `send`,
`flagged`, `withheld`) and `TaskNotificationMessage::handback_report`
(`HandbackReport`: `text`, optional `warning`).
### Changed
- **Breaking:** `ApiErrorParams` no longer implements `Eq`, because it now
holds a `RateLimitInfo` (which has `f64` fields). It still implements
`PartialEq`. `RateLimitInfo` now derives `PartialEq`.
- `tests/schemas/claude_stream_json_snapshot.txt` re-snapshotted against
CLI 2.1.292 (59 → 60 SDK output union members). The new `ephemeral: text,
warning` row is an extractor labelling artifact: it is the
`handback_report` object, labelled by the `cache_control` literal that
follows it in the bundle. It is not a new wire frame.
## [2.1.293] - 2026-10-06
Re-baseline against Claude CLI **2.1.291**. Pin-only: the 2.1.290 → 2.1.291
stream-json schemas are identical (object keys, enums, literals and field
descriptions), and `--help` and the model-id set are byte-identical.
## [2.1.292] - 2026-10-05
Re-baseline against Claude CLI **2.1.290**. Models the 2.1.289 → 2.1.290
stream-json drift: one new `system` subtype and three optional fields, all
additive. The model-id set is unchanged. `--help` only changed the
`attach`/`logs` background-session subcommands to accept a session name
(neither is modeled).
### Added
- `system/file_attachments_missing` (`SystemSubtype::FileAttachmentsMissing`,
`FileAttachmentsMissingMessage`, `MissingFileAttachment`,
`SystemMessage::as_file_attachments_missing`,
`KnownSystemEvent::FileAttachmentsMissing`) — files a user message sent by
reference did not arrive (`message_uuid`, `sent_count`, `missing[]` with
`file_uuid` and an open-set `reason`). Claude Code Remote workers only.
- `ResultMessage::resume_store_confirm_detail` and `ResumeStoreConfirmDetail`
(`write_ms`, `ahead_ms`, `post_ms`, `other_ms`, `posts`, `failed_posts`,
`rows_ahead`, `rows`) — how the `resume_store_confirm` phase was spent.
- `ResultMessage::system_prompt_detail` and `SystemPromptDetail`
(`user_context`, `refresh_reason`, `slowest_producer`,
`slowest_producer_ms`, `cpu_ms`) — what the `system_prompt` phase waited
for.
- `UserMessage::refused_message_id` — the API id of the refused response, on
the synthetic message that re-asks the model after a refusal.
### Fixed
- The `StartupFailureReason` rustdoc had been attached to
`InputAttachmentsDetail` since 2.1.290; it is back on the enum.
### Changed
- `tests/schemas/claude_stream_json_snapshot.txt` re-snapshotted against
CLI 2.1.290 (58 → 59 SDK output union members).
## [2.1.291] - 2026-10-04
Re-baseline against Claude CLI **2.1.289**. No wire drift: the 128 extracted
schema blocks have identical labels, object keys, enum bodies and literals
to 2.1.288, `--help` is byte-identical and the model-id set is unchanged.
### Changed
- Re-pin the tested CLI version to 2.1.289.
- Rustdoc refresh for the upstream description changes: `system/ui_log`,
`system/ui_toast` and `system/ui_status` text can now run to 10000
characters plus an ellipsis (was 4096), `ui_toast` frames are no longer
spaced 2 s apart by the hooks host, and `UiInvalidateMessage::instances`
is also sent when a `Client` drawn in those instances failed
(`ui.fault`).
## [2.1.290] - 2026-10-03
Re-baseline against Claude CLI **2.1.288**. Models the 2.1.287 → 2.1.288
stream-json drift: one new `system` subtype and seven optional fields, all
additive. The model-id set is unchanged. `--help` only swapped the
`project` management subcommand for `purge [path]` (neither is modeled).
### Added
- `system/instruction_size_warning` (`SystemSubtype::InstructionSizeWarning`,
`InstructionSizeWarningMessage`, `SystemMessage::as_instruction_size_warning`,
`KnownSystemEvent::InstructionSizeWarning`) — the session's instruction
files add up to more than the recommended limit (`total_chars`,
`total_limit_chars`, `file_count`, optional `largest_chars`). Sent to
Claude Desktop's Code tab only; safe to ignore.
- `ResultMessage::input_attachments_detail` and `InputAttachmentsDetail`
(`slowest_producer`, `slowest_producer_ms`, `cpu_ms`, `major_faults`,
`changed_files_reread`) — what the `input_attachments` phase did on the
turn.
- `ResultMessage::flag_fetch_kick` — whether start-up kicked off the
feature-flag fetch early, or why not (open string set).
- `InformationalMessage::tag` — opaque feature tag on a line a host may treat
specially.
- `TurnHandoffAvailableMessage::carried_writes` — the worker writes the files
of already-answered Write calls a `turn_handoff` request carries.
- `ApiErrorParams::media` / `ApiErrorParams::media_reason` with the open-set
`ApiErrorMedia` (`image` / `document`) and `ApiErrorMediaReason`
(`unprocessable` / `unsupported_by_model` / `media_budget`), the parameters
of the new `media_removed` value of `AssistantMessage::api_error`.
### Changed
- `tests/schemas/claude_stream_json_snapshot.txt` re-snapshotted against
CLI 2.1.288 (57 → 58 SDK output union members).
## [2.1.289] - 2026-10-02
### Fixed
- `LoginFlow::submit_code` no longer wraps the authorization code in
bracketed-paste markers unless the CLI's login screen has actually switched
bracketed-paste mode on (`ESC[?2004h`). Claude CLI 2.1.280 never enables it
there and does not strip the markers, so the opening `ESC[200~` was accepted
as literal input and sent to the token endpoint as the first seven
characters of the code (`"code":"\u001b[200~<real code>"`, intercepted
live). The server answered `invalid_grant` and the CLI printed
`Login failed: Request failed with status code 400` for every well-formed
code, surfacing as `Error::CodeRejected`. The framing is still applied when
the TUI advertises bracketed paste (2.1.220 behaviour), and the delayed lone
CR that defeats the 64-byte paste-classification swallow is sent in both
cases. `SUBMIT_PATH` is now `.../v7` so deployed builds are identifiable
from the timeout channel line.
## [2.1.288] - 2026-10-02
Re-baseline against Claude CLI **2.1.287**. Models the 2.1.286 → 2.1.287
stream-json drift: six optional fields on three frames, all additive. No new
subtypes. The bundle dropped the internal `set_expanded_view` side-panel hint
(never modeled) and hid `--client-data-url` from `--help` while still
accepting it. The model-id set gained only three internal strings
(`claude-code-tools`, `claude-code-gh-standin`, `claude-mods`).
### Added
- `AssistantMessage::thinking_duration_ms` — how long a thinking-only frame's
block took to stream, from `content_block_start` to `content_block_stop`,
for display.
- `ResultMessage::first_text_post_queue_wait_ms` and
`ResultMessage::first_text_post_queued_behind` (reusing
`StreamPostQueuedBehind`) — the text POST twins of the stream POST queue
timing added in 2.1.278.
- `ResultMessage::turn_start_resume_kind` and `TurnStartResumeKind`
(`orphaned_permission` / `turn_handoff` / `deferred_tool_use` / `none`,
open set) — what the turn had to settle before its own prompt.
- `TurnHandoffAvailableMessage::home_files` and `::file_names` — two more
capability markers a cloud worker announces on
`system/turn_handoff_available`.
### Changed
- `ResultMessage::time_to_request_phases_ms` docs follow the 2.1.287 phase
split: `turn_start_resume` became `resume_other`, `resume_store_confirm`,
`resume_staged_files_wait`, `resume_connector_wait` and
`turn_start_tool_run`; `process_user_input` now excludes `input_images`,
`input_attachments`, `input_command` and `input_hooks`; and
`system_prompt_rebuild` is split out of `system_prompt`. The map was
already an open set, so no type change.
- `TurnHandoffAvailableMessage::staged_files` docs note the `filestore_path`
exception (a synced file is not waited for).
- `CliFlag::ClientDataUrl` docs note the flag is hidden from `--help` on
2.1.287+ but still accepted.
- `tests/schemas/claude_stream_json_snapshot.txt` re-snapshotted against CLI
2.1.287.
## [2.1.287] - 2026-10-01
Re-baseline against Claude CLI **2.1.286**. Models the 2.1.285 → 2.1.286
stream-json drift: three optional timing fields, all additive. No removals,
no new subtypes, and `--help` is unchanged. The bundle's model-id set lost
only a stray `claude-haiku-3-55` string that the crate never modeled.
### Added
- `UserMessage::server_received_wall_ms` — epoch ms on the session server's
clock when it received the HTTP request that delivered the message. Stamped
by the session server, not settable by clients.
- `ResultMessage::first_request_input_tokens` — input + cache-read +
cache-creation tokens of the model call sent at `request_sent_wall_ms`, for
grouping turn latency by prompt size.
- `ResultMessage::user_message_server_received_wall_ms` — the triggering user
message's `server_received_wall_ms`, copied onto the result so the session
server can measure from its own receipt to `request_sent_wall_ms`.
### Changed
- `tests/schemas/claude_stream_json_snapshot.txt` re-snapshotted against CLI
2.1.286.
## [2.1.286] - 2026-09-30
Re-baseline against Claude CLI **2.1.285**. Models the 2.1.284 → 2.1.285
stream-json drift, all additive: eight new `system` subtypes (the SDK output
union grew 49 → 57 members), eight optional fields on `result`, one on usage,
one on the compaction metadata and one new startup failure reason. No
removals. The set of model ids in the bundle is unchanged from 2.1.284.
### Added
- `system/session_title_changed` — `SessionTitleChangedMessage`
(`SystemSubtype::SessionTitleChanged`, `KnownSystemEvent` variant,
`is_`/`as_` accessors). The session's name for a host that shows it: sent
at startup when the session already has one and after each rename. It can
arrive before the `system/init` that first names its `session_id`.
- Seven plugin-UI pushes a headless session sends for a remote surface to
draw, each with its `SystemSubtype` / `KnownSystemEvent` variant and
`is_`/`as_` accessors:
- `system/ui_log` (`UiLogMessage`), `system/ui_toast` (`UiToastMessage`)
and `system/ui_status` (`UiStatusMessage`, whose `text` is `None` when
the plugin clears its pinned line) — the plugin's `$.ui.log` /
`$.ui.toast` / `$.ui.status` text.
- `system/ui_invalidate` (`UiInvalidateMessage`, with the optional
`UiInvalidatedInstance` list) — drawn trees may be stale.
- `system/ui_panes` (`UiPanesMessage` / `UiPane`) — the placed-pane roster
with its shown, focused and focus-requested pane ids.
- `system/ui_scroll` (`UiScrollMessage`) and `system/ui_focus`
(`UiFocusMessage`) — a plugin moved a site's window or focus ring; the
site is named by the open-set `UiSiteComponent` (`Pane`, `AbovePrompt`).
- `ResultMessage` request-timing fields, present on `CLAUDE_CODE_REMOTE`
sessions and hosts that set `CLAUDE_CODE_EMIT_STARTUP_TIMING`:
`time_to_request_phases_ms` (open-set phase → ms map summing to
`time_to_request_ms`), `process_turn_index`, `time_to_request_cpu_ms`,
`time_to_request_loop_lag_ms` and `time_to_request_major_faults`.
- `ResultMessage::turn_start_phases_ms` (open-set step → ms map covering
`frame_enqueued_wall_ms` → `turn_started_wall_ms`) and
`::turn_start_control_requests_ms`.
- `ResultMessage::api_error` — the typed kind of the API error that ended
the turn, mirroring `AssistantMessage::api_error`.
- `UsageInfo::fallback_credit` and `AssistantUsage::fallback_credit` — the
outcome of a fallback-credit token the request presented (opaque JSON;
`null` on the wire when none was presented, which a live 2.1.285 result
does emit).
- `CompactMetadata::pre_compact_artifact_read_versions`
(`ArtifactReadVersion { slug, ver }`) — the artifact versions the
conversation held before the compaction.
- `StartupFailureReason::ProviderNotAllowed` (`provider_not_allowed`):
managed settings' `allowedProviders` does not list the session's API
provider.
### Changed
- `StartupFailureReason::ManagedSettingsInvalid` doc: also reported when
managed model settings block the default model and leave none allowed.
- `lib.rs` tested-version doc line caught up (was stuck at 2.1.283).
### Not modeled
- 2.1.285 also added a family of host → CLI control request subtypes for the
same plugin-UI surface (`ui_attach`, `ui_detach`, `ui_render`, `ui_press`,
`ui_input`, `ui_select`, `ui_close`, `ui_copy`, `ui_message`,
`ui_pane_show`, `ui_pane_focus`, `ui_prompt_*`, `ui_client_*`) plus
`list_directory`. `ControlRequestPayload` covers only tool permission,
hook, MCP, initialize and interrupt requests, so these stay out.
- `claude --help` gained `--desktop` (open the session in the Claude Desktop
app). Interactive-only, like `--cloud` and `--tmux`; not mirrored on
`CliFlag`.
## [2.1.285] - 2026-09-28
### Added
- **`ClaudeModel::Sonnet55`** (`claude-sonnet-5-5`, "Sonnet 5.5",
released 2026-09-28) — and the `sonnet` floating alias now resolves
there first-party (staged rollout in the 2.1.284 bundle, new table
active). The only registry addition since the 2.1.280 catalog refresh.
### Changed
- Re-baseline the tested pin to Claude CLI **2.1.284**: full integration
tier green, drift script clean (no wire changes).
## [2.1.284] - 2026-09-26
Re-baseline against Claude CLI **2.1.283**. Models the 2.1.282 → 2.1.283
stream-json drift: one additive optional field on `stream_event`, no removals,
no new subtypes, and no enum or literal changes anywhere in the extracted
schemas (nested objects included). The model registry is byte-identical to
2.1.282. `claude --help` gained one flag, mirrored on `CliFlag`.
### Added
- `StreamEventMessage::thinking_display: Option<String>` — the
`thinking.display` value Claude Code put in the API request body for the
response the event belongs to, i.e. which display the thinking text was
requested with (not how to show it). Set on `message_start`, on a thinking
block's `content_block_start` and on `thinking_delta` events when the body
had a display; absent on every other event, on Remote Control sessions and
from older CLIs. Upstream marks it `@internal`, so the value set is open.
- `CliFlag::ClientDataUrl` (`--client-data-url <url>`) — URL of a signed
configuration document; the CLI exits if it cannot load it or it does not
cover the selected model. The `CLAUDE_CODE_CLIENT_DATA_URL` environment
variable is the equivalent that keeps the URL out of the process list.
## [2.1.283] - 2026-09-25
### Added
- **`LoginFlow::poll_outcome`** — poll for login completion WITHOUT
submitting a code. Investigated on CLI 2.1.282: the sign-in flow now
opens the browser and polls the authorization session in the
background (device-code grant, PKCE authorize URL redirecting to the
hosted `platform.claude.com/oauth/code/callback` page); credentials
are persisted by the CLI when the user approves, and the on-screen
"Paste code here if prompted" box is only the fallback for machines
where the recommended sign-in isn't available. Drivers should
interleave `poll_outcome` with their own paste handling and treat
either as completion — wirecheck's login card now does exactly that
(previously it parked on the paste and would time out on logins that
had already succeeded).
## [2.1.282] - 2026-09-25
Re-baseline against Claude CLI **2.1.282**. Models the 2.1.281 → 2.1.282
stream-json drift: one additive optional field on `result`, no removals and
no new subtypes. The model registry and `claude --help` are byte-identical
to 2.1.281. The only other schema change is a new `session-inbox` value in
the task-notification provenance `subkind`, which `MessageOrigin` already
carries verbatim through its open `kind` + flattened `extra` shape.
### Added
- `ResultMessage::frame_intake_phases_ms: Option<BTreeMap<String, u64>>` —
how the triggering send's frame spent the time between
`frame_received_wall_ms` and `frame_enqueued_wall_ms`, in integer
milliseconds per intake step (`before_read`, `dedup`, `flag_settle`,
`receive_hook`, `attachments`, `admission_wait`, `admit_check`, `other`).
Steps that did not run are absent; `before_read` and `other` are always
present; unrecognized step names are kept. Present exactly when
`frame_received_wall_ms` is, except from older producers.
### Changed
- Re-snapshot `tests/schemas/claude_stream_json_snapshot.txt` from CLI
2.1.282 and re-pin `TESTED_VERSION`.
## [2.1.281] - 2026-09-24
Re-baseline against Claude CLI **2.1.281**. Models the 2.1.280 → 2.1.281
stream-json drift: one new `system` subtype and seven additive optional
fields across three wire types, no removals. The model registry is
byte-identical to 2.1.280; the only CLI surface change is `--agents`
accepting a file path under `--print`.
### Added
- `system/per_turn_effort_changed` (`SystemSubtype::PerTurnEffortChanged`,
`PerTurnEffortChangedMessage`, `KnownSystemEvent::PerTurnEffortChanged`,
`SystemMessage::{is,as}_per_turn_effort_changed`) — the conversation
stopped sending effort per turn after the server refused it; always
carries `per_turn_effort_active: false`.
- `InitMessage::per_turn_effort_active` — whether per-turn effort is active
for the init frame's model (`true` only ever arrives on an init).
- `InitMessage::view_mode` (`ViewMode`: `focus` | `default`, open-set) —
the `/focus` transcript view, on Remote Control bridge and headless
stream-json inits.
- `ConversationResetMessage::trigger` (`ConversationResetTrigger`: `clear`
| `plan_mode_exit` | `fresh_session` | `onboarding`, open-set),
`::user_message_uuid` (trigger `clear` only: the `/clear` message's uuid)
and `::timestamp` (ISO 8601 UTC, display only).
- `AssistantMessage::local_command_outcome` (`LocalCommandOutcome` with
`LocalCommandOutcomeKind`: `unavailable_headless` | `unknown` | `failed`
| `restart_required`, open-set, plus the `unknown` kind's closest-command
`suggestion`) — what a local-command row reports, so a host can offer a
fix instead of relaying the text.
### Changed
- Re-baseline the tested pin to Claude CLI **2.1.281** and refresh the drift
snapshot (49 union members). `CliFlag::Agents` docs note that `--print`
also accepts a file path.
## [2.1.280] - 2026-09-22
### Added
- **`ClaudeModel`**: `Opus55` (`claude-opus-5-5`, "Opus 5.5", released
2026-09-22 — the `opus` floating alias now resolves here first-party),
plus `Fable51` (`claude-fable-5-1`) and `Mythos51`
(`claude-mythos-5-1`) from the 2.1.280 binary's model registry.
- `AssistantMessage.narration_hint` — optional `"hidden"`/`"faint"`
renderer preference for the frame's narration blocks (2.1.280+),
companion to `narration_block_indexes`; kept open for future values.
### Changed
- Re-baseline the tested pin to Claude CLI **2.1.280**: full integration
tier green (28 live incl. the strict stream audit) and the drift
snapshot refreshed (`assistant` + `narration_hint` was the only wire
change from 2.1.278).
## [2.1.278] - 2026-09-19
Re-baseline against Claude CLI **2.1.278**. Models the 2.1.276 → 2.1.278
stream-json drift: all additive, all optional. Every enum value set and wire
literal is unchanged, and `claude --help` and the model catalog are
byte-identical to 2.1.276.
### Added
- `InitMessage::scratchpad_path` — absolute path of the session's scratchpad
directory; omitted when the scratchpad is disabled and from the redacted
init sent over the Remote Control bridge.
- `TurnHandoffAvailableMessage::staged_files` and `::no_query_first` — two
more worker capability markers, previously landing in `extra`.
- `UserMessage::initiator` — the host's label for what started the turn
(`scheduled-task`, `sleep-auto-resume`, …); usage attribution only.
- `UserMessage::pasted_content` — content the user pasted rather than typed,
each entry a string or an array of content blocks. The top-level drift
check does not see this one (it sits on the nested SDK user-message
schema).
- `CommandInfo::builtin` — marks Claude Code's own slash commands on
`system/commands_changed` rows. Also nested-only.
- `ResultMessage` timing instrumentation:
`first_stream_post_queue_wait_ms`, `first_stream_post_queued_behind`
(`StreamPostQueuedBehind`, open-set over `durable_post`, `ephemeral_post`,
`retry_backoff`, `hold`, `none`), `frame_received_wall_ms`,
`frame_enqueued_wall_ms`, `turn_started_wall_ms`, `first_text_post_ms`,
`first_text_post_wall_ms`.
## [2.1.276] - 2026-09-18
Re-baseline against Claude CLI **2.1.276**. Models the 2.1.274 → 2.1.276
stream-json drift: two additive fields and one required→optional flip. The
model catalog, alias table, and CLI argument surface are byte-identical to
2.1.274 apart from minified renames.
### Added
- `PermissionDeniedMessage::decision_reason_code`
(`PermissionDeniedReasonCode`, open-set over
`classifier_transcript_too_long`, `outside_reads_blocked`,
`memory_paused`) — a code a host can act on beside the unchanged
`decision_reason_type`; absent for every other reason.
- `ToolResultMeta::remedy` (`ToolResultRemedy`) — the fix a host can offer
for what a `tool_result` reports, stamped from structured producer state:
`kind` (`ToolResultRemedyKind`, 14 known kinds from `mcp_needs_auth` to
`staged_for_review`) plus the parameters that kind needs — `servers`,
`provider` (`RemedyLoginProvider`: `claude_ai`, `claude_design`), `path`,
`managed`, `feature` (`RemedyFeature`: `workflows`, `artifacts`), `cause`
(`RemedyFeatureCause`), and `policy_kind` (`RemedyPolicyKind`). Every enum
is open-set with an `Unknown(String)` arm.
### Changed
- **Breaking:** `ToolResultMeta::non_execution_kind` is now
`Option<String>`. The CLI made it optional because an entry may carry a
`remedy` alone, on a result that did run (a staged settings write, a
sandbox violation on exit 0).
- Re-snapshot `tests/schemas/claude_stream_json_snapshot.txt`.
Not modeled: the bundle's new MCP-server error-code list
(`CLAUDEAI_BEARER_REJECTED`, `FIRST_PARTY_AUTH_REJECTED`,
`APPROVAL_REQUIRED`) is host-side telemetry routing and appears in no
stream-json schema.
## [2.1.274] - 2026-09-17
Re-baseline against Claude CLI **2.1.274**. Models the 2.1.273 → 2.1.274
stream-json drift, all additive (no removals or required/optional flips).
The model catalog and CLI argument surface are byte-identical to 2.1.273.
### Added
- `AssistantMessage::api_error_code` — the server's `error.details.error_code`
for a synthetic API-error message, copied through when it is an identifier
(`^[a-z][a-z0-9_]{0,63}$`). Carries server gate codes the CLI build has no
`api_error` value for, so a host can key on a new gate without a Claude
Code release.
- `AssistantMessage::api_error_params` (`ApiErrorParams { effort, provider,
remedy }`) — parameters of `api_error`, present only for the kinds that
have any: the refused effort level (`effort_requires_thinking`), the
provider whose credentials failed, and how to repair them
(`provider_credentials`, `gateway_session_expired`). Open-set enums
`ApiErrorProvider` (`bedrock`, `anthropicAws`, `mantle`,
`anthropicGoogleCloud`, `vertex`, `foundry`, `gateway`) and
`ApiErrorRemedy` (`refresh_command`, `refresh_credentials`, `adc`,
`gateway_token`, `host_managed`, `model_access`).
- `ResultMessage::api_error_code` — the `api_error_code` of the API error
that ended the turn, on a `success` result with `is_error: true`.
- `ResultMessage::startup_failure_reason` (`StartupFailureReason`, open-set
over the 16 known causes such as `cwd_unavailable`, `cli_version_too_old`,
`worktree_resume_refused`, `bypass_root`) — set on the zeroed
`error_during_execution` result a stream-json run writes before exiting on
a known startup failure; `errors` carries the same text as stderr. Paths
that used to end with stderr alone write that result only when the host
sets `CLAUDE_CODE_STARTUP_FAILURE_RESULTS`.
### Changed
- `AssistantMessage::api_error` is documented as the open set it always was;
2.1.274 grows it from 3 to 24 kinds (`effort_requires_thinking`,
`provider_credentials`, `gateway_session_expired`,
`model_requires_usage_credits`, `field_not_granted`, ...). No type change.
- `system/init` `mcp_servers` entries gained an optional `source`; the crate
carries them as untyped `Value`s, so nothing changes.
- Re-snapshot `tests/schemas/claude_stream_json_snapshot.txt`.
## [2.1.273] - 2026-09-16
Re-baseline against Claude CLI **2.1.273**. Models the 2.1.270 → 2.1.273
stream-json drift, all additive (no removals or required/optional flips).
### Added
- Three `system` subtypes, each with a `SystemSubtype` variant, a typed
struct, `is_*`/`as_*` accessors on `SystemMessage`, a `KnownSystemEvent`
variant, and wrap-audit coverage:
- `turn_handoff_available` → `TurnHandoffAvailableMessage`: a cloud
worker that accepts the `turn_handoff` control request announces the
contract version, the tools it would accept, its `worker_epoch`, and
`relay_marker`.
- `turn_preempted` → `TurnPreemptedMessage`: the CLI stopped the running
turn so a rapid follow-up (`reason: rapid_followup`) is answered at once;
carries `preempted_by_uuid` and `preempted_message_uuids`. Precedes the
stopped turn's `result` frame. Only sent to consumers that declared
`rapidFollowupPreempt` on initialize.
- `peer_message_hold` → `PeerMessageHoldMessage`: a cross-session message
the receive-side policy held rather than queued, and how the hold
resolved. Open-set enums `PeerMessageHoldState` (`held`/`released`/
`dropped`), `PeerMessageLane` (`bridge`/`stdin`/`socket`),
`PeerMessageHoldCause`, and `PeerMessageHoldOutcome`.
- `AssistantMessage.usage_report` (`Option<Box<UsageReport>>`) — structured
twin of the `/usage` report carried on the synthetic assistant message
that delivers its text: the session totals (reusing `UsageSession`), the
server's usage rows (`UsageReportRateLimits` / `UsageReportLimit` /
`UsageReportScope`), and extra-usage spend (`UsageReportExtraUsage`).
Boxed to keep `ClaudeOutput` within clippy's variant-size budget.
- `AssistantMessage.local_command_run` (`LocalCommandRun { command, args }`)
— on the local-command twin, the run that printed the row.
- `TaskNotificationMessage.reason` (`TaskEndReason`, currently only
`worker_restart`) — set when a task did not end through an ordinary
completion, failure, or stop.
- `DevIntentMessage.trigger` (`DevIntentTrigger`, open set of eleven
values incl. `project_scan`) — the evidence a `dev_intent` detection fired
on, and `DevIntentKind::AndroidApp`. The CLI now also scans the session's
git repository in print mode and reports project evidence at startup,
after each `conversation_reset`, and at the first message of a
conversation whose earlier scan did not find every kind.
### Changed
- `audit_frame` compares JSON numbers numerically: a whole-valued float the
CLI serialized as an integer (`40`) no longer counts as changed when an
`f64` field re-serializes it as `40.0`. This was a false positive, not a
data loss, and `/usage` frames trip it routinely.
- `CodeChangePublishedMessage.branch` is now also sent with `created` on any
provider (the branch the create was opened from). Doc-only.
- Re-pin to Claude CLI **2.1.273**; snapshot refreshed. The live
integration suite passes unchanged.
## [2.1.270] - 2026-09-13
### Changed
- Re-pin to Claude CLI **2.1.270**. No stream-json wire drift from
2.1.269: the fingerprint check is clean against the committed snapshot,
and a field-level diff of all 108 extracted zod schemas is empty after
normalizing minified aliases (no doc-string edits this time either). The
model registry, alias table and accepted `--model` argument list are
byte-identical between the two binaries. The live integration suite
passes unchanged. Pin-only release.
## [2.1.269] - 2026-09-12
### Changed
- Re-pin to Claude CLI **2.1.269**. No stream-json wire drift from
2.1.268: the fingerprint check is clean against the committed snapshot,
and a field-level diff of the extracted zod schemas shows only minified
alias churn and doc-string edits. The one substantive doc change is that
`user_message_uuid` is now stamped on both the turn's first non-ping
stream event and its first complete assistant message, independently, so
the same uuid may appear on both frames. The live integration suite
passes unchanged. Pin-only release.
## [2.1.268] - 2026-09-11
Re-baseline against Claude CLI **2.1.268**. Models the 2.1.267 → 2.1.268
stream-json drift, all additive (no removals or required/optional flips).
### Added
- `resume_reason` on `AssistantMessage`, `StreamEventMessage`, and
`ResultMessage` — why the turn is the automatic re-run of a turn a worker
restart interrupted (`CLAUDE_CODE_RESUME_INTERRUPTED_TURN`): the host's
`CLAUDE_CODE_RESUME_REASON` when it set one (`host_draining`,
`checkpoint_restore`, `container_recreated`, ...), else `interrupted_turn`.
Stamped on the same reply frames as `user_message_uuid`, and on the
re-run's result whether success or error.
- `ResultMessage.result_index` — delivery sequence of the result within the
run, numbered by the hosting process in write order starting at 0. A
result whose write fails still consumes its number, so a gap means a
result was lost.
- `ResultMessage.local_command` — the local slash command that produced a
success result when the query loop was bypassed.
### Changed
- Re-pin to Claude CLI **2.1.268**; snapshot refreshed. The live
integration suite passes unchanged.
## [2.1.267] - 2026-09-10
### Changed
- Re-pin to Claude CLI **2.1.267**. No stream-json wire drift from
2.1.266: the fingerprint check is clean against the committed snapshot,
and the live integration suite passes unchanged. Pin-only release.
## [2.1.266] - 2026-09-09
Re-baseline against Claude CLI **2.1.266**. Models the 2.1.263 → 2.1.266
stream-json drift, all additive (no removals or required/optional flips).
### Added
- `SystemSubtype::DevIntent` / `DevIntentMessage` / `DevIntentKind` /
`KnownSystemEvent::DevIntent`, plus `SystemMessage::is_dev_intent` /
`as_dev_intent` — the new `system/dev_intent` frame the CLI emits once per
recognized kind of development work per conversation (e.g. `ios_app`, which
Claude Code Desktop keys its iOS Simulator entry point on). `kind` is an
open enum with an `Unknown` fallback, matching the CLI's "ignore a kind you
do not recognize" contract.
- `AssistantMessage.historical` and `AssistantMessage.wire_ingest_context` —
the Remote Control replay flag and the input-normalization provenance map
that rides alongside `wire_tool_inputs`.
- `ResultMessage.runner_exit` (`RunnerExit` with an open `RunnerExitPhase`
enum) — set by the self-hosted runner on a synthesized failure result when
the session process fails to start or exits abnormally.
- `InitMessage.startup_timing` — cold-start telemetry (named phases and
resume-hydration counters) on hosted-session init frames, stored as raw
JSON.
- `CompactBoundaryMessage.historical` and `UserMessage.historical` — the same
Remote Control replay flag on those two frames.
### Changed
- The committed schema snapshot moves to 2.1.266. Between 2.1.263 and 2.1.266
the CLI also tightened the assistant/user `message` field from `z.unknown()`
to a concrete Anthropic-Messages-API schema, so the extractor now reaches
the API content-block schemas (`text`, `image`, `thinking`, `tool_use`,
`tool_result`, `document`, `search_result`, `redacted_thinking`,
`tool_reference`, `file`, `url`, `message`) and the snapshot records them.
These are passthrough shapes the crate already types via `ContentBlock`
(with an `Unknown` fallback), so no new content-block modeling is required;
the wire JSON is unchanged. The full live integration suite passes against
2.1.266.
## [2.1.263] - 2026-09-07
### Changed
- Re-baseline the tested pin to Claude CLI **2.1.263**: `TESTED_VERSION`,
both README `Tested against:` lines and the crate version move to 2.1.263.
A full field-by-field diff of the extracted stream-json schemas
(2.1.261 → 2.1.263) shows only renamed minified identifiers; every wire
label, field and enum is unchanged, and the committed snapshot is
byte-identical after `--update`. The integration suite passes unchanged
against 2.1.263. No code changes beyond the pin.
## [2.1.261] - 2026-09-05
Re-baseline against Claude CLI **2.1.261**: the tested pin (`TESTED_VERSION`,
both README `Tested against:` lines) and the crate version move to 2.1.261.
Models the 2.1.259 → 2.1.261 stream-json drift (all additive) reported by the
nightly drift check on issue #361, plus the one field 2.1.261 added on top of
the 2.1.260 report.
### Added
- `SystemSubtype::CloudSessionDelta` / `CloudSessionDeltaMessage` /
`KnownSystemEvent::CloudSessionDelta` — the new `system/cloud_session_delta`
frame the headless client of a cloud-hosted session writes when the
session's status changes between two inits (`seq`, `changed`, and the
whole `cloud_session` block as raw JSON, matching `InitMessage`).
- `AssistantMessage.narration_block_indexes` — indexes into
`message.content` of the thinking blocks the server tagged as narration.
- `ResultMessage.first_content_frame_ms`, `.first_stream_post_ms`,
`.first_stream_post_ack_ms`, `.first_stream_post_wall_ms` — stream-timing
instrumentation on `result/success`.
- `ApiRetryMessage.no_response` (`ApiRetryNoResponse`) — present only on
first-byte-timeout retries, with how long the attempt waited and how long
the retry will wait.
- `ThinkingTokensMessage.user_message_uuid` — the client uuid of the send the
thinking progress belongs to.
### Fixed
- `scripts/extract_claude_sdk_schemas.py` now resolves schema references only
inside the bundle chunk that holds the SDK output union. On 2.1.261 the
assistant schema calls a plain `se()` helper (`z.unknown()`) whose only
lazy definition lives in another chunk; following it crawled that chunk's
module graph and inflated the extraction to 667 "schemas" (and the drift
report to 89 phantom wire labels). The 2.1.239 and 2.1.259 extractions are
byte-identical before and after.
## [2.1.259] - 2026-09-03
Re-baseline against Claude CLI **2.1.259**: the full live integration suite
(28 tests) plus the live subagent-wrapping audit pass, so the tested pin
(`TESTED_VERSION`, both README `Tested against:` lines) and the crate version
move to 2.1.259. Models the 2.1.239 → 2.1.259 stream-json drift (all additive)
reported by the nightly drift check on issue #354.
### Added
- `AssistantMessage.user_message_uuid` / `.user_message_uuids` — the client
uuid(s) that triggered the turn, stamped on the first reply frame so a
consumer can bind the reply to its send. Also added to `StreamEventMessage`
and `ResultMessage`.
- `AssistantMessage.wire_tool_inputs` and `.local_command_source` —
round-trip-only wrapper siblings the CLI carries for history replay.
- `ResultMessage.queued_turn_count` — user-initiated sends still queued when
the result was produced.
- `UserMessage.client_composed` — the client composed the turn from content
the user did not type.
- `McpMeta.resource_links` and `TaskNotificationMessage.resource_links`
(`ResourceLink`) — files an MCP tool returned by reference.
- `TaskStartedMessage.ambient`, `TaskNotificationMessage.ambient`, and
`BackgroundTaskInfo.ambient` — housekeeping tasks hosts should hide from
activity indicators.
- `CodeChangePublishedMessage.branch` — the working branch for providers
(`gerrit`) whose changes have no head branch of their own.
- `InitMessage.footer_indicator` (`FooterIndicator`), `.worker_epoch`, and
`.powershell_path`.
- `RateLimitInfo.unified_windows` (`UnifiedRateLimitWindows`,
`UnifiedWindowUsage`) and `.rate_limit_grace_active` — per-window
subscription usage and the latched grace signal. The nested
`unified_windows` drop was surfaced by the live subagent-wrapping audit,
not the coarse (top-level-only) drift checker.
- `AssistantErrorKind::AccountOnHold` — a wire error value the enum was
missing.
### Fixed
- The schema drift checker's key scanner now skips backtick template literals
(and their `${...}` interpolations) in `.describe()` prose, which 2.1.259's
`system/init.footer_indicator` describe introduced — previously they
desynced the top-level field scan.
## [2.1.240] - 2026-09-02
### Fixed
- Login flows: the login child exiting with `Login failed: <reason>`
(CLI >= 2.1.25x behavior after a rejected token exchange) now
classifies as `CodeRejected` instead of the `LoginChildExited`
forensics blob. Reproduced live on claude 2.1.259 by the agent-portal
consumer.
## [2.1.239] - 2026-09-01
Re-baseline against Claude CLI **2.1.239**: the full live integration suite
(28 tests) passes, so the tested pin (`TESTED_VERSION`, both README
`Tested against:` lines) and the crate version move to 2.1.239. Models the
2.1.232 → 2.1.239 stream-json drift (all additive) and repairs the schema
extractor against the CLI's new bundle style.
### Added
- `AssistantMessage.context_usage` (`ContextUsage` + `ContextCategory`,
`ContextOverLimit`, `ContextMcpTool`, `ContextMemoryFile`, `ContextAgent`,
`ContextSkill`) — the structured twin of the `/context` report — and
`AssistantMessage.batch_tool_uses` (`BatchToolUse`).
- `ResultMessage.subagent_stats` (`SubagentStats` + `SubagentSpawnRequests`,
`SubagentKillCounts`, `SubagentRefusalCounts`) — running totals for
subagents started through the Agent tool.
- `InitMessage.effort` (the session's resolved effort level) and
`InitMessage.cloud_session` (raw JSON snapshot on cloud-hosted sessions).
- `TaskStartedMessage.{is_backgrounded, spawn_depth}`,
`CodeChangePublishedMessage.action`, `VcsStateChangedMessage.branch`,
`ModelRefusalFallbackMessage.saw_cyber_refusal`, and
`UserMessage.seeded_summon`.
### Fixed
- `scripts/extract_claude_sdk_schemas.py` (and with it the nightly drift
check, which had been soft-skipping since the CLI's bundler switched
styles): schemas are now also discovered in the free-function zod style
(`ve(()=>_e({type:Tt("…")}))`) used by CLI 2.1.239+, alongside the
zod-namespace style (`Se(()=>E.object({type:E.literal("…")}))`) of older
binaries. The snapshot is re-baselined against 2.1.239 (43 wire labels).
## [2.1.234] - 2026-08-19
### Added
- **`version::tested_cli_version()`** — the tested-against CLI release,
machine-readable from the published artifact (previously a private
const only the runtime warning could see). CI keeps it in lockstep
with the README's `Tested against:` line.
## [2.1.233] - 2026-08-19
### Added
- **`transcript` module** — the CLI's unpublished on-disk transcript
path rule, exported so consumers stop growing private copies:
`encode_project_dir` (`/` and `.` → `-`, measured against real
transcript stores and pinned by tests, documented as lossy) and
`transcript_path(home, cwd, session_id)`, which takes `home`
explicitly so a test can never accidentally resolve into a real
`~/.claude`. Extracted at agent-portal's request to delete its
duplicate implementation.
## [2.1.232] - 2026-08-14
### Added
- Three fields CLI 2.1.232 added to the wire, each found by wirecheck's
live subagent wrapping audit (present on the wire, dropped by the typed
model): **`messaging_socket_path`** and **`terminal_slash_commands`**
on the `system` init message, and
**`usage.output_tokens_details.thinking_tokens`** on the `result`
message (typed as `OutputTokensDetails`).
### Changed
- `TESTED_VERSION` and the crate version move to 2.1.232; the full live
wirecheck claude tier (curated + cargo) passes against the installed
CLI.
## [2.1.223] - 2026-08-05
### Changed
- **`LoginOutcome`, `TokenSource`, and `Osc52Status` now derive
Serialize/Deserialize** — login outcomes are relay-shaped for web/launcher
surfaces that ship them over the wire (requested by agent-portal's
login-automation assessment). No wire or behavior change.
## [2.1.222] - 2026-08-04
Catches up to Claude CLI 2.1.222; snapshot baseline and `TESTED_VERSION`
move to 2.1.222, and the full live suite passes against the installed
binary. Also ships the complete login-tooling saga and session forking
(below) that accumulated unreleased.
### Added (2.1.222 drift)
- **`ModelRefusalFallbackMessage.scope`** — new open enum
`RefusalFallbackScope` (`session` | `local` + `Unknown(String)`):
`session` means the main thread fell back and the session model is
swapped (also the meaning when the field is absent, i.e. every CLI
before 2.1.222); `local` means a subagent / side-question / background
fork fell back and only that response used the fallback model. (#272)
### Changed
- **Rejected-code retry now follows the CLI's real state machine** —
recovered from the 2.1.220 binary: the error screen renders **no input
component**, and its only affordance (Enter) restarts the OAuth flow with
a **new PKCE challenge**. There is no same-challenge retry; a corrected
code pasted after a rejection lands nowhere, silently. Accordingly:
`submit_code` after a `CodeRejected` now returns `InvalidState` instead
of writing into the void, and the new
**`LoginFlow::retry_new_url(timeout)`** presses Enter, waits for
`waiting_for_login` to re-render, and returns the **new** authorize URL
to show the user (the old URL's code is dead). Live-verified end to end:
reject → refused re-paste → new URL with rotated challenge in ~1 s →
fresh input field accepts the next submission. (#270)
### Added
- **Bracketed-paste-mode telemetry**: channel lines now stamp
`paste-mode@submit=on|off|never-advertised` — the TUI's advertised
`?2004h/l` state at the moment the code was written, from the raw
stream. Directly tests the late-write hypothesis (a TUI that drops paste
mode while the user is off authorizing would receive a late frame as raw
ESC keypresses) in production, where the 19–22 s human submit latency
cannot be replicated by harnesses that write in milliseconds.
`SUBMIT_PATH` → `…+paste-probe/v6`. (#269)
### Fixed
- **Login flows were structurally blind to child death.** `LoginFlow` held
the PTY *slave* open for its whole life, so when the CLI exited, the
master never saw EOF and the reader blocked forever — production attempt
four's child died ~5 s after code submission and the flow spent the
remaining 85 s polling a corpse, reporting benign absence on every
channel. The slave is now dropped at spawn (the child owns its own fds),
making master-side EOF fire on exit. (#267)
### Added
- **Child death is a first-class outcome** (#267):
- New `Error::LoginChildExited { code, transcript }`, returned within
~1 s of death from `auth_url` and `submit_code_and_wait` (EOF-driven,
with a `try_wait` backstop for a dead parent whose PTY is held open by
an orphaned grandchild). Replaces the generic `Unknown`/`Protocol`
errors on the exited-without-outcome paths — downstream match arms
with a catch-all are unaffected.
- **Pre-submit liveness check**: a death *before* the paste is reported
as such ("BEFORE code submission — nothing was written"), timestamping
death against the write — the discriminator between "the frame killed
it" and "it was already gone".
- Channel lines now carry `child=alive|exited(code)`; `SUBMIT_PATH`
bumps to `…+exit-aware/v5`.
- An exit that races a credentials write still resolves to success.
Live-verified: kill-after-submit surfaces `LoginChildExited(code=143)`
in ~570 ms with the masked input echo intact in the tail; kill-before
is caught in microseconds; the rejection path is unchanged (~375 ms).
- **Write-path provenance**: `auth::SUBMIT_PATH` identifies the compiled
code-submission mechanism and is stamped into the `LoginTimeout` channel
line (`submit-path=bracketed-paste+lone-cr-150ms+term-forced/v3`) — which
deployed binary is running becomes readable from one log line. Necessary
because release builds inline the paste-frame bytes into immediates, so
byte-grepping a binary for `ESC[200~` proves nothing in either
direction. (#265)
### Changed
- **Login flows scrub session/credential env vars from the spawned CLI**
(`CLAUDECODE`, `CLAUDE_CODE_ENTRYPOINT`, `CLAUDE_CODE_CHILD_SESSION`,
`CLAUDE_CODE_SESSION_ID`, `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`,
`ANTHROPIC_AUTH_TOKEN`), aligning with the session spawn path's
`CLAUDECODE` scrubbing in `cli.rs` — a login child's purpose is minting
fresh credentials, so it must not inherit the host's. Measured on 2.1.220
that none of these break submission when present; this removes the axis,
not a reproduced failure. `SUBMIT_PATH` bumps to
`…+env-scrubbed/v4`. (#266)
- **Login flows now force `TERM=xterm-256color` on the spawned CLI.** The
crate is the terminal on the other side of the PTY (it parses OSC 8/52
and speaks bracketed paste), so it advertises a deterministic capability
surface instead of inheriting the host process's TERM (server processes
often have none). Measured on CLI 2.1.220, submission works under
`TERM=dumb` and TERM-unset too — this eliminates an environment axis
rather than fixing a reproduced failure. (#265)
### Fixed
- **Login code submission failed silently for every production-length
code.** The TUI classifies any single PTY write of **≥ 64 bytes** as a
paste and swallows a trailing CR into the paste payload instead of
treating it as Enter (measured on CLI 2.1.220: 62-char code + CR submits;
63-char + CR sits at the prompt forever). Real authorization codes are
~90+ chars, so no unframed single-chunk write could ever submit — this is
what produced the silent 30s/90s timeouts in production, undetected
because every test fixture happened to be under 64 bytes. `submit_code`
now writes the code wrapped in **bracketed-paste framing**
(`ESC[200~…ESC[201~` — the paste path the CLI explicitly enables via
`ESC[?2004h`), then sends CR as its own write 150 ms later. Live-verified
across a 36–120 byte matrix including both previously-silent boundary
lengths; unit fixtures moved to production lengths (64/92/108/120) and a
live integration test now submits 92- and 108-char codes, both of which
any sub-64-byte fixture is structurally incapable of covering. (#263)
### Added
- **Three-source success detection for login flows**, driven by the prod
finding that every *rejection* is detectable on screen but *success* may
not be (the TUI masks secret material). `submit_code_and_wait` and
`finish` now recover the minted token from: (1) visible screen text,
(2) **OSC 52 clipboard-copy escapes** decoded from raw PTY bytes — exact,
immune to wrapping/masking; confirmed emitted by CLI 2.1.220 — and
(3) **credentials-store watching**: `.credentials.json` (under
`CLAUDE_CONFIG_DIR` or `~/.claude`) created/updated after submission is
authoritative success even with nothing observable on the PTY.
`LoginOutcome` gains `token_source: Option<TokenSource>` and
`credentials_updated: bool`. (#259)
- **Self-diagnosing login timeouts**: new `Error::LoginTimeout { transcript }`
replaces `Error::Timeout` in `submit_code_and_wait`, carrying a per-channel
status line (`screen` / `osc52` / `credentials`) plus the post-submission
screen content — a silent failure now names the blind channel. (#259)
### Changed
- **Success-path OSC 52 telemetry**: `LoginOutcome` gains
`osc52: Osc52Status` (`Absent` / `Unterminated` / `Undecodable` /
`PresentNoToken` / `TokenRecovered`) and `copy_nudge_sent: bool`, and the
`LoginTimeout` channel line gains `copy-nudge=sent|not-sent` — so a
`token: None` success distinguishes "screen offers no copy affordance"
from "affordance fired with a non-token payload", crate-side, where the
PTY bytes are actually observable. The copy nudge is also recorded so any
unexpected TUI consequence is attributable. (#261)
- **Copy-affordance nudge on confirmed success**: when the credentials store
updates but no token is yet observable, `submit_code_and_wait` presses `c`
(the TUI's "c to copy" affordance) once, giving the OSC 52 channel a chance
to deliver the exact token bytes before the grace window closes — reducing
the frequency of `token: None` successes that downstream must treat as
re-login-after-redeploy mode. Best-effort: a stray keypress on screens
without the affordance, in a flow that has already succeeded. (#260)
- **Login rejection detection widened**: the collapsed `Press Enter to retry`
prompt now anchors `CodeRejected` as a fallback when the error wording
doesn't contain `OAuth error` — live capture shows the renderer mangles
wording (`Requstfailed withstatus code 400`), while the retry prompt is the
rejection loop's structural constant. (#259)
- **`submit_code` hardening**: the code and its terminating CR are written as
a single chunk, and an empty-after-trim code is refused with a clear error
instead of pressing Enter on an empty field (a silent hang). (#259)
- **Login support tooling** behind a new `auth` feature. The CLI's login
flows are interactive Ink TUIs (they hang forever on a pipe), so
`auth::LoginFlow` drives them under a pseudo-terminal (`portable-pty`) and
exposes the flow's human shape as an API: `start(LoginMode)` →
`auth_url()` (lifted intact from the CLI's OSC 8 hyperlink) → user visits
the URL and brings back a code → `submit_code()` → `finish()`, which
returns the minted `sk-ant-oat01-…` token for `LoginMode::SetupToken`.
Dropping the flow cancels it. `auth::auth_status()` types
`claude auth status --json`. Live-tested: a real `setup-token` flow yields
the PKCE authorize URL; `examples/login.rs` walks the full interactive
loop. (#257)
- **`LoginFlow::submit_code_and_wait`**: outcome-driven completion that
watches the CLI's *output* instead of its exit. Production use showed two
hangs `finish()` cannot see: the CLI never exits on a rejected code (it
prints `OAuth error … Press Enter to retry` and waits), and its Ink TUI
positions words with cursor-column escapes instead of spaces, so
ANSI-stripped text runs together (`Pastecodehereifprompted>`) and
phrase-matching never fires. The new method returns as soon as a minted
token appears, fails fast with the new `Error::CodeRejected { message }`
when an OAuth error is printed (scanning only output after the current
submission, so a retry can't trip on a prior attempt's error), and leaves
the flow alive on rejection so a corrected code reaches the same PKCE
session. Presence checks collapse whitespace; token extraction stays
newline-bounded (collapsing would glue trailing prose onto the token) with
a loud display-wrap detector instead of silent truncation. PTY width is
raised to 1000 columns so wrapping is impossible at the source.
- **Session forking**: `ClaudeCliBuilder::fork_from(source_session_id)`
assembles the full `--resume <src> --fork-session --session-id <new>`
combination (generating a fresh UUID unless one is chained via
`session_id`), and `fork_session(bool)` exposes the raw flag — which the
builder previously could not emit at all. Covered by a live integration
test proving the fork carries the source's history under the new session
id. (#227)
### Changed
- **Breaking**: `UsageInfo.iterations` is now `Vec<UsageIteration>` (was
`Vec<serde_json::Value>`), with typed `input_tokens`, `output_tokens`,
optional per-iteration cache fields (`cache_read_input_tokens`,
`cache_creation_input_tokens`, `cache_creation`), and the wire `type`
exposed as `kind` (`"turn"` and `"message"` observed). (#250)
- **`UsageInfo` docs now state the counters are accumulated roll-ups** across
the turn's API iterations — correct for cost, wrong for context occupancy
(`cache_read_input_tokens` can exceed the context window several times over
on tool-heavy turns). Use the last `iterations` entry for context
estimates, as the CLI does. (#250)
## [2.1.220] - 2026-07-31
Version jumps 2.1.166 → 2.1.220 to re-align with the tested Claude CLI
version, per the crate's versioning convention.
### Added
- `RawAsyncClient` for newline-framed, undecoded access to the Claude
stream-json protocol.
- `ClaudeInput::user_message_without_session` for messages associated with the
current CLI process session.
- `ClaudeCliBuilder::working_directory` for setting the working directory of
spawned Claude CLI processes.
### Changed
- On Windows, Claude CLI launches and version checks now use
`CREATE_NO_WINDOW` to avoid opening console windows.
### Contributors
- Thanks to @ozitrance — a first-time contributor to this crate — for the
entire feature set in this release: `RawAsyncClient`,
`user_message_without_session`, `working_directory`, and the Windows
`CREATE_NO_WINDOW` support (#243, #244, #245).
## [2.1.166] - 2026-07-27
### Changed
- **Tested Claude CLI version** bumped to 2.1.220 — no wire drift vs the
2.1.219 snapshot, and the full integration suite passes against the
installed binary.
- Shared dependencies and lint policy moved to the workspace root: `serde`,
`serde_json`, `thiserror`, `tokio`, `log`, `which`, and dev `env_logger` are
now `{ workspace = true }`, and the crate opts into `[workspace.lints]`
(`unsafe_code = "deny"`). Aligns previously drifted versions: `tokio`
1.47.1 → 1.49.0, `log` 0.4.27 → 0.4.29, `env_logger` 0.11.8 → 0.11.9.
## [2.1.165] - 2026-07-25
### Added
- **`SystemMessage::as_code_change_published()` / `as_vcs_state_changed()`**
(plus `is_*` checks) — dedicated typed accessors following the
`as_init()` pattern, so consumers reach `CodeChangePublishedMessage` /
`VcsStateChangedMessage` without matching on `KnownSystemEvent` or poking
raw JSON (#231). Note: the `vcs_state_changed` SDK frame is flat
(`kind` + `cwd`) — the CLI's internal git/gh watcher event carries richer
`commit`/`push`/`branch`/`pr` sections, but the emitter flattens each to
one-or-more `kind` frames before they cross the wire.
## [2.1.164] - 2026-07-24
Catches up to CLI 2.1.219 — the Opus 5 release. Snapshot baseline and
`TESTED_VERSION` move to 2.1.219; the full integration suite passes against
the installed binary.
### Added
- **`ClaudeModel::Opus5`** — pinned variant for `claude-opus-5` (display name
"Opus 5", knowledge cutoff May 2026). The floating `opus` alias resolves to
`claude-opus-5` first-party as of CLI 2.1.219 (noted on the `Opus` variant
doc). Model table refreshed from the 2.1.219 binary; the accepted floating
aliases are unchanged.
- **`FastModeDisabledReason`** — open enum (`free`, `preference`,
`extra_usage_disabled`, `network_error`, `unknown`, `not_first_party`,
`disabled_by_env`, `model_not_allowed`, `sdk_opt_in_required`, `pending`,
plus `Unknown(String)`) carried as the new optional
`fast_mode_disabled_reason` on `ResultMessage` and `InitMessage`: why fast
mode can't serve right now, complementing the existing `fast_mode_state`.
- **`InitMessage.mcp_server_errors`** — new `McpServerError` struct (`name`,
`type`, `message`) recording `--mcp-config` entries that failed validation
and were skipped.
- **`PluginInfo.version`** — installed plugin version on the init plugin
list (caught by the live wire-fidelity audit, not the drift fingerprint —
it is a nested field).
## [2.1.163] - 2026-07-22
Models the CLI 2.1.205 → 2.1.218 stream-json drift surfaced by the fixed
schema extractor (#223); the committed snapshot baseline and `TESTED_VERSION`
move to 2.1.218.
### Added
- **`ClaudeOutput::CommandLifecycle`** — typed variant for the new
`command_lifecycle` wire type (fate of a queued command: queued → started →
completed/cancelled/discarded), with `CommandLifecycleMessage` and the open
`CommandLifecycleState` enum (`Unknown(String)` fallback).
- **`system/code_change_published`** — `CodeChangePublishedMessage`
(`provider`, `url`, `repo`, `identifier`): the session is now associated
with a published pull/merge request.
- **`system/vcs_state_changed`** — `VcsStateChangedMessage` with the open
`VcsMutationKind` enum (`commit`/`push`/`merge`/`rebase` +
`Unknown(String)`): a harness-observed command mutated repository state.
Both new subtypes are wired through `SystemSubtype`, `KnownSystemEvent`,
and the `typed_value` wrapping audit.
- **`AssistantMessage.aborted`** — true when the message was truncated by an
interrupt/abort before the stream completed — and
**`.resumed_from_incomplete_thinking`** — true when the turn continued a
truncated thinking block (max-output-tokens recovery).
- **`ResultMessage.request_sent_wall_ms`** (fractional wall-clock ms) and
**`.user_message_uuid`** (wire uuid of the user message the result answers).
- **`ToolProgressMessage.heartbeat`**, **`.subagent_type`**, and
**`.subagent_retry`** (new `SubagentRetry` struct: attempt, max_retries,
retry_delay_ms, error_status, error_category).
- **`UserMessage.tool_result_meta`** — new `ToolResultMeta` struct carrying
the harness-stamped `non_execution_kind` for error tool results and any
human-typed `user_feedback` deny comment.
## [2.1.162] - 2026-07-22
### Fixed
- **`ClaudeInput::interrupt()` now actually interrupts** (#218). It previously
serialized to a bare `{"subtype":"interrupt"}`, which the CLI silently
ignores (verified against 2.1.205 and 2.1.211) — the in-flight turn ran to
completion. It now emits the required `control_request` envelope
`{"type":"control_request","request_id":...,"request":{"subtype":"interrupt"}}`,
which the CLI acknowledges with a `control_response` and cancels the turn
immediately (verified live: ack in ~10ms, turn ends with
`result subtype=error_during_execution`).
### Changed
- **Breaking**: `ClaudeInput::interrupt(request_id)` now takes the unique
request id for the control envelope. `AsyncClient::interrupt()` /
`SyncClient::interrupt()` generate an `interrupt-<uuid>` id and now return
`Result<String>` (the id) so callers can correlate the CLI's
`control_response` ack.
- **Breaking**: removed `SDKControlInterruptRequest` — its bare wire shape is
a no-op against the CLI. Use `ClaudeInput::interrupt(request_id)` or the new
`ControlRequestMessage::interrupt(request_id)` constructor instead.
### Added
- **`ControlRequestPayload::Interrupt`** variant and
**`ControlRequestMessage::interrupt(request_id)`** constructor (mirroring
`::initialize`), so the correctly-enveloped interrupt can be built typed.
## [2.1.161] - 2026-07-11
### Added
- **`ClaudeModel`** — convenience enum for every model selector the Claude CLI
accepts, keyed by human-friendly names. Floating aliases (`Sonnet`, `Opus`,
`Haiku`, `Fable`, `Best`, `OpusPlan`, and the `[1m]` context variants) plus
the pinned registry models (`Fable5`, `Mythos5`, `Opus48` … `Haiku35`),
extracted from the CLI 2.1.205 binary's model registry. `cli_arg()` returns
the exact `--model` string, `display_name()` the human-readable name, and
`Custom(String)` passes unknown models through verbatim.
`ClaudeCliBuilder::model(ClaudeModel::Sonnet5)` works directly via
`Into<String>`. Re-exported at the crate root.
## [2.1.160] - 2026-07-10
### Added
- **Claude Code CLI 2.1.205 output coverage.** Added typed coverage for new
top-level SDK frames (`stream_event`, `tool_progress`, `auth_status`,
`tool_use_summary`, `prompt_suggestion`, `conversation_reset`), 19 newer
`system` subtypes, richer result/init/status/compact/user/assistant wrapper
fields, and `get_usage` control-response quota payloads.
- **Rate limit schema updated to CLI 2.1.205.** `RateLimitInfo` now carries the
full `rate_limit_event` wire schema:
- New fields: `overage_resets_at`, `overage_in_use`, `surpassed_threshold`,
`overage_period_monthly` / `overage_period_channel` (new
`OveragePeriodUtilization` struct), `error_code` (new `RateLimitErrorCode`
enum), `can_user_purchase_credits`, and
`has_chargeable_saved_payment_method`.
- `RateLimitWindow` gains `SevenDayOpus`, `SevenDaySonnet`,
`SevenDayOverageIncluded`, and `Overage` variants.
- `OverageStatus` gains `AllowedWarning`.
- `OverageDisabledReason` expands from 2 to 12 typed variants matching the
CLI enum.
- Re-exported `OveragePeriodUtilization` and `RateLimitErrorCode` at the
crate root.
- **`SubagentUsageRollup`** — session-level accumulator for the subagent
token rollup the CLI renders as `<subagent_tokens>` / `<agent_count>` in its
terminal `<usage>` block (resolves #169). Feed every `ClaudeOutput` through
`observe()`; it gates on genuine `Task` results (`agentId`/`totalTokens`
present), dedupes replayed frames by `agentId`, and totals tokens, agent
count, tool uses, and duration. `UsageInfo` now documents explicitly that
the `result` frame's usage covers the main agent only — the rollup is not
carried on the wire. Re-exported at the crate root.
### Changed (breaking)
- `ClaudeOutput` gained additional variants, so exhaustive matches must handle
the new top-level frame types.
- `ResultSubtype`, `TaskStatus`, and `TaskType` are now forward-compatible open
enums with `Unknown(String)` fallbacks.
- `TaskStartedMessage.task_type`, `TaskStartedMessage.tool_use_id`, and
`TaskProgressMessage.last_tool_name` are now optional to match CLI 2.1.205
wire frames.
- `RateLimitInfo::is_using_overage` is now `Option<bool>` — the field is
optional in the CLI wire schema and events omitting it previously failed to
deserialize.
- `RateLimitWindow::Hourly` removed; the window no longer exists in the CLI
schema. An `"hourly"` value now parses as `RateLimitWindow::Unknown`.
### Changed
- **Tested Claude CLI version** bumped to 2.1.205 — the full integration suite
passes against the installed binary. One suite fix was required: CLI 2.1.205
only enables `AskUserQuestion` in headless mode when a permission-prompt
tool is configured (`--permission-prompt-tool`), so the round-trip test now
spawns with one, matching the existing converge test.
## [2.1.159] - 2026-06-27
### Added
- **Typed subagent token accounting.** `Task`-tool result messages now expose
the subagent's token / timing / tool-use rollup through typed fields instead
of raw-JSON poking (resolves #168, #169):
- New `SubagentResult` struct modeling the `Task` `tool_use_result` —
`status`, `prompt`, `agent_id`, `agent_type`, `content`, `resolved_model`,
`total_duration_ms`, `total_tokens`, `total_tool_use_count`, the nested
per-model `usage` (`UsageInfo`), and an optional `SubagentToolStats`.
- `UserMessage::subagent_result()` accessor parses the result leniently,
returning `None` only when `tool_use_result` is absent.
- `total_tokens` is the per-run `subagent_tokens` line item; summing it across
a session's `Task` results yields the subagent token rollup the CLI renders
in its terminal `<usage>` block. (The `stream-json` `result` frame's own
`usage` does not carry the subagent rollup — the `Task` result is the source
of truth.)
- Re-exported `UsageInfo`, `ServerToolUse`, `SubagentResult`, and
`SubagentToolStats` at the crate root.
### Changed
- Enriched the crate description, `keywords`, and `categories` for crates.io
discoverability (agent / Claude Code / Anthropic / async terms).
## [2.1.158] - 2026-06-25
### Added
- **Subagent message coverage.** Typed every field a `Task`-tool /
`local_agent` subagent session emits, verified against real captures:
- New `system` subtypes `task_updated` (with `TaskUpdatedMessage` /
`TaskPatch`) and `thinking_tokens` (with `ThinkingTokensMessage`), plus
`SystemMessage::as_task_updated` / `as_thinking_tokens` accessors.
- `TaskStartedMessage` and `TaskProgressMessage` gain `subagent_type`;
`TaskStartedMessage` also gains `prompt`.
- `AssistantMessage` gains `request_id`, `subagent_type`, `task_description`;
`AssistantMessageContent` gains `message_type` (the API `type` field);
`UserMessage` gains `subagent_type` and `task_description`.
- `ToolUseBlock` gains a typed `caller` (`ToolCaller`).
- `InitMessage` gains `analytics_disabled` and `product_feedback_disabled`.
- `ResultMessage` gains `ttft_ms`, `ttft_stream_ms`, and `time_to_request_ms`.
- **Wire-fidelity audit** — `audit_frame` / `assert_fully_wrapped` / `FrameAudit`
(exported from the crate root and `claude_codes::io`) check that a raw frame
deserializes, round-trips losslessly, and — for `system` frames — resolves to
a modeled subtype whose typed view captures every field.
- **`AsyncClient::receive_raw`** — read the next frame as a raw
`serde_json::Value` before typed parsing, for auditing wire fidelity.
- New `test_cases/subagent_sessions/` captures and a `subagent_wrapping_tests`
suite that audits every frame (fixtures always; a live subagent run under
`integration-tests`).
### Changed
- **Tested Claude CLI version** bumped to 2.1.178 — the full integration suite,
including the new live subagent run, passes against the installed binary.
## [2.1.157] - 2026-06-11
### Changed
- **Tested Claude CLI version** bumped from 2.1.150 to 2.1.170. The full
integration suite passes against CLI 2.1.172; the pin sits two patch
versions behind. The newer-than-tested CLI warning now triggers only
above 2.1.170.
## [2.1.156] - 2026-06-10
### Added
- **`ContentBlock::Fallback`** — typed variant for model-fallback content
blocks (`{"type": "fallback", "from": {"model": ...}, "to": {"model": ...}}`),
emitted when the CLI switches the response to a fallback model mid-turn.
Previously these deserialized as `ContentBlock::Unknown`. The wire shape was
verified against the claude CLI 2.1.172 binary and real session transcripts;
it carries no fields beyond the from/to models (no reason field exists).
New types `FallbackBlock` and `FallbackModel` are exported from the crate
root and `claude_codes::io`. (#160)
## [2.1.155] - 2026-06-08
### Changed (breaking)
- **`TextBlock::citations`** is now `Vec<Citation>` instead of
`Vec<serde_json::Value>`. Consumers read typed fields (`citation_type`,
`url`, `title`, `cited_text`, `document_index`, `document_title`) instead of
JSON-poking each entry. Unmodeled location fields (start/end indices,
`encrypted_index`, …) are preserved verbatim in `Citation::extra`, so the
type round-trips losslessly across all citation shapes.
### Added
- **`Citation`** — typed content-block citation struct (re-exported from
`claude_codes::io`).
## [2.1.154] - 2026-06-08
### Added
- **`ToolInput::from_named_input(name, input)`** — parses a tool-use `input`
using the authoritative tool *name* from the `ToolUse` block instead of
guessing the variant from field shape. Resolves the inherent ambiguity of
the untagged `Deserialize` impl for structurally-identical inputs — most
notably `WebSearch` vs `ToolSearch`, both bare `{ "query": String }`, where
the untagged impl always picked `ToolSearch` and dropped the `WebSearch`
query. Falls back to `Unknown` on shape mismatch and defers to the untagged
impl for unmodeled (e.g. MCP) tool names.
## [2.1.153] - 2026-06-08
### Changed (breaking)
- **`ResultMessage::model_usage`** is now
`Option<BTreeMap<String, ModelUsageEntry>>` instead of
`Option<serde_json::Value>`. Consumers read typed per-model usage
(`input_tokens`, `output_tokens`, `cache_read_input_tokens`,
`cache_creation_input_tokens`, `cost_usd`, `web_search_requests`)
keyed by model name, instead of poking the `Value`. Unmodeled wire
keys are preserved in `ModelUsageEntry::extra`.
### Added
- **`ModelUsageEntry`** — typed per-model usage/cost record (re-exported
from `claude_codes::io`).
## [2.1.152] - 2026-06-08
### Added
- **`CompactBoundaryMessage`** gains optional per-compaction stats:
`summary: Option<String>` (also accepted under the `content` / `text`
wire keys), `leaf_message_count: Option<u32>` (also accepted under
`message_count`), and `duration_ms: Option<u64>`. Consumers can read
these from the typed struct instead of poking `SystemMessage::data`.
## [2.1.151] - 2026-06-08
### Added
- **Round-trip regression test** — `test_task_messages_roundtrip_through_value`
pushes `task_started` / `task_progress` / `task_notification` system
messages through `from_str` → `to_value` → `from_value` → typed accessor,
covering the proxy/relay path where a dropped or renamed field would
otherwise surface only as a `None` downstream.
## [2.1.150] - 2026-05-29
### Changed
- Pinned the tested Claude CLI version forward to `2.1.150`. The full integration suite passes against the newer CLI with no protocol changes required.
## [2.1.142] - 2026-05-27
### Changed
- **`ToolPermissionRequest::answer_questions`** — Signature changed from `HashMap<String, String>` (keyed by caller-chosen string) to `&HashMap<usize, String>` (keyed by question index). The previous signature let callers — and the rustdoc example — accidentally key answers by `header` instead of the full question text, which makes the CLI render `"Your questions have been answered: ."` with an empty body and leaves Claude unable to see the choice. The new signature looks each index up in the request's `questions` array and uses `q.question` as the wire key, so the wrong-key footgun is impossible.
- **`answer_questions` now returns `AskUserQuestionResponseError`** instead of a bare `serde_json::Error`, distinguishing `WrongTool` / `ParseInput` / `QuestionIndexOutOfRange { index, total }` failure modes. Also validates that `tool_name == "AskUserQuestion"` before parsing the input.
### Added
- **`AskUserQuestionResponseError`** — Re-exported error type for the helper.
### Migration
If you wired up to the 2.1.141 `answer_questions(HashMap<String, String>)` form, swap to passing a `HashMap<usize, String>` keyed by question index (matches the natural UI shape of "the user picked option X for question 0"). No other callsite changes needed.
## [2.1.141] - 2026-05-17
### Added
- **`ToolPermissionRequest::answer_questions(answers, request_id)`** —
typed helper for replying to an `AskUserQuestion` permission request.
Parses `self.input` as `AskUserQuestionInput`, attaches the supplied
`HashMap<String, String>` answers, and returns a `ControlResponse`
whose `updatedInput` carries both the original `questions` array AND
the new `answers`. Eliminates the `"undefined is not an object
(evaluating 'q.map')"` failure mode in downstream viewers that read
`tool_use_result.questions` and call `questions.map(...)` — that
crash happens when the response payload is built by hand and the
original `questions` are dropped. Existing `allow` / `allow_with`
continue to work for non-AskUserQuestion approvals.
### Verified
- All 26 integration tests pass against the live Claude CLI, including
`test_ask_user_question_answered_and_converges` which drives Claude
through the new helper and confirms the agent's follow-up reply
references the chosen answer (`"You picked Blue."`).
## [2.1.140] - 2026-05-13
### Added
- **`UserMessage.tool_use_result`** — `Option<serde_json::Value>` capturing the top-level structured tool result the CLI emits alongside `tool_result` content blocks (e.g. `{ questions, answers }` for `AskUserQuestion`, `{ stdout, stderr, exit_code }` for `Bash`). Previously dropped during deserialization, which broke proxies relaying user messages to viewers that read the field.
- **`UserMessage.timestamp`** — `Option<String>` capturing the CLI's ISO-8601 timestamp on echoed tool results.
- **`UserMessage::tool_use_result_as<T>()`** — Typed accessor for parsing `tool_use_result` into a caller-specified type when the tool is known (e.g. `tool_use_result_as::<AskUserQuestionInput>()`).
- **Integration regression test** — `test_ask_user_question_answered_and_converges` drives the full AskUserQuestion round-trip through the permission control protocol and asserts `tool_use_result` survives end-to-end without information loss.
### Changed
- Updated `TESTED_VERSION` to `2.1.140`
## [2.1.117] - 2026-04-15
### Added
- **`ContentBlock::Unknown(Value)`** — Fallback variant for forward compatibility with new content block types from the CLI. Prevents deserialization failures when encountering unknown block types (#104)
- **Typed content block variants** — `ServerToolUse`, `WebSearchToolResult`, `CodeExecutionToolResult`, `McpToolUse`, `McpToolResult`, `ContainerUpload` for the new server-side and MCP content blocks emitted by CLI 2.1.117 (#105, #106, #107)
- **`TextBlock.citations`** — `Vec<Value>` field for web search citations on text blocks (#108)
- **`ResultMessage` fields** — `api_error_status`, `stop_reason`, `terminal_reason`, `fast_mode_state`, `model_usage` (#109)
- **`UsageInfo` fields** — `cache_creation`, `inference_geo`, `iterations`, `speed` (#110)
- **`ServerToolUse.web_fetch_requests`** — Tracks web fetch request count (#110)
- **`InitMessage` fields** — `uuid`, `memory_paths`, `fast_mode_state` (#111)
- **`PluginInfo.source`** — Plugin registry source identifier (#111)
- **`AssistantMessageContent` fields** — `stop_details`, `context_management` (#112)
- **`AssistantUsage.inference_geo`** — Inference geography field (#112)
- **`UserMessage` fields** — `parent_tool_use_id`, `uuid` (#113)
- **New `ToolInput` types** — `MultiEdit`, `LS`, `NotebookRead`, `ScheduleWakeup`, `ToolSearch` with typed structs (#114)
- **`ClaudeCliBuilder.max_thinking_tokens()`** — Builder method and `CliFlag::MaxThinkingTokens` for extended thinking control (#115)
- **`ContentBlock.block_type()`** and **`ContentBlock.is_unknown()`** — Helper methods for content block introspection
### Changed
- Updated `TESTED_VERSION` to `2.1.117`
- `UsageInfo` fields now use `#[serde(default)]` for robustness
- `ServerToolUse` now derives `Default`
## [2.1.53] - 2026-03-17
### Added
- **Binary path resolution via `which`** — `ClaudeCliBuilder::spawn()`, `spawn_sync()`, and `build_command()` now resolve non-absolute binary paths using `which` at spawn time, producing a clear `BinaryNotFound` error instead of an opaque OS "file not found" (#102)
- **`Error::BinaryNotFound`** — New error variant for when the CLI binary isn't found on PATH
### Changed
- **`spawn_sync()` return type** — Now returns `crate::error::Result<Child>` instead of `std::io::Result<Child>` for consistent error handling
- **`build_command()` return type** — Now returns `Result<Command>` instead of `Command` to surface binary resolution errors
## [2.1.52] - 2026-03-12
### Added
- **`SDKControlInterruptRequest`** — Typed struct for the `{ "subtype": "interrupt" }` SDK control message, used to gracefully stop a running Claude session without killing the process
- **`ClaudeInput::interrupt()`** — Constructor for creating interrupt messages
- **`AsyncClient::interrupt()`** and **`SyncClient::interrupt()`** — Convenience methods to send an interrupt to the CLI subprocess
## [2.1.51] - 2026-02-27
### Changed
- **`Error::Deserialization`** now wraps `ParseError` instead of `String`, giving callers structured access to the raw input line, parsed JSON value, and error message
- **`ParseError`** gains a `raw_line: String` field containing the exact stdout line (works even when the input isn't valid JSON)
## [2.1.50] - 2026-02-27
### Fixed
- `RateLimitInfo.resets_at` and `RateLimitInfo.rate_limit_type` are now `Option` — Claude CLI can omit these fields in `rate_limit_event` messages with `status: "allowed"`
## [2.1.49] - 2026-02-25
### Changed
- **`SystemSubtype`** — enum replacing `String` for system message subtypes (`init`, `api_error`, etc.)
- **`ApiErrorType`** — enum replacing `String` for API error types (`authentication_error`, `overloaded_error`, etc.)
- **`RateLimitStatus`** — enum replacing `String` for rate limit statuses (`rate_limited`, `rate_limit_cleared`)
- **`RateLimitWindow`** — enum replacing `String` for rate limit windows (`minutely`, `daily`, etc.)
- **`PermissionType`** — enum replacing `String` for permission types (`addRules`, `setMode`)
- **`PermissionDestination`** — enum replacing `String` for permission destinations (`session`, `project`)
- **`PermissionBehavior`** — enum replacing `String` for permission behaviors (`allow`, `deny`)
- **`PermissionModeName`** — enum replacing `String` for permission mode names (`acceptEdits`, `bypassPermissions`)
- **`MessageRole`** — enum replacing `String` for message roles (`user`, `assistant`)
- **`CompactionTrigger`** — enum replacing `String` for compaction triggers (`auto`, `manual`)
- **`StopReason`** — enum replacing `String` for stop reasons (`end_turn`, `max_tokens`, `tool_use`)
- **`TodoStatus`** — enum replacing `String` for todo statuses (`pending`, `in_progress`, `completed`)
- **`OverageStatus`** — enum replacing `String` for overage billing status (`allowed`, `rejected`)
- **`OverageDisabledReason`** — enum replacing `String` for overage disabled reason (`org_level_disabled`, `out_of_credits`)
- **`ImageSourceType`** — enum replacing `String` for image encoding type (`base64`)
- **`MediaType`** — enum replacing `String` for image MIME types (`image/jpeg`, `image/png`, `image/gif`, `image/webp`)
- **`GrepOutputMode`** — enum replacing `String` for grep output mode (`content`, `files_with_matches`, `count`)
- **`SubagentType`** — enum replacing `String` for task subagent types (`Bash`, `Explore`, `Plan`, `general-purpose`)
- **`NotebookCellType`** — enum replacing `String` for notebook cell types (`code`, `markdown`)
- **`NotebookEditMode`** — enum replacing `String` for notebook edit modes (`replace`, `insert`, `delete`)
- **`ApiKeySource`** — enum replacing `String` for API key source in init messages (`none`)
- **`OutputStyle`** — enum replacing `String` for output style in init messages (`default`)
- **`InitPermissionMode`** — enum replacing `String` for permission mode in init messages (`default`)
- **`StatusMessageStatus`** — enum replacing `String` for status message status (`compacting`)
All enums include an `Unknown(String)` fallback variant for forward compatibility, plus `as_str()`, `Display`, and `From<&str>` implementations.
### Breaking
- Struct fields that were `String` are now typed enums — callers using `.as_deref()`, string comparisons, or `.to_string()` on these fields need to update to use the enum variants or `.as_str()` method
## [2.1.47] - 2026-02-24
### Added
- **`TaskStartedMessage`** — Typed struct for `task_started` system messages emitted when a background task (agent or bash) begins
- **`TaskProgressMessage`** — Typed struct for `task_progress` system messages with tool name, description, and cumulative usage stats
- **`TaskNotificationMessage`** — Typed struct for `task_notification` system messages emitted when a background task completes or fails
- **`TaskUsage`** — Cumulative usage statistics (`duration_ms`, `tool_uses`, `total_tokens`)
- **`TaskType`** enum — `LocalAgent` or `LocalBash`
- **`TaskStatus`** enum — `Completed` or `Failed`
- **`SystemMessage` helpers** — `is_task_started()`, `is_task_progress()`, `is_task_notification()` and corresponding `as_task_*()` methods
## [2.1.46] - 2026-02-20
### Fixed
- **`RateLimitInfo.overage_status`** now `Option<String>` — `allowed_warning` events omit this field, previously causing deserialization failures
### Added
- **`RateLimitInfo.utilization`** — `Option<f64>` capturing rate limit usage (0.0–1.0)
## [2.1.45] - 2026-02-18
### Added
- **Expanded `InitMessage` fields** - Added typed fields for `slash_commands`, `agents`, `plugins`, `skills`, `claude_code_version`, `api_key_source`, `output_style`, and `permission_mode`
- **`PluginInfo` struct** - Typed representation of plugin entries with `name` and `path` fields
- **`allow_recursion()` on `ClaudeCliBuilder`** - Enables spawning Claude CLI from within a Claude Code session by unsetting `CLAUDECODE` env var
- **`/clear` integration test** - Verifies session ID resets after `/clear` command
### Changed
- Updated `TESTED_VERSION` to `2.1.47`
- All integration tests now use `allow_recursion()` for reliable execution inside Claude Code sessions
## [2.1.20] - 2026-02-17
### Added
- **`RateLimitEvent` and `RateLimitInfo`** - Support for `rate_limit_event` messages from Claude CLI
- `ClaudeOutput::RateLimitEvent` variant with `is_rate_limit_event()` and `as_rate_limit_event()` helpers
## [2.1.19] - 2026-02-17
### Added
- **`CliFlag` enum** - Comprehensive enum covering all 41 Claude CLI flags for building launcher UIs and advanced configuration
- **`InputFormat` and `OutputFormat` enums** - Typed representations of `--input-format` and `--output-format` options
- **`PermissionMode::Delegate` and `PermissionMode::DontAsk`** - Added missing permission mode variants
- `CliFlag::as_flag()` - Returns the CLI flag string (e.g., `"--add-dir"`)
- `CliFlag::to_args()` - Converts a flag + value into CLI argument strings
- `CliFlag::all_flags()` - Returns all flag names with descriptions for enumeration
## [2.1.18] - 2026-01-26
### Changed
- Increase stdout buffer from 8KB to 10MB to handle large JSON messages
## [2.1.17] - 2026-01-25
### Added
- **Permission struct for "remember this decision" support** - New typed API for building permission responses that support Claude Code's "remember this decision" functionality.
When responding to tool permission requests, you can now grant permissions so similar actions won't require approval in the future:
```rust
use claude_codes::{ToolPermissionRequest, Permission};
fn handle_permission(req: &ToolPermissionRequest, request_id: &str) -> ControlResponse {
// Allow and remember this specific command for the session
req.allow_and_remember(
vec![Permission::allow_tool("Bash", "npm test")],
request_id,
)
}
```
Or accept Claude's suggested permission:
```rust
// Use the first permission suggestion if available
let response = req.allow_and_remember_suggestion(request_id)
.unwrap_or_else(|| req.allow(request_id));
```
Available `Permission` constructors:
- `Permission::allow_tool(tool_name, rule_content)` - Allow a specific tool with a pattern (session-scoped)
- `Permission::allow_tool_with_destination(tool_name, rule_content, destination)` - Allow with custom scope ("session" or "project")
- `Permission::set_mode(mode, destination)` - Set a permission mode like "acceptEdits"
- `Permission::from_suggestion(suggestion)` - Convert a `PermissionSuggestion` to a `Permission`
**Migration from `allow_with_permissions`:**
Before (manual JSON conversion):
```rust
// Old approach - manually convert to JSON
let perms_json: Vec<serde_json::Value> = suggestions
.iter()
.filter_map(|p| serde_json::to_value(p).ok())
.collect();
ControlResponse::from_result(
&request_id,
PermissionResult::allow_with_permissions(input, perms_json)
)
```
After (typed API):
```rust
// New approach - use typed Permission API
let permissions: Vec<Permission> = suggestions
.iter()
.map(Permission::from_suggestion)
.collect();
req.allow_and_remember(permissions, request_id)
```
- **`decision_reason` and `tool_use_id` fields on `ToolPermissionRequest`** - These fields are now exposed for consumers that need them when building custom permission handling logic. The `tool_use_id` is particularly useful for correlating permission requests with tool uses in the message stream.
- **`ClaudeOutput::Error` variant for Anthropic API errors** - New variant to capture API errors (500, 529 overloaded, rate limits, etc.) that were previously unparsed.
```rust
use claude_codes::ClaudeOutput;
match output {
ClaudeOutput::Error(err) => {
if err.is_overloaded() {
println!("API overloaded, retrying...");
} else if err.is_rate_limited() {
println!("Rate limited: {}", err.error.message);
} else {
println!("API error: {}", err.error.message);
}
}
// ... handle other variants
}
```
Helper methods on `AnthropicError`:
- `is_overloaded()` - HTTP 529 overloaded error
- `is_server_error()` - HTTP 500 server error
- `is_rate_limited()` - HTTP 429 rate limit error
- `is_authentication_error()` - HTTP 401 auth error
- `is_invalid_request()` - HTTP 400 invalid request
Helper methods on `ClaudeOutput`:
- `is_api_error()` - Check if this is an error variant
- `as_anthropic_error()` - Get the error if this is one
### Changed
- `allow_with_permissions` method documentation clarified to note it takes raw `Vec<Value>`. For type safety, prefer the new `allow_and_remember` method.
## [2.1.16] - 2026-01-22
### Fixed
- Fixed `PermissionSuggestion` struct to correctly handle both `setMode` and `addRules` suggestion types from Claude CLI.
## [2.1.15] - 2026-01-21
### Added
- Re-export `ContentBlock`, `ToolUseBlock`, and other io types at crate root
- Typed `UsageInfo` on `AssistantMessage` with `input_tokens`, `output_tokens`, and `cache_creation_input_tokens`
- Typed `PermissionSuggestion` for `ToolPermissionRequest` permission suggestions
- Typed `PermissionDenial` for `ResultMessage` permission denial details
- Typed `StatusDetails` and `SuggestionMetadata` for system status responses
- Typed system message subtypes (`init`, `status`, `compact_boundary`)
- Typed `ToolInput` definitions for all built-in tools (Read, Write, Edit, Bash, Glob, Grep, etc.)
- Helper methods on `ClaudeOutput`: `is_assistant_message()`, `is_result()`, `is_error()`, `as_assistant()`, `as_result()`, `as_system()`, `text_content()`, `tool_uses()`
- `errors` field on `ResultMessage` for capturing error details
- Real production message test captures for structured content
## [2.1.4] - 2026-01-10
### Added
- Tool approval protocol support with interactive permission request/response handling
- `ControlRequest` and `ControlResponse` types for the tool permission workflow
- `ToolPermissionRequest` with `allow()`, `deny()`, and `allow_with_permissions()` helpers
### Fixed
- `--session-id` flag no longer incorrectly added when using `--resume` or `--continue`
## [2.1.3] - 2026-01-09
### Changed
- Version sync with Claude CLI 2.1.3
- WASM support documentation for the `types` feature with `wasm32-unknown-unknown`
## [2.0.76] - 2026-01-04
### Changed
- Version sync with Claude CLI 2.0.76
- Fixed content deserialization to handle both string and array formats
### Fixed
- Removed debug `eprintln` statements from output
## [0.3.0] - 2025-08-30
### Changed
- **Breaking:** Reorganized to feature-based architecture with `sync-client`, `async-client`, and `types` features
- **Breaking:** Switched logging from `tracing` to `log` crate
- **Breaking:** Client modules moved to top-level `client_sync` and `client_async`
- `types` feature enables WASM-compatible type definitions without client dependencies
## [0.2.1] - 2025-08-28
### Added
- `ping()` method on `AsyncClient` and `SyncClient` for connectivity testing
- `parse_json_tolerant()` to handle ANSI escape codes in responses
- Integration tests for slash commands (`/help`, `/status`, `/cost`)
### Fixed
- `num_turns` field type to handle `-1` for slash commands
## [0.2.0] - 2025-08-26
### Added
- Image content block support (JPEG, PNG, GIF, WebP) with `user_message_with_image()`
- OAuth token and API key environment variable support (`CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`)
### Changed
- **Breaking:** Session IDs use `UUID` type instead of `String`
- **Breaking:** `ClaudeInput::user_message()` now requires `UUID` for session_id
## [0.1.2] - 2025-08-25
### Added
- `resume_session()` and `resume_session_with_model()` on both clients
- Environment variable support for OAuth tokens and API keys
- Validation warnings for incorrect token/key prefixes
## [0.1.1] - 2025-08-25
### Added
- Session UUID versioning to track Claude Code sessions
- `session_uuid()` getter on both `AsyncClient` and `SyncClient`
- CLI builder generates UUID v4 by default
## [0.1.0] - 2025-08-25
### Added
- Comprehensive crate and module-level documentation
- `AsyncClient` and `SyncClient` API docs
### Changed
- Simplified licensing to Apache-2.0 only
## [0.0.5] - 2025-08-24
### Added
- `AsyncClient` with `query()` and `query_stream()` methods
- `SyncClient` for non-async contexts
- `ResponseStream` and `ResponseIterator` for iterative response processing
- `ResultMessage` with `UsageInfo` for token usage and cost tracking
- Claude CLI version checking with compatibility warnings
- Example programs: `basic_repl`, `async_client`, `sync_client`
### Changed
- Message types restructured to match Claude Code SDK (System, User, Assistant, Result)
## [0.0.1] - 2025-08-23
### Added
- Initial implementation of `claude-codes` crate
- `ClaudeInput` and `ClaudeOutput` enums for typed protocol messages
- `ClaudeCliBuilder` for streaming JSON mode
- Interactive testing binary for protocol debugging
- Automatic test case capture for failed deserializations