# Claude Code v2.1.224

> Claude Code v2.1.224, released 7 Aug 2026 (2026-08-07). 352 entries read out of the shipped bundle. Unofficial, and not affiliated with Anthropic.

Web version: https://changelogs.core-directive.com/v/2.1.224

A large amount of unreachable groundwork sits in this build, 59 entries in all. Cross-session messaging is the bulk of it: a `ListAgents` tool (alias `ListPeers`), a startup inbox socket, and SendMessage addressing peers by local socket or session id, all requiring the `tengu_harbor_kite` gate or `CLAUDE_CODE_HARBOR_KITE` and never running on Windows. Uploading untracked working-tree files into a cloud session needs both the `tengu_violin_wood` gate and the project setting `remoteFileMode` set to `container_sync`, with ceilings of 192 files, 100 MB and 60 seconds. Also present but off: a document database for the artifact tool behind `tengu_umber_lattice` or `CLAUDE_CODE_ARTIFACT_DB`, PR review artifact publishing whose enablement helper returns false unconditionally, per-session attention budget state whose key bindings splice in an empty object, and server-pushed work hints behind `CCR_SHR_SSE_HINTS`.

Memory becomes read-write with a new `memory_write` tool that creates or overwrites a whole document, refusing read-only stores, oversized content and anything a secret scan flags. A full `self-hosted-runner` command tree ships with `orchestrator`, `setup`, `doctor`, `code-sign` and `decode-token`, no gate in front of it, plus nine operator tools for inspecting a runner fleet and a `--scm-connector-host` tunnel to a private GitHub Enterprise server. `claude --environment <ccpool_...>` starts a cloud session on your own self-hosted environment, and it now works non-interactively, printing the session id and view URL or a JSON object with `--output-format json`. A new `crossSessionInbound` setting takes `accept`, `hold` or `refuse` for messages from your other sessions, `/list-agents` (aliased `/peers`) shows everything you can message, and `CLAUDE_CODE_MESSAGING_SOCKET` records a socket path with the session. That is 12 of 179 entries.

The per-session subagent spawn limit is gone entirely, along with its error message and counter; nesting depth and duplicate checks still apply. A run of fixes makes mailbox writes report failure instead of assuming delivery: plan submission throws a dedicated error, plan approval and rejection return failure with a retry hint, shutdown requests name the target, and typing `@name` in the REPL shows a 5 second notification when the write does not land. Incoming bridge messages were hardcoded as ungated and could run immediately as commands; they are now classified from content, platform and origin. `CLAUDE_CODE_DISABLE_ARTIFACT` and the managed `disableArtifact` setting are checked in two more paths that previously reported artifacts enabled anyway, and a plugin turned on only by repository settings is no longer written into your own installation records.

## What probably matters to you

Anything you can use today, anything that visibly changes, and anything worth poking at. One line each, open for detail.

### New `memory_write` tool for saving documents to a connected memory store

Claude can now save documents into a connected memory store, with secret scanning blocking risky writes.

**What**

Memory goes from read-only to read-write. Alongside `memory_list` and `memory_read`, Claude can now create or overwrite a whole document in a connected, writable memory store, and is prompted to check each of your replies for corrections and preferences worth keeping. Writes are refused for read-only stores, empty content, content over the byte cap, or anything a secret scan flags. Requires memory stores to be connected via `CLAUDE_MEMORY_STORES`, the `allow_memory_sync` permission, and `CLAUDE_CODE_DISABLE_ORG_MEMORY` unset.

**Details**

- Prompt guidance: one topic per file, frontmatter on each document, and a per-store index document kept current.
- Read-only stores refuse with "The memory store ... is read-only in this session; changes will not persist." Store resolution gained a write flag defaulting to false, which drives that refusal.
- Secret-detection refusal notes the store "is shared across the organization's connected projects."
- Written text is sanitised: control characters become U+FFFD, and line separators including U+2028 and U+2029 become plain newlines.
- The whole feature also sits behind the remote-config flag `tengu_haze_glass`, whose fallback in this build is off, so the real value comes from server config.
- In auto mode a write is routed to classifier review ("Saving a shared memory document requires classifier review."); in plan mode it asks.

**Evidence**

`The memory store ${JZo(e)} is read-only in this session; changes will not persist.`, `save a document to a connected project memory store`

- Flag `tengu_haze_glass`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Memory
- Names: `memory_write`, `memory_list`, `memory_read`, `CLAUDE_MEMORY_STORES`, `CLAUDE_CODE_DISABLE_ORG_MEMORY`
- Tier: Use it now
- Useful: 5/5
- Signal: 5/5

### `claude self-hosted-runner` command group

A whole new claude self-hosted-runner command tree is available to anyone who types it.

**What**

The CLI now ships and dispatches a whole `self-hosted-runner` subcommand tree: `orchestrator`, `setup`, `doctor`, `code-sign`, `decode-token`, plus a bare runner mode. None of this code was in 2.1.223. No flag or remote gate sits in front of the dispatch, so anyone who types the command reaches it.

**Details**

- Each subcommand lazily loads its own module, so startup for everyone else is unaffected.
- Comes with its own usage text, an operator setup and doctor guide, and a `/healthz` probe on the orchestrator.
- Entering the path fires a `cli_self_hosted_runner_path` telemetry event first, then dispatches.
- Around 21k lines of runner code are new to the bundle; whether that reflects a new feature or a packaging change is not something the source alone settles.

**Usage**

`claude self-hosted-runner doctor claude self-hosted-runner orchestrator` **Evidence** `cli_self_hosted_runner_path`, `Usage: claude self-hosted-runner [options]`

- Area: Self-Hosted Runner
- Names: `claude self-hosted-runner`, `self-hosted-runner orchestrator`, `self-hosted-runner setup`, `self-hosted-runner doctor`, `self-hosted-runner code-sign`
- Tier: Use it now
- Useful: 4/5
- Signal: 5/5

### /list-agents shows every Claude session you can message

Type /list-agents or /peers to see every Claude session you can message, with status and age.

**What**

A new slash command, aliased `/peers`, lists your subagents alongside other reachable Claude sessions: local ones on the same machine, cloud sessions, and Remote Control bridge sessions. Each row shows status, working directory and age. It appears only when peer messaging itself is available.

**Details**

- Gated by the same check that enables sending messages to peers, so if messaging is off the command is not listed.

**Usage**

`/list-agents` **Evidence** `List subagents and other Claude sessions you can message`

- Area: Slash Commands
- Names: `/list-agents`, `/peers`
- Tier: Use it now
- Useful: 5/5
- Signal: 4/5

### `self-hosted-runner orchestrator` polls for spawn hints and runs your own spawn script

An orchestrator subcommand polls for capacity hints and runs your own spawn-runner script to autoscale.

**What**

A new subcommand for people running their own Claude Code runner fleet. It polls the server for hints that more runner capacity is needed and runs `${hooks-dir}/spawn-runner` once per hint, so autoscaling is handled by a script you write. `--hook-concurrency` defaults to 4, `--hook-timeout` to 60s, and `--expected-spawn-seconds` to 120 (sent to the server as the lease time). `--min-idle` requests standby capacity, `--debug-dir` keeps diagnostics.

**Details**

- Hook exit codes are a contract: 0 is success, 1 is retryable and backs off, 2 or higher is non-retryable and trips a circuit break.
- The tail of the hook's stderr is forwarded to the server as the failure reason.
- It exits non-zero if the poll endpoint returns HTTP 400, 401, 403, 404 or 426.
- It refuses to start when the hook timeout plus the 5s SIGKILL grace period exceeds `--expected-spawn-seconds`. The hooks directory is required.

**Evidence**

`/v1/code/runners/self-hosted/spawn-hints/poll`

- Area: Self-Hosted Runner
- Names: `self-hosted-runner orchestrator`, `--hook-concurrency`, `--hook-timeout`, `--expected-spawn-seconds`, `--min-idle`
- Tier: Use it now
- Useful: 3/5
- Signal: 5/5

### Self-hosted runner operator tools

Nine operator tools can inspect runners, sessions, secrets, health, metrics and logs, loaded only via --base-tools.

**What**

Nine new tools let an operator inspect and drive a self-hosted runner fleet: `self_hosted_runner_get_pool`, `_list_runners`, `_list_sessions`, `_list_secrets`, `_spawn_local`, `_read_health`, `_read_metrics`, `_tail_log` and `_requeue_session`. They load only when the CLI's `--base-tools` names one of them, and never when `CLAUDE_CODE_REMOTE` is set or the session kind is "bg". Auth reuses the operator's existing OAuth session, and tailed logs are redacted for secrets before reaching model context.

**Details**

- The enabling latch is one-way in production: the internal helper that turns the operator tools back off is documented as test-only.
- When a runner spawns a session, any tool name starting with `self_hosted_runner_` is stripped from the server-supplied tools list before launch, so a spawned session cannot be handed fleet-operator tools.
- If stripping empties the list, the `--tools` argument is dropped entirely and the default tool pool is used.

**Evidence**

`[runner:session] Stripped self-hosted-runner operator tool names from the server-supplied tools arg`, `self_hosted_runner_spawn_local`

- Area: Self-Hosted Runner
- Names: `CLAUDE_CODE_REMOTE`
- Tier: Use it now
- Useful: 3/5
- Signal: 5/5

### Tunnel from the orchestrator to a private GitHub Enterprise host

Pass --scm-connector-host to tunnel Anthropic requests into a GitHub Enterprise server only your network can reach.

**What**

Passing `--scm-connector-host` opens a persistent WebSocket to Anthropic and proxies inbound HTTP requests to a GitHub Enterprise server that is only reachable from inside your network. `--scm-connector-id` is required alongside it, `--scm-connector-provider` defaults to `ghe`, and `--scm-connector-ca-file` supplies a custom CA. The connector is entirely off unless you pass the host flag.

**Details**

- Requests are checked against a method allowlist, hop-by-hop headers are stripped, and paths that would escape the configured origin are refused.
- Frames asking to open a raw socket or a PTY (`wsOpen`, `ptyOpen`) are rejected.
- Close code 4003 means another orchestrator already holds this connector; it retries after 30 seconds.
- `--scm-connector-host-rewrite` is documented as being for end-to-end testing only.

**Evidence**

`/v1/code/scm-connectors/{provider}/{id}/tunnel`

- Area: Self-Hosted Runner
- Names: `--scm-connector-host`, `--scm-connector-id`, `--scm-connector-provider`, `--scm-connector-ca-file`, `--scm-connector-host-rewrite`
- Tier: Use it now
- Useful: 3/5
- Signal: 5/5

### Artifact tool gains a document database, off by default

Published artifacts can gain a document database Claude reads and writes, off unless you set the env var.

**What**

The tool that publishes artifacts can now read and write a published artifact's document database, through two new actions `read_db` and `write_db`. Reads take a `db_op` of get, list or query; writes take set, update or delete, addressed by `collection` and `doc_id`. The actions only appear in the tool's schema when the `tengu_umber_lattice` gate is on (source fallback is false, real value from remote config) or the `CLAUDE_CODE_ARTIFACT_DB` environment variable is set.

**Details**

- A collection is a path of 1 to 31 slash-separated segments (letters, digits, `_ - . ~ : @ +` per segment), so subcollections nest like `boards/b1/columns`; `doc_id` is a single segment. There are byte caps on the composed document path and on serialized `data`.
- Queries support `where` comparators including the Firestore-style `array-contains`, plus `order_by`, `limit` and `cursor`; list results page through a cursor.
- Rows read back are wrapped in a `=== BEGIN ARTIFACT DB` fence and labelled as collaborator-written data, not instructions, and the read-consent prompt warns that collaborator-written rows will be read into the conversation.
- Prompts flag ownership: a suffix of "(someone else's artifact)" or "(ownership unconfirmed)" and a shared-with note, and the consent prompt says the database is written by its collaborators when a share probe fails.
- Consent is recorded per artifact for the session, once for reads and once for writes, with separate handling in plan mode and for notification-triggered reads.
- Transcript lines render as "read database" and "write database"; verbose mode shows the target document and share status on writes.
- Backend errors are normalised to a fixed set (not found, invalid argument, quota or rate, busy, upstream auth, store unavailable, not declared) each with a human remedy.
- Read is treated as read-only and safe to run concurrently; write is not.

**Evidence**

`Read a published artifact's database \u2014 collaborator-written rows will be read into the conversation`, `Database operation: 'get', 'list' or 'query' for read_db; 'set', 'update' or 'delete' for write_db. Required for both database actions; meaningless for every other action.`, `database write committed`, `CLAUDE_CODE_ARTIFACT_DB`, `Database collection path: 1-31 "/"-separated segments (letters, digits, _ - . ~ : @ + per segment), so subcollections nest like "boards/b1/columns". Required for read_db and write_db.`

- Flag `tengu_umber_lattice`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Artifacts
- Names: `CLAUDE_CODE_ARTIFACT_DB`
- Tier: Not switched on
- Useful: 3/5
- Signal: 5/5
- Present in the build but not switched on

### Cross-session messaging inbox now actually starts at startup

Startup can now actually open the local socket that lets other sessions message you.

**What**

Startup opens a local socket so other Claude Code sessions on the same machine can send this one messages. In the previous build the call site was empty. It is off unless the `tengu_harbor_kite` gate is on (in-source fallback false) or `CLAUDE_CODE_HARBOR_KITE` is set, and it never runs on Windows.

**Details**

- Startup clears `CLAUDE_CODE_MESSAGING_SOCKET` and, unless a socket path was passed explicitly, uses the default path.
- Success and failure are reported as `agents_cross_session_inbox`, with a `bind_failed` reason; setup time is recorded as `setup_uds_messaging_ms`.
- Skipped with a log line on remote thin clients and when the gate is off.

**Evidence**

`[uds-messaging] Skipped: cross-session messaging gate off`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_HARBOR_KITE`, `CLAUDE_CODE_MESSAGING_SOCKET`
- Tier: Not switched on
- Useful: 3/5
- Signal: 5/5
- Present in the build but not switched on

### New ListAgents tool for finding other sessions you can message

A tool that lists other Claude sessions you can message exists but stays off by default.

**What**

A tool that lists the other Claude Code sessions you can send a message to now exists and is registered, under the name `ListAgents` with the alias `ListPeers`. It is off unless the `tengu_harbor_kite` gate is on (source fallback false, real value from remote config) or the `CLAUDE_CODE_HARBOR_KITE` environment variable is set, and it never runs on Windows.

**Details**

- It is read-only and safe to run concurrently, and returns a single formatted list of reachable agents.
- Its `channel` and `q` inputs are both documented as "Not available in this build; leave unset."
- Previously only the name existed as a constant; the implementation is new here.
- The cache key for tool schemas now encodes whether the gate is on, so flipping it does not reuse a stale schema.

**Evidence**

`list agents you can SendMessage to`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_HARBOR_KITE`
- Tier: Not switched on
- Useful: 3/5
- Signal: 5/5
- Present in the build but not switched on

### Untracked files can be uploaded to a cloud session's working directory

Cloud sessions can upload untracked files, but only behind an off-by-default gate and a project setting.

**What**

Cloud sessions can now start from more than the git bundle. A new seeding step finds files that are untracked but not gitignored, hashes them and uploads them to the session, with hard ceilings of 192 files, 104857600 bytes (100 MB) and a 60 second deadline. It is off unless the `tengu_violin_wood` gate is on (it falls back to off, including on any error) and the project setting `remoteFileMode` is `container_sync`. Otherwise the session starts from the git bundle alone.

**Details**

- The file list comes from `git ls-files --others --exclude-standard -z` at the git root, and dependency directories are filtered out: node_modules, venv, .venv, __pycache__, build, dist, target, vendor.
- Symlinks, hard links, oversize files and files outside the git root are skipped.
- Each file is uploaded to the session's synced_file endpoint along with a manifest listing what was sent.
- You see a per-file progress line and a summary. When the feature is unavailable you get "Cloud file sync is unavailable for this session; starting from the git bundle alone".
- Telemetry: `tengu_dir_sync_inventory` carries per-reason skip counts including `skipped_too_large`, `skipped_symlink` and `skipped_over_budget`; upload start/complete and `tengu_dir_sync_seed_complete` record the outcome.

**Evidence**

`tengu_dir_sync_inventory`, `tengu_dir_sync_seed_complete`

- Flag `tengu_violin_wood`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cloud Sessions
- Names: `remoteFileMode`, `container_sync`
- Tier: Not switched on
- Useful: 3/5
- Signal: 5/5
- Present in the build but not switched on

### Auto mode can classify questions instead of stopping

In auto mode, a question can be classified and handled instead of halting the run.

**What**

In auto mode, deciding to ask on the AskUserQuestion tool no longer automatically counts as needing a human and halting the run; it can be classified and handled instead. `CLAUDE_CODE_AUTO_MODE_CLASSIFY_ASK_USER_QUESTION` decides this if set; otherwise the server-side auto mode config's `classifyAskUserQuestion` value applies and is treated as on unless it is explicitly false, so with no server value the new behaviour is active.

**Details**

- The old path logged a `tengu_auto_mode_fallback_to_ask` event and stopped.

**Evidence**

`CLAUDE_CODE_AUTO_MODE_CLASSIFY_ASK_USER_QUESTION`

- Area: Auto Mode
- Names: `CLAUDE_CODE_AUTO_MODE_CLASSIFY_ASK_USER_QUESTION`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Create a cloud session without a terminal

Create a cloud session straight from the command line with --environment and a prompt, optionally as JSON.

**What**

Running Claude Code non-interactively with `--environment` now creates a cloud session straight from the command line. It prints the title, session id, view URL and a resume hint, or with `--output-format json` emits an object with `ok`, `session_id`, `url` and `pool_id`. A prompt is required, as a positional argument or on stdin, and its absence is a hard error.

**Details**

- previously this path needed an interactive terminal
- new counters `tengu_remote_create_session`, `tengu_remote_create_session_success` and `tengu_remote_create_session_error` are tagged with `entry_point: pool_headless`

**Usage**

`claude --environment my-pool -p "fix the failing test" --output-format json` **Evidence** `Error: non-interactive --environment requires a prompt (positional or stdin). Run from a TTY for an interactive cloud session.`

- Area: Cloud Sessions
- Names: `--environment`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Cross-machine messages ask first when peer isolation is on

With isolatePeerMachines on, cross-machine messages now stop and ask for your approval.

**What**

With the isolatePeerMachines protection enabled, sending a message to a Remote Control session, to a cloud session, or to a name that could not be resolved because the cloud session list was unavailable, now stops and asks for your explicit approval instead of going ahead.

**Details**

- The three triggering cases are bridge addresses, resolved cloud sessions, and unresolvable names during a failed session-list lookup.

**Evidence**

`isolatePeerMachines is enabled \u2014 cross-machine message requires explicit approval`

- Area: Cross-Session Messaging
- Names: `isolatePeerMachines`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Cross-session messages from a coordinator are now delivered by default

Task notifications from a coordinator session now arrive automatically unless you set crossSessionInbound to refuse or hold.

**What**

Messages arriving from another session are now sorted into three classes, and a new "coordinator" class (a task notification that carries a peer send-message) is delivered unless you explicitly set `crossSessionInbound`. Set it to `refuse` to reject them or `hold` to buffer them; with no setting at all, coordinator messages are accepted outright.

**Details**

- The other two classes are peer messages, which follow the existing peer policy, and ungated messages.
- `hold` puts coordinator messages into the same hold queue as peer messages; the queue drops its oldest entry when full.
- Only an explicit `crossSessionInbound` value can hold or refuse this class, so an unset config means accept.

**Evidence**

`Your "crossSessionInbound" setting is "hold".`

- Area: Cross-Session Messaging
- Names: `crossSessionInbound`, `refuse`, `hold`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### New environment variable `CLAUDE_CODE_MESSAGING_SOCKET`

Point CLAUDE_CODE_MESSAGING_SOCKET at a socket path to enable session-to-session messaging and stream-json message replay.

**What**

Set `CLAUDE_CODE_MESSAGING_SOCKET` to a socket path and Claude Code records it with the session and, when the output format is `stream-json`, turns on replay of user messages without you asking for it. The variable is also carried through the set of `CLAUDE_CODE_*` variables Claude Code forwards to what it launches.

**Details**

- The path is stored on the session registration record as `messaging_socket_path`.
- Replay is only auto-enabled when it was not already on and the output format is `stream-json`; other output formats ignore the socket path for this purpose.
- Neither the variable nor the session field existed in the previous build.

**Evidence**

`CLAUDE_CODE_MESSAGING_SOCKET`

- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_MESSAGING_SOCKET`, `stream-json`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### New setting: `crossSessionInbound`

crossSessionInbound accepts accept, hold or refuse for messages arriving from your other sessions.

**What**

A settings key controlling what happens when another of your Claude Code sessions sends this one a message. Values are `accept`, `hold` and `refuse`. Left unset, a message is delivered automatically only when the sending session's permission mode is in the same class as yours, and is otherwise held for you to approve.

**Details**

- Held and refused messages produce user-facing text naming the setting, for example `Your "crossSessionInbound" setting is "hold".`

**Usage**

`{ "crossSessionInbound": "refuse" }` **Evidence** `crossSessionInbound`

- Area: Cross-Session Messaging
- Names: `crossSessionInbound`, `accept`, `hold`, `refuse`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Start a cloud session on a self-hosted environment with `--environment`

Start a cloud session on your own environment with claude --environment, plus hidden --ref and --on-branch companions.

**What**

`claude --environment <environment_id>` starts a cloud session on one of your own self-hosted environments (ids look like `ccpool_...`). An id that does not parse is rejected with an explicit error. Hidden companions `--pool` (a deprecated alias), `--correlation-id`, `--ref` and `--on-branch` also exist.

**Details**

- `--bg` cannot be combined with `--environment`; the two are separate backends and you get a dedicated message pointing at direct invocation.
- The `--bg` argument filter strips `--environment` and `--pool`, including their `=value` forms, before forwarding the remaining arguments.

**Usage**

`claude -p "fix the flaky test" --environment ccpool_abc123` **Evidence** `Create a new cloud session that runs on the given self-hosted environment (ccpool_...).`

- Area: Cloud Sessions
- Names: `--environment`, `--pool`, `--correlation-id`, `--ref`, `--on-branch`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Bridge messages from peers can no longer skip the approval gate

Text arriving over the editor bridge from peers can no longer run immediately without approval.

**What**

Incoming messages on the repl bridge were always classified as ungated because the classification was hardcoded, so any text arriving could be run immediately as a command. Messages are now classified from their content, platform, origin and receiver grouping, and the run-immediately shortcut is taken only when that classification really is ungated. Peer and coordinator messages go through the normal accept-or-hold policy.

**Evidence**

`[bridge:repl] Ran immediate command without enqueue: `

- Area: Editor Bridge
- Tier: You'll notice
- Useful: 4/5
- Signal: 4/5

### Messages from other sessions can be held for your approval

Messages from a session with a mismatched permission mode are held and shown with an approve/deny dialog.

**What**

When another Claude Code session sends this one a message, it can now be held instead of delivered. Two new reasons for holding: the sender's permission mode does not match yours, and a sender that never stated its mode into a session that bypasses prompts. Held messages appear with an explanation and, where a permission handler is wired up, an approve/deny dialog with a preview. Approving, denying or letting it expire sends a status message back to the sender.

**Details**

- Behaviour is controlled by the `crossSessionInbound` setting, resolved from policy, flag and user settings, then narrowed to the most restrictive of local and project settings.
- With no explicit setting, a session that bypasses prompts defaults to holding messages and every other session defaults to accepting them. An unrecognized permission mode fails closed to hold.
- Hold reasons recorded are `explicit-setting`, `mode-unknown`, `mode-mismatch`, `no-mode-asserted` and `bypass-default`; held, accepted and refused outcomes are all reported.
- The sender-mode matching half is additionally behind the `tengu_harbor_kite_mode_emit` flag, which is off in this build.
- The approval dialog is registered as a new notification type with its own title.

**Evidence**

`A message from another session needs your approval`, `The sender did not attest its permission mode and this session bypasses prompts. Review it below, or set "crossSessionInbound" to "accept".`

- Flag `tengu_harbor_kite_mode_emit`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 4/5
- Signal: 4/5

### New post-commit review hook fires after git commits

After a git commit completes, Claude is now nudged to review what it just committed.

**What**

Startup registers an internal hook that watches Bash tool output and injects a system message when it sees a completed git commit, so the model is prompted to review what was just committed.

**Details**

- Registered as an internal `PostToolUse` callback hook with a timeout of 1, matching the Bash tool names.
- Fires only when the command looks like a git commit and the output matches commit-shaped patterns such as `create mode` or `N files changed`.
- Skipped in one mode check; registration sits in the same non-plugin-only startup branch as the session file-access hooks.

**Evidence**

`registerUltrareviewPostCommitHook`

- Area: Git Integration
- Tier: You'll notice
- Useful: 4/5
- Signal: 4/5

### Memory writes are scanned for secrets before they land

Memory writes get scanned for secrets and refused, naming what matched and that the store is shared.

**What**

Memory document content is scanned before it is written, and a match refuses the write with a message naming what matched and noting that the store is shared with everyone on the project. The tool's own instructions carry the same rule. Behind `tengu_haze_glass`, which is false in this build.

**Evidence**

`Never write secrets or credentials into a memory \u2014 the store is shared with every collaborator on the project, and such writes are refused.`

- Flag `tengu_haze_glass`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Memory
- Tier: Not switched on
- Useful: 4/5
- Signal: 4/5
- Present in the build but not switched on

### MCP tools that need you to click through are not silently pre-approved

MCP tools that need you to click through always reach you instead of being auto-approved.

**What**

An MCP tool that declares it requires user interaction no longer has its permission re-checked in the background and auto-proceeded when the check returns "allow". Those tools always reach you.

**Details**

- The background re-check bails out when the tool reports `requiresUserInteraction`, which comes from the MCP metadata key `anthropic/requiresUserInteraction`.
- Separately, the permission-prompt telemetry event now records `isMcp`, distinguishing MCP tools from built-in ones.

**Evidence**

`anthropic/requiresUserInteraction`

- Area: MCP
- Tier: You'll notice
- Useful: 5/5
- Signal: 3/5

### Attention budget: state and telemetry land, nothing can trigger it

A per-session attention budget with up and down shortcuts is built but completely unreachable.

**What**

Groundwork for a per-session attention budget, one of "w25", "w50", "w100", "w200" or "unlimited" and defaulting to "unlimited". A cycle handler emits `tengu_attention_budget_cycle`, a new attachment type carries the budget into and out of the transcript, and two new key actions "chat:attentionUp" and "chat:attentionDown" exist. None of it is reachable: the chat key bindings and handler map both splice in an empty object where those entries would go.

**Details**

- The attachment renders nothing and its generator returns an empty list, so the budget never reaches the model.
- The cycle callback also returns early behind two condition checks that this build does not resolve.
- A `preserveLiveBudget` option is passed into session restore and ignored by the function receiving it.

**Evidence**

`tengu_attention_budget_cycle`, `chat:attentionUp`

- Area: Context Management
- Tier: Not switched on
- Useful: 2/5
- Signal: 5/5
- Present in the build but not switched on

### Git checkout machinery for self-hosted runners ships in full

Full git checkout machinery for self-hosted runners now ships, but you cannot reach it interactively.

**What**

The code that prepares a git working copy for runner and bring-your-own-compute sessions is now in the bundle; the previous release only declared its environment variable. It is not reachable from an interactive session. `CLAUDE_RUNNER_FETCH_DEPTH` tunes clone depth and accepts `full`, `0`, or a positive integer, with an unparseable value logged and ignored in favour of the default.

**Details**

- Access is validated with exponential backoff, and fetches retry under a cumulative wall-clock cap, degrading to HTTP/1.1 and low-speed timeouts on later attempts.
- Git's progress output feeds a stall watchdog that sends SIGTERM then SIGKILL to a hung fetch.
- Detects empty remotes and refs that have permanently failed, prefers a delta `read-tree` reset over `reset --hard`, and manages detached per-session worktrees.
- Command lines, stderr and telemetry are redacted for URLs and credentials: Anthropic keys, GitHub/GitLab/Slack/Square tokens, JWTs, and Bearer and Basic auth headers.

**Evidence**

`[byoc:git] Repository is empty, skipping checkout`

- Area: Runners
- Names: `CLAUDE_RUNNER_FETCH_DEPTH`
- Tier: Not switched on
- Useful: 2/5
- Signal: 5/5
- Present in the build but not switched on

### PR review artifact publishing, present but hard off

A full pull request review page with diagrams and comparisons is built but never offered.

**What**

The Artifact tool can expose a `pr_review` option that publishes a composed pull request review page from a JSON payload you author, rendered through a bundled template. It is backed by a large new review format covering diagrams, flows, before-and-after comparisons, concerns with visuals, and followups. The helper that decides whether the option is offered returns false unconditionally in this build, so the option never appears.

**Evidence**

`Publish a composed PR review page: file_path names the structured payload .json the artifact-pr-review skill had you author, and the page is built from the bundled review template at publish time.`

- Area: Artifacts
- Tier: Not switched on
- Useful: 2/5
- Signal: 5/5
- Present in the build but not switched on

### Server can push work hints over SSE so the runner picks up jobs faster

The runner can hold a live server connection and wake instantly on work, but it defaults off.

**What**

Set `CCR_SHR_SSE_HINTS` and the runner holds an event-stream connection to the server and wakes its poll loop as soon as a `work_available` event arrives, instead of waiting out the full poll interval. It defaults to off, and nothing else in the build turns it on.

**Details**

- Wakes are jittered by up to 200ms to spread out a fleet reacting to the same event, and the wake source is reported back to the server.
- The stream times out after 45 seconds without a read and reconnects with exponential backoff capped at 30 seconds.
- Wakes are suppressed while the runner is already at capacity.

**Evidence**

`CCR_SHR_SSE_HINTS`

- Area: Self-Hosted Runner
- Names: `CCR_SHR_SSE_HINTS`
- Tier: Not switched on
- Useful: 2/5
- Signal: 5/5
- Present in the build but not switched on

### `self-hosted-runner setup` and `doctor` walk you through it in a scoped Claude session

setup and doctor relaunch Claude with a restricted toolset to walk you through runner install and diagnostics.

**What**

Two new subcommands re-launch Claude with a fixed system prompt and a restricted tool allowlist to guide an operator interactively. `setup` goes from creating an environment in the Admin UI to a live runner and writes `./runner-setup/CHEAT-SHEET.md`. `doctor` works through nine diagnostic categories one step at a time and produces a redacted tarball you can attach to a support escalation.

**Details**

- Both run with `--permission-mode default` and tools limited to Bash, Read, Write, Todo and Task plus the typed runner admin tools.
- `doctor` additionally gets tools to list sessions and requeue a session; `setup` gets the tool that spawns a local runner.
- Outcomes are recorded under telemetry names `cli_self_hosted_setup` and `cli_self_hosted_doctor`, including `spawn_failed`, `child_signal` and `child_nonzero`.

**Evidence**

`Start the self-hosted runner doctor wizard. Greet me, then ask me to describe the symptom or pick from the 8 diagnostic categories. Work through it one step at a time.`

- Area: Self-Hosted Runner
- Names: `self-hosted-runner setup`, `self-hosted-runner doctor`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Advisor tool options and session budgets documented

Bundled API docs now cover advisor tool limits, caching, and session spend budgets you can set or remove.

**What**

The bundled tool-use reference now documents `max_uses`, `max_tokens` and `caching` on the advisor tool definition, along with the `advisor_tool_result_error` content variant and its error codes. The cURL guide gains session budget examples: creating one with `max_list_cost` in minor currency units, raising or lowering it, or removing it, which is one-way. Agent `version` is now an integer rather than a string.

**Evidence**

`advisor_tool_result_error`

- Area: API Docs
- Names: `max_uses`, `max_tokens`, `caching`, `max_list_cost`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Feedback drafting tool queues a report you review before sending

Claude can draft a feedback report locally that waits for you to review and send via /feedback.

**What**

Claude can now prepare a draft feedback report about Claude Code locally. Nothing is sent automatically: the draft waits for you to review and submit it via `/feedback`, and the model is instructed not to announce it in the middle of a task. Scope covers both product bugs and the model's own behaviour, such as retracting a correct answer, stopping short of finishing, or spawning too many subagents.

**Details**

- The prompt prescribes a fixed bullet structure for the draft, with a `failure_mode` field filled in only for reports about model behaviour.
- Whether this tool is exposed in your session is not something the bundle itself decides.

**Evidence**

`Queue a draft feedback report about Claude Code`

- Area: Feedback
- Names: `/feedback`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Git identity, commit signing and proxied clones for runner sessions

--configure-git sets a commit identity and installs SSH commit signing backed by a code-sign subcommand.

**What**

`--configure-git` sets a git identity of Claude <noreply@anthropic.com>, installs an SSH commit signing program and Co-authored-by hook stubs under `<base-dir>/.runner`, and requires git 2.34 or newer. The signing itself is done by a new `self-hosted-runner code-sign` subcommand, which sends the commit to the session's signing endpoint using the session access token.

**Details**

- `--use-anthropic-git-proxy` routes clones through Anthropic's git proxy using a repo-local credential helper. It requires `--capacity 1` and git 2.32 or newer.
- A governed-git configuration delivered by the server can supersede the proxy, mounting repos read-only and resetting `remote.origin.url` back to the plain upstream address.
- Only SSH-style signing is supported by `code-sign`; anything else is rejected.

**Evidence**

`code-sign: only SSH-style signing (-Y sign) is supported; got: `

- Area: Self-Hosted Runner
- Names: `--configure-git`, `self-hosted-runner code-sign`, `--use-anthropic-git-proxy`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Local messaging socket: where it lives, who can read it, and refusing to steal a live one

The messaging socket lives under XDG_RUNTIME_DIR with locked-down permissions and refuses to hijack a live one.

**What**

The local socket used for session-to-session messaging binds under `$XDG_RUNTIME_DIR/cc-socks/<pid>.sock`, with the directory created 0700 and the socket 0600, and its path exported as `CLAUDE_CODE_MESSAGING_SOCKET`. If you pass `--messaging-socket-path` pointing at a socket something is already listening on, startup refuses rather than hijacking it.

**Details**

- Falls back to a `/tmp` path, or `$PREFIX/tmp` under Termux, when the primary path would exceed the 103 byte limit on socket paths.
- Non-local paths are refused outright.
- Input is line-delimited JSON; any line over 1 MiB is dropped.

**Evidence**

`--messaging-socket-path points to a live socket: `

- Area: Cross-Session Messaging
- Names: `--messaging-socket-path`, `CLAUDE_CODE_MESSAGING_SOCKET`, `XDG_RUNTIME_DIR`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Remote sessions can run on self-hosted runner pools

You can target your organization's own runner machines when starting a remote session.

**What**

Claude Code can now list your organization's self-hosted runner pools (machines your org runs itself rather than Anthropic-hosted ones) and target one for a remote session. Availability is decided by your organization's server-side configuration and first-party login; other auth returns an empty list.

**Details**

- Pools come from `/v1/code/runners/self-hosted/pools` with the beta header `ccr-byoc-2025-07-29`, and each entry carries a pool id, name and a count of live runners.
- A pool id prefixed `ccpool_` is sent as the self-hosted pool target instead of the usual environment id.
- A pool default coming from an untrusted source is ignored with a warning pointing at `/remote-env`, and a public Project rejects pools outright: public Projects run on Anthropic-hosted infrastructure only.
- List outcomes are recorded with separate non-200, API-error and unexpected-response variants.

**Evidence**

`ccr-byoc-2025-07-29`, `/v1/code/runners/self-hosted/pools`

- Area: Self-Hosted Runner
- Names: `/remote`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Runner can refuse repo-committed settings that reach outside the workspace

The runner can warn about or block repo-committed settings that grant access outside the workspace.

**What**

Before starting a session, the runner inspects the checked-out repo's `.claude/settings.json` and `settings.local.json` for grants that would escape the session workspace and reports or blocks them. `--confine-repo-settings` (or `SELF_HOSTED_RUNNER_CONFINE_REPO_SETTINGS`) picks enforce, warn or off; the CLI defaults to warn, which logs what it would have refused and starts the session anyway.

**Details**

- What it looks at: permissions.allow paths, sandbox filesystem allowRead/allowWrite, additionalDirectories, bare Edit and Write rules, `env` set by the repo, and any repo override of the operator's sandbox or hooks posture.
- Symlinks and `..` are resolved, and a path that cannot be resolved is treated as a violation rather than allowed.
- An invalid value for the flag fails at startup rather than silently falling back.
- Outcomes are reported through telemetry named `self_hosted_confine`, with separate reasons for warn and enforce.

**Evidence**

`self_hosted_confine_violation_enforce`

- Area: Self-Hosted Runner
- Names: `--confine-repo-settings`, `SELF_HOSTED_RUNNER_CONFINE_REPO_SETTINGS`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Runner collects metrics from its child sessions and re-exports them

Set OTEL_METRICS_EXPORTER=prometheus and child sessions report through the runner, giving one scrape target per machine.

**What**

The runner's health server accepts OTLP/JSON metrics on POST `/v1/metrics` and merges them into its own `/metrics` output, labelled with `session_id` and `client_platform`. When capacity is above 1 and you have set `OTEL_METRICS_EXPORTER=prometheus`, child sessions are automatically pointed at `http://127.0.0.1:<health-port>/v1/metrics` instead, so one scrape target covers the whole machine.

**Details**

- Requests from any address other than loopback get 403. Bodies over 1 MiB get 413.
- Child metric names that collide with the runner's own `claude_code_self_hosted_runner_` prefix are dropped.
- A session's series are swept from the output when that session ends.

**Evidence**

`OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE`

- Area: Self-Hosted Runner
- Names: `OTEL_METRICS_EXPORTER`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Sandbox credential masking gains JWT handling, per-claim masking and AWS SigV4 re-signing

Sandbox credential masking can fake JWTs, mask single claims, and re-sign AWS requests after swapping keys.

**What**

Credential entries under `sandbox.credentials` can now use `decode: "jwt"` to swap a real token for a structurally valid fake so client-side parsing still works, and `maskClaims` to mask only named claims and rebuild the token. A new `awsPairs` setting groups masked environment variables into access-key, secret and session-token trios so the proxy can re-sign AWS SigV4 requests, and a new `sigv4` block sets deny or passthrough policy for streaming uploads, presigned URLs and SigV4A.

**Details**

- For environment variables, combining `decode` with `extract`, or with a fail-closed `onExtractNoMatch`, is rejected at validation time.
- SigV4A signatures are asymmetric, so there is no shared key to recompute; the `sigv4` block decides what happens to them.
- `awsPairs` and `sigv4` are only honored from user settings, managed/policy settings, or the CLI `--settings` flag, and are now forwarded into the proxy's credential block.
- At startup, a masked environment variable whose name uses the reserved synthesized-placeholder prefix produces a warning and degrades to `deny`.

**Evidence**

`Policy for SigV4A (AWS4-ECDSA-P256-SHA256) asymmetric signatures: there is no shared-key HMAC to recompute.`, `awsPairs`

- Area: Sandbox
- Names: `sandbox.credentials`, `decode`, `maskClaims`, `awsPairs`, `sigv4`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Advisor entry added to Managed Agents multiagent rosters

Multiagent rosters can include one advisor entry so the main thread consults a stronger model mid-turn.

**What**

Rosters accept a new `{"type": "advisor", "model": ...}` entry, at most one per roster, letting the main thread consult a more capable model partway through a turn.

**Details**

- Consultations run as a platform-spawned thread under a reserved name and do not count against the 25-concurrent-thread limit.
- Advice arrives as an `agent.thread_message_received` event, either plaintext or `[{"type": "redacted"}]` depending on the advisor model.
- Fable 5 is documented as temporarily unavailable in the advisor role.

**Evidence**

`anthropic.advisor`

- Area: Claude API Skill
- Names: `advisor`
- Tier: You'll notice
- Useful: 3/5
- Signal: 4/5

### Managed Agents sessions can carry a hard dollar spend cap

The bundled Claude API docs now describe a hard dollar spend cap that pauses a session idle.

**What**

The bundled `claude-api` skill, the documentation Claude Code consults when you ask it to write code against the Claude API, now documents a session `budget`. It is set at create time only, in minor units and USD only, for example `{"type": "limit", "max_list_cost": {"amount": "2500", "currency": "USD"}}`. A session that reaches the cap pauses idle instead of terminating, and only resumes if the budget is raised or removed.

**Details**

- A paused session accepts only settle events: `user.tool_confirmation`, `user.tool_result`, `user.custom_tool_result`, `user.interrupt`.
- Removing a budget is one-way; it cannot be added back to that session.
- Raising the cap must exceed what the session already spent, otherwise the API rejects it.
- Claude Code will generate and explain this shape whenever the skill is in use.

**Evidence**

`budget.max_list_cost must be greater than the session's consumed list cost`

- Area: Claude API Skill
- Names: `budget`, `max_list_cost`
- Tier: You'll notice
- Useful: 3/5
- Signal: 4/5

### Memory documents carry version tokens and report conflicts

Org memory documents now show version tokens and report conflicts so concurrent edits get retried instead of clobbered.

**What**

Reading an org memory document now shows a version token next to the updated date, writes report the version they produced, and a concurrent edit produces a distinct conflict result telling the model to re-read the document and retry with the fresh token. Requires org memory to be enabled for the session.

**Details**

- The conflict message is "The memory store reported a concurrent change to this document."
- The conflict result also carries the conflicting path taken from the server response.
- The version token is appended to reads in the form `[version: ...]`.

**Evidence**

`The memory store reported a concurrent change to this document.`

- Area: Memory
- Tier: You'll notice
- Useful: 3/5
- Signal: 4/5

### Turn summary now counts workshop page edits and hook time

Turn summaries now count workshop page edits and how long PreToolUse hooks took.

**What**

The activity summary after a turn reports workshop writes as their own line, "workshop making/made N page edit(s)" with a diff stat of lines added and removed. If nothing else happened but hooks ran, it adds a line counting how many PreToolUse hooks fired and how long they took in total.

**Details**

- Workshop write counts and line deltas are tracked per tool call.

**Evidence**

`hookTotalMs`

- Area: Activity Summary
- Tier: You'll notice
- Useful: 3/5
- Signal: 4/5

### /ultrareview prompts and free-quota counts, all switched off by default

Your remaining free cloud reviews can appear in the slash menu and in tips after commits.

**What**

New code fetches your remaining free cloud reviews from `/v1/ultrareview/quota` (3 second timeout) and can show that count in the slash-command menu, plus two one-off tips: a prose pointer, and a hook that fires after a `git commit` suggesting `/ultrareview` before you push. Each of the three surfaces, `slash_menu`, `prose_pointer` and `post_commit`, is keyed separately in the `tengu_ultrareview_awareness` remote config, so with no server value nothing appears.

**Details**

- `CLAUDE_CODE_ULTRAREVIEW_QUOTA_FIXTURE` substitutes a fixed quota response instead of calling the API.

**Evidence**

`Tip: Run /ultrareview before you push to catch bugs with a cloud-based multi-agent review`

- Flag `tengu_ultrareview_awareness`: Not enough to say (read for one account on one subscription tier against v2.1.224; this account: no value returned, anonymous baseline: no value returned, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Code Review
- Names: `/ultrareview`, `CLAUDE_CODE_ULTRAREVIEW_QUOTA_FIXTURE`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### An auto-generated index of your memory files

Claude can auto-generate an index of your memory files and feed it in as a source.

**What**

Claude Code can build a listing of the memory files under a directory and feed it in as an extra memory source, under a virtual path named `<auto-memory-index>` rather than a real file. The content begins with a generated line naming the directory and noting that the paths below it are relative. It is off unless a server value enables `tengu_stone_shell`, whose built-in fallback is false, and it is skipped entirely when `CLAUDE_CODE_REMOTE` is set.

**Evidence**

`<auto-memory-index>`

- Flag `tengu_stone_shell`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Memory
- Names: `CLAUDE_CODE_REMOTE`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Artifact tool gains document-store read and write

Artifacts get document read and write operations with separate remembered consent for each.

**What**

New read and write operations address documents by page address, collection path and document id, with errors for a bad address, an over-long composed path and a missing document id. Consent is remembered separately for reads and writes, and again separately for approvals given by a person in plan mode.

**Details**

- The stored consent covers write approval, human write approval, and per-address lists for read consent and human read consent.
- Whether these operations are offered unconditionally or behind a flag is decided by the tool's schema, which is not resolved in this build.

**Evidence**

`doc_id is required for a get`

- Area: Artifacts
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Effort picker can show relative cost, gated off

Effort levels can be labelled with relative cost like ~2x, hidden behind an off gate.

**What**

The effort level slider can label each level with an estimated cost multiplier against the model's default effort, shown as `~2x` or `~0.25x`, with a distinct note on the default level. It is behind the `tengu_marbled_teal` gate, which falls back to off, and it also needs per-model cost index data to be present.

**Evidence**

`the estimated cost of `

- Flag `tengu_marbled_teal`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Thinking Effort
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Effort slider can show what each level costs

The thinking-effort picker can show what each level costs relative to the default.

**What**

The thinking-effort picker gains a description line under each level: the default level is labelled as the default, and the others show an estimated multiple of the default's cost taken from the model's cost index. When shown it replaces the warning about overthinking at the top level. It is behind the remote flag `tengu_marbled_teal`, which falls back to off in this build, so whether you see it is decided by server config.

**Evidence**

`The default effort for this model`

- Flag `tengu_marbled_teal`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Thinking Effort
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Files received from a peer session are verified before use

Files another session sends you are size- and hash-checked and confined to a staging folder before use.

**What**

When another session sends file attachments, they are now checked before Claude sees them: the attachment list is schema-parsed and capped in count, each path must sit inside the transfer staging directory, must be a regular file, and must match the declared size and hash. Verified files are copied into an uploads directory with owner-only permissions. This runs only with peer messaging enabled.

**Details**

- A file that fails any check is logged with a `[peer-file-transfer]` prefix and surfaces as a notice, rather than being attached silently.

**Evidence**

`transfer path is outside the file-transfer spool`

- Area: Cross-Session Messaging
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Fleet view can list sessions it has no record of

Fleet view can show other running sessions it never tracked, once two gates are on.

**What**

The fleet view refresh folds in other concurrent interactive sessions found in the local process registry, so sessions started outside its own bookkeeping appear. Needs both the `tengu_fleetview_peers` gate (in-source fallback false) and the cross-session messaging gate.

**Details**

- A registry entry qualifies if it is a different pid, has a session id, reports a peer protocol version at or above a minimum, and was seen within the last 24 hours.
- Synthesised entries are keyed off the other process's pid; a locally known newer name or intent wins over the registry copy.

**Evidence**

`tengu_fleetview_peers`

- Flag `tengu_fleetview_peers`: Not enough to say (read for one account on one subscription tier against v2.1.224; this account: no value returned, anonymous baseline: no value returned, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Background Tasks
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Memory writes will require the version token from your last read

Memory writes will need the version token from your last read, forcing a read before overwriting.

**What**

A build-gated change to the memory tools makes every write carry `if_version`: either the 12-character version token returned by the most recent read or write of that path, or the literal word `new` for a document that does not exist yet. The model therefore has to read a document before overwriting it. Reads now return the token; listing does not. Off in this build unless the `tengu_haze_glass` gate is on, which defaults to false.

**Details**

- A stale token, or `new` against a path that already exists, rejects the write and returns the current content (when it fits within the read size cap) plus its current version, so the model can merge and retry.
- Rejections are recorded as `tengu_memory_tools_version_conflict` with the reason.

**Evidence**

`Every ${lS} needs if_version. Pass the literal word new for a document that does not yet exist.`

- Flag `tengu_haze_glass`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Memory
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Permission prompts for artifact database access

Artifact database access asks permission first, with reads and writes scoped differently.

**What**

The new artifact database actions ask before first use, and the prompt spells out how far approval reaches: approving a write covers database writes to any artifact for the rest of the session, approving a read covers that one artifact for the rest of the conversation. Writes persist and are visible to everyone who can open the artifact. The actions are gated behind `CLAUDE_CODE_ARTIFACT_DB` and the `tengu_umber_lattice` gate.

**Details**

- In plan mode the request is always routed to the user rather than the automatic permission classifier.

**Evidence**

`Claude wants to write to this artifact's database \u2014 writes persist and are visible to everyone who can open the artifact; approving covers database writes to any artifact for the rest of this session.`

- Flag `tengu_umber_lattice`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Artifacts
- Names: `CLAUDE_CODE_ARTIFACT_DB`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### SendMessage can address other sessions directly and reports delivery timing

Messages can be addressed to a specific session by socket or id, blocked unless peer messaging is enabled.

**What**

SendMessage can now target a peer by local socket (`uds:`) or by session id (`bridge:`), with failure advice that distinguishes a stale socket from a busy pipe, and it records a `send_message_delivery` event with the route taken and the time it took. Both address schemes are refused at validation time unless the peer feature is on, which needs `tengu_harbor_kite` or `CLAUDE_CODE_HARBOR_KITE` and a non-Windows machine.

**Details**

- A socket that is gone (connection refused or missing) and a pipe that is busy produce different guidance.
- The delivery event also carries how long the send waited when it was blocked.
- With `isolatePeerMachines` on, sending to a cloud session now requires an explicit approval prompt.

**Evidence**

`send_message_delivery`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_HARBOR_KITE`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Untracked files can be seeded into a cloud session

Teleporting to a cloud session can upload your untracked files so the remote starts with them.

**What**

Teleporting to a cloud session can now inventory your git root and upload untracked working-tree files so the remote session starts with them, reporting progress as it goes. It needs both the `tengu_violin_wood` gate, whose fallback is off, and the per-project setting `remoteFileMode` set to `container_sync`. If the upload times out or is unavailable, teleport falls back to sending the git bundle alone.

**Details**

- Uploaded files go under a `.ccr-seed` prefix accompanied by `manifest.json` and `ready.json`.
- New telemetry events: `tengu_dir_sync_inventory` and `ccr_dir_sync_seed`.

**Evidence**

`Cloud file sync timed out after `

- Flag `tengu_violin_wood`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cloud Sessions
- Names: `remoteFileMode`, `container_sync`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Teammate messages carry the sender's permission mode

Messages between agents now carry the sender's permission mode, preserved across relays.

**What**

Messages passed between agents now include the sender's permission mode in the message header, drawn from a fixed two-value list, and the parser preserves it when a message is relayed through further hops.

**Evidence**

`from-mode=`

- Area: Agents
- Tier: Under the hood
- Useful: 3/5
- Signal: 4/5

### "pool" renamed to "environment" in runner flags, old names still work

Runner "pool" flags are renamed to "environment"; old names still work but warn.

**What**

`--pool-secret-file` and `SELF_HOSTED_RUNNER_POOL_SECRET` still work on both the runner and the orchestrator but now print a deprecation warning to stderr. Use `--environment-secret-file` or `SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET` instead; the error shown when no secret is supplied at all names only the new spellings.

**Evidence**

`[runner:warn] --pool-secret-file is deprecated; use --environment-secret-file`

- Area: Self-Hosted Runners
- Names: `--environment-secret-file`, `SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET`, `--pool-secret-file`, `SELF_HOSTED_RUNNER_POOL_SECRET`, `--environment`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### `--teleport` and `--cloud` show up in `claude --help`

`--teleport` and `--cloud` now show up in `claude --help`.

**What**

Both options are no longer hidden and now appear in help output. `--remote` stays hidden as a deprecated alias.

**Evidence**

`--cloud [description|session_id|url]`

- Area: CLI
- Names: `--teleport`, `--cloud`, `--remote`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### `ANTHROPIC_BEDROCK_REGION_PREFIX` pins the Bedrock inference-profile region

Set ANTHROPIC_BEDROCK_REGION_PREFIX to pick which regional inference profile your Bedrock models resolve to.

**What**

A new environment variable lets Bedrock users choose which regional inference-profile prefix models resolve to. Accepted values are `us`, `eu`, `apac`, `jp`, `au` and `global`. It is added to the environment passthrough sets used for sandboxed and spawned processes, so child processes inherit it.

**Details**

- An older seven-entry region list including `us-gov` is kept separately, used for parsing Bedrock hostnames rather than for this variable.
- Setting a prefix that cannot be confirmed, or models that resolve to a different prefix, produce warnings (see below).

**Usage**

`export ANTHROPIC_BEDROCK_REGION_PREFIX=eu` **Evidence** `ANTHROPIC_BEDROCK_REGION_PREFIX`

- Area: Bedrock
- Names: `ANTHROPIC_BEDROCK_REGION_PREFIX`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### `claude plugin eval` writes a report and publishes it by default

Plugin eval now writes report.html and publishes it to claude.ai unless you pass --no-publish.

**What**

After cases run, `report.html` is written into the results directory even without `--report`, and the report is published to claude.ai by default rather than only with `--publish-report`. Pass `--no-publish` to keep it local. `--no-publish` and `--publish-report` together are an error. Publish-by-default is controlled by a server flag whose built-in fallback is on, so with no server value it publishes; an explicit `--publish-report` ignores the flag entirely.

**Details**

- The JSON result is now computed once and shared between writing, publishing and printing.
- Publishing failures explain that artifacts are turned off for the account, provider or privacy mode, and the failure text names the local copy when one was written.
- `--publish-report`'s help text changed to match the new default.

**Usage**

`claude plugin eval ./my-plugin --no-publish` **Evidence** `tengu_quartz_thimble`, `--no-publish and --publish-report are contradictory — pass at most one.`

- Flag `tengu_quartz_thimble`: Off by default, switched on for this account (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: off) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Plugin Eval
- Names: `claude plugin eval`, `--no-publish`, `--publish-report`, `--report`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### New `dialogExpiry` setting for how long forwarded dialogs wait

Set dialogExpiry to control how long a forwarded permission prompt waits before resolving to its safe default.

**What**

A settings key accepting `60s`, `5m`, `10m` or `never` sets how long a permission or user dialog forwarded to a remote client stays parked awaiting an answer, and how long a held cross-session message waits for approval, before it resolves to its safe default. Without the setting the timeout is 300000 ms (5 minutes). `CLAUDE_CODE_USER_DIALOG_TIMEOUT_MS` overrides both.

**Details**

- The setting is read from trusted configuration sources only.
- Prompts answered locally are unaffected; this only applies to dialogs sent to a remote client.

**Usage**

`{ "dialogExpiry": "10m" }` **Evidence** `Max time a permission/user dialog forwarded to a remote client stays parked awaiting an answer`

- Area: Permissions
- Names: `dialogExpiry`, `CLAUDE_CODE_USER_DIALOG_TIMEOUT_MS`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### New setting: `dialogExpiry`

dialogExpiry accepts 60s, 5m, 10m or never for how long forwarded dialogs and held messages wait.

**What**

A new settings key controls how long a permission or question dialog forwarded to a remote client stays parked, and how long a held cross-session message waits for approval, before resolving to its safe no-action default. Accepts "60s", "5m", "10m" or "never", defaults to 5 minutes, and falls back to the default on an invalid value. Purely local prompts are unaffected.

**Details**

- Resolution order: the environment variable `CLAUDE_CODE_USER_DIALOG_TIMEOUT_MS`, then `dialogExpiry`, then 300000 ms.
- Read from trusted settings sources only, and sits next to the existing `askUserQuestionTimeout` key with the same enum.

**Usage**

`{ "dialogExpiry": "10m" }` **Evidence** `dialogExpiry: Ir(["60s", "5m", "10m", "never"])`, `dialogExpiry`

- Area: Permissions
- Names: `dialogExpiry`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### Cross-session messages render as a chat line

Messages from another session now render as a chat line instead of raw XML.

**What**

A message arriving from another Claude Code session used to show as raw XML. It now renders with an "@ name" header and the body indented, with the wrapper tags stripped. Outside verbose and transcript mode, and when not queued, it collapses to a compact peer marker.

**Evidence**

`UserCrossSessionMessage`

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### Feedback tool now covers Claude's own behaviour, not just product bugs

Claude will now offer to draft feedback when it itself goes wrong in your session.

**What**

The built-in feedback drafting tool used to be scoped to Claude Code the product. It now explicitly handles model-behaviour reports too, and will offer to draft one when Claude itself goes wrong in the session: retracting a confident answer, stopping short of the task, declining something reasonable, spawning too many subagents, tone problems, over-questioning, or scope creep.

**Details**

- Report fields are unchanged: type, title, details, area, failure_mode, task_category.

**Evidence**

`draft product or model-behavior feedback report queue`

- Area: Feedback
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### Forked skill launches: session cap removed, duplicate race closed

The cap on forked-skill subagents per session is gone, and duplicate launches are blocked.

**What**

The per-session cap on how many forked-skill subagents you can spawn is gone from this build entirely. The rule that only one agent per forked skill runs at a time is now checked both before and after the async write that reserves it, closing a window where two launches could both get through.

**Evidence**

`forked_skill_live_duplicate`

- Area: Subagents
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### Invalid sandbox credential settings now fail closed instead of being ignored

A malformed sandbox.credentials block now denies everything instead of quietly disabling protection.

**What**

A malformed `sandbox.credentials` block used to be dropped entirely, leaving no credential protection in place. It now degrades to a deny-everything skeleton: all sigv4 signing modes deny, and implicit AWS credential pairing is suppressed rather than left open.

**Details**

- Invalid awsPairs entries become non-functional suppressors built from the conventional AWS environment variable names, with a deterministic suffix so the result is stable across runs.
- The sigv4 sub-keys streaming, presigned and sigv4a each degrade individually to "deny".
- `allowPlaintextInject` degrades to an explicit false, so a lower-precedence settings file cannot turn it back on.
- A single object where a list is expected is accepted as a one-element list with a warning.
- A valid credentials block is salvaged out of an otherwise invalid `sandbox` value, with every other sandbox field ignored.

**Evidence**

`The credentials block was salvaged from the invalid sandbox value and stays enforced; every other sandbox field was ignored.`

- Area: Sandbox
- Names: `sandbox.credentials`
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### Plugins enabled only by repo settings are no longer auto-installed and recorded

A plugin switched on by repo settings alone is no longer installed into your own records.

**What**

A plugin turned on solely by settings committed in the repository you opened is no longer written into your own plugin installation records. Previously a project or local settings file could get a plugin auto-installed on your behalf. Plugins backed by your own config are unaffected.

**Details**

- Skipped entries are logged with the reason that they were enabled only by repo-authored settings.

**Evidence**

`Skipped auto-recording `

- Area: Plugins
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### Remote Control disconnects appear in the transcript

Losing Remote Control now adds a transcript warning hinting at `/remote-control` to reconnect.

**What**

Losing the Remote Control connection now adds a warning message to the conversation instead of failing silently, with a hint to run `/remote-control` to reconnect. The hint is omitted when the reason already mentions logging in, remote control, a restart, policy or an update, and an identical message is not repeated twice in a row.

**Details**

- only reached while the Remote Control bridge is running
- errors now also record whether the cause was the terminal or something else

**Evidence**

` \u2014 run /remote-control to reconnect`

- Area: Remote Control
- Names: `/remote-control`, `/remote`
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### Runner management tools require a first-party login and say so

Runner management tools now require `claude login` on Anthropic's API; API keys are rejected.

**What**

The self-hosted runner tools now check up front that you are on Anthropic's own API and signed in with `claude login`. An API key is not accepted. Failures carry the HTTP status, and 401 or 403 responses append a hint pointing at `claude login` and `ANTHROPIC_BASE_URL`.

**Details**

- The API base is `ANTHROPIC_BASE_URL` with trailing slashes stripped, falling back to the built-in Anthropic API URL.
- These tools are only reachable from the setup and doctor wizards.

**Evidence**

`Self-hosted runner management is only available on the first-party Anthropic API provider.`

- Area: Self-Hosted Runners
- Names: `claude login`, `ANTHROPIC_BASE_URL`
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### Tell another Claude session something and hear back when it was not delivered

If another session holds, denies or ignores your message, you now see a warning instead of silence.

**What**

When you send a message to another Claude session and the recipient holds, denies, or lets it expire, your session now prints a warning line and quietly tells the model not to sit waiting for a reply. Receipts arrive in batches of up to 200 per status and are de-duplicated per recipient address.

**Details**

- Two surfaces per receipt: a visible warning line, and a low-priority note injected into the conversation so the model stops blocking on an answer.
- Covers held, denied and expired outcomes.

**Evidence**

`[Cross-session delivery notice]`

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### The per-session subagent spawn limit is gone

You can spawn as many subagents as you want in one session; the per-session cap is gone.

**What**

There is no longer any cap on how many subagents one session can spawn. The limit, its error message and the counter that tracked spawns have all been removed. Subagents launched from a forked skill still stop at the nesting depth limit and still refuse to start a duplicate of one that is already running.

**Details**

- `CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION` still appears in a list of recognised environment variable names, but nothing in the build reads or enforces it.

**Evidence**

`forked_skill_depth_cap`

- Area: Subagents
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### Wizard operator tools have a one-way on switch

A session switch for wizard operator tools exists, defaults off, and can never be turned back off.

**What**

A session flag for "wizard operator tools" ships defaulting to off, and its setter refuses to be passed false outside tests. Once something enables it during a session, nothing in normal operation turns it back off. No code in this build turns it on.

**Evidence**

`setWizardOperatorToolsEnabled(false) is test-only: the wizard latch is one-way in production`

- Area: Tools
- Tier: Not switched on
- Useful: 1/5
- Signal: 5/5
- Present in the build but not switched on

### Inference geography can be pinned on a Managed Agents agent

The Claude API docs describe pinning inference geography to us or global on an agent's model.

**What**

The skill docs add `inference_geo`, either `"us"` or `"global"`, set inside an agent's `model` object rather than at the top level as on the Messages API. It is checked when the agent is saved, when a session is created, and on every turn, and it cannot change for the life of a session.

**Details**

- Unlike `effort`, an `inference_geo` given in a per-session `model` override is applied.
- Omitting it in an override clears the agent's pin.
- Every model in a multiagent roster must use the same geography.

**Evidence**

`Pinning inference geography`

- Area: Claude API Skill
- Names: `inference_geo`
- Tier: You'll notice
- Useful: 2/5
- Signal: 4/5

### Tighter limits on the first publish of a review page

First publish of a review page now caps followups, unchanged items and text lengths.

**What**

Publishing a review page for the first time now rejects more than 3 unchanged-item entries, more than 3 followups, a bottom line longer than 600 characters, or any concern body longer than 300 characters. Pages already published under the older, looser limits can still be republished as they are.

**Evidence**

`bottom_line over the first-publish cap`

- Area: Review Pages
- Tier: You'll notice
- Useful: 2/5
- Signal: 4/5

### Workshop document writes counted separately in the activity summary

Writes to workshop documents are counted separately from scratchpad writes in the activity summary.

**What**

The collapsed activity summary now counts writes to workshop documents (files like `.workshop.md` or `.workshop.html` outside your working directory) on their own, with lines added and removed, rather than lumping them in with scratchpad writes.

**Details**

- Counts are merged across collapsed groups.
- A new pass rolls the counts back when the matching tool result comes back as an error, so failed edits no longer inflate the totals.

**Evidence**

`workshopLinesRemoved`

- Area: Activity Summary
- Tier: You'll notice
- Useful: 2/5
- Signal: 4/5

### Artifact database access must be declared when publishing

An artifact must declare database capability when published, or database calls fail and ask you to republish.

**What**

Among the new database errors for artifacts is one for publishing: if the artifact's currently published version does not declare the db capability, the call fails and tells you to republish with it declared. Database access is opt-in per artifact rather than automatically available. Gated by `CLAUDE_CODE_ARTIFACT_DB` and the `tengu_umber_lattice` flag.

**Evidence**

`this artifact's current published version does not declare the database capability \u2014 republish it with the db capability declared, then retry`

- Flag `tengu_umber_lattice`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Artifacts
- Names: `CLAUDE_CODE_ARTIFACT_DB`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Cloud sessions can be created through a newer request shape

Cloud session creation can use a newer request shape for review and plan sources.

**What**

Creating a cloud session can now build a different request body for the newer session-create endpoint, carrying worker, source, outcome, seed bundle and branch-reuse fields in place of the older single session-context blob. It applies only when an account-level check passes and the session comes from ultrareview or ultraplan; nothing in this build sets that check, so what accounts get is decided elsewhere. `--project` and agent dispatch still refuse outright on accounts still using the old endpoint.

**Evidence**

`v1alpha2`

- Area: Cloud Sessions
- Names: `--project`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Forked sessions are told once that their twin is still running

A forked session can be told once that its twin is still live and how to reach it.

**What**

When a session is detected as a copy of another that is still live, a one-time note is queued for the model explaining that history up to the fork point is shared, that the two have since diverged, and how to find and message the sibling. Requires the `tengu_harbor_kite` gate (fallback false) or `CLAUDE_CODE_HARBOR_KITE`, and is skipped on Windows.

**Details**

- Suppresses itself if the note is already in the conversation or already queued.
- Reported as `fork_source_note`, with a `parent_gone` reason when the original session cannot be resolved.

**Evidence**

`This session began as a fork (copy) of another session that is still running`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_HARBOR_KITE`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Forked sessions can be told their original is still running

A forked session can be told its original is still running and how to message it.

**What**

A new note injected into the conversation tells the model that this session is a copy of another session that is still running, when the copy was made, and how to find and message the original. It is off by default: the path is disabled on Windows and otherwise falls back to `tengu_harbor_kite` being false, so it only runs if remote config or `CLAUDE_CODE_HARBOR_KITE` turns it on.

**Details**

- The note is wrapped in `<fork-source>` tags in the prompt.
- Queued and pending prompts are checked first so the note is not added twice.
- Logs `fork_source_note`, with a `parent_gone` outcome when the original session can no longer be found.

**Evidence**

`This session began as a fork (copy) of another session that is still running:`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_HARBOR_KITE`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Memory index built by scanning files rather than read from disk

A memory index can be generated by scanning recent files rather than read from a stored index.

**What**

New code builds a memory index at load time by walking the memory directory, taking the most recently modified files and reading each one's `name` and `description` header fields, falling back to the filename and first heading. The result is rendered as markdown and truncated at a byte budget. It runs only for stores that ask for a generated index; stores without that keep using an index file on disk.

**Details**

- The walk skips dotfiles and is capped on depth, number of files and per-file size.
- Emits `tengu_memdir_index_assembled` with scan counts, how many files needed the filename fallback, whether output was truncated, and whether a disk index was superseded.
- The consolidation prompt has a matching variant that drops the "update the index file" step in favour of keeping each file's header fields accurate.

**Evidence**

`tengu_memdir_index_assembled`

- Area: Memory
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Newer cloud session creation endpoint for review and plan sources

Cloud sessions from review or plan sources can use a newer creation endpoint allowing --project grouping.

**What**

When a cloud session comes from an ultrareview or ultraplan source and the `tengu_woven_harbor` server flag is on, creation uses a newer endpoint, and only then are grouping sessions under a `--project` and dispatching to a Kindling agent accepted instead of refused. The flag falls back to off when the server sends no value, so by default the existing refusals still apply.

**Evidence**

`tengu_woven_harbor`

- Flag `tengu_woven_harbor`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cloud Sessions
- Names: `--project`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Assistant text is scanned for harness-shaped tags

Claude's own replies are scanned for text imitating internal control markup, and matches counted only.

**What**

Main-session assistant messages are now checked for text that looks like Claude Code's own internal control markup, including system reminders, harness envelopes and token-count, session-context, wake and role-turn shaped lines. Nothing is rewritten; the matches are only counted and reported.

**Details**

- Runs for main-session, non-error assistant messages only, with no flag involved.

**Evidence**

`tengu_assistant_output_harness_content`

- Area: Permissions
- Tier: Under the hood
- Useful: 2/5
- Signal: 4/5

### Directory sync apply starts with the working-file syncer

Startup also starts a directory sync apply step alongside the working-file syncer in normal local sessions.

**What**

Startup now also loads and starts directory sync apply for the current directory, in the same branch that starts the working-file syncer, and logs `dir_sync_apply_import_failed` if that load fails. The branch runs only when both `CLAUDE_CODE_ENVIRONMENT_KIND` and `CLAUDE_CODE_DISABLE_WORKING_SYNC` are unset, which is the normal local case.

**Evidence**

`dir_sync_apply_import_failed`

- Area: Sync
- Names: `CLAUDE_CODE_ENVIRONMENT_KIND`, `CLAUDE_CODE_DISABLE_WORKING_SYNC`
- Tier: Under the hood
- Useful: 2/5
- Signal: 4/5

### Model catalog ships per-effort cost multipliers

Bundled models carry per-effort cost multipliers, though nothing in this build reads them.

**What**

Four entries in the bundled model list now carry a cost multiplier per effort level, for example `low: 0.47, medium: 0.74, high: 1, xhigh: 2.41, max: 5.59` on one and a gentler `xhigh: 1.6, max: 1.7` on others, alongside the existing default effort of `high`. The remote config schema was extended to match, each level an optional positive number, so a server can override them. No code in this build reads the values back.

**Details**

- The data is present in every build, which points at cost-aware effort selection landing later.

**Evidence**

`effort_cost_index`

- Area: Model Catalog
- Tier: Under the hood
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Plan-mode reentry records whether artifacts come first

Re-entering plan mode records whether the setup calls for artifacts first.

**What**

The record written when re-entering plan mode now notes when the configuration calls for artifacts first, alongside the plan file and doc paths it already stored.

**Evidence**

`plan_mode_reentry`

- Area: Plan Mode
- Tier: Under the hood
- Useful: 2/5
- Signal: 4/5

### Startup remote-control setting now tracks where its value came from

Remote control startup now records where its on/off value came from, but nothing reads that yet.

**What**

Resolving whether remote control is on at startup now returns the origin of the value as well: managed policy, a command-line flag, user settings, an older global config value, a project or local setting turning it off, or nothing set. The warning that repo-scoped settings are ignored is unchanged. Nothing in the build was found reading the origin.

**Evidence**

`legacy_global_config`

- Area: Remote Control
- Names: `remoteControlAtStartup`
- Tier: Under the hood
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Artifact kill switch now applies to more artifact paths

The artifact kill switch now actually disables two more paths that ignored it.

**What**

`CLAUDE_CODE_DISABLE_ARTIFACT`, and the managed `disableArtifact` setting, are now checked first in two more artifact enablement paths. Previously those paths could still report artifacts as enabled with the kill switch set.

**Evidence**

`CLAUDE_CODE_DISABLE_ARTIFACT`

- Area: Artifacts
- Names: `CLAUDE_CODE_DISABLE_ARTIFACT`, `disableArtifact`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Prometheus metrics on the runner and orchestrator health port

The runner and orchestrator serve Prometheus metrics at /metrics on the health port.

**What**

The health listener (`--health-port`, default 8080, 0 disables) now serves Prometheus metrics at `/metrics` alongside `/healthz`. The runner exports `claude_code_self_hosted_runner_*`: capacity, active and initializing sessions, per-platform session counts, init duration, poll errors and per-session idle time. The orchestrator exports `claude_code_self_hosted_orchestrator_*`: queue depth, spawn-hook outcomes, spawn-hook and queue-wait histograms, clock skew and connector state.

**Details**

- Poll-error series are pre-seeded for all five kinds (transport, timeout, 5xx, 429, 4xx) so `rate()` queries work from the moment the process starts rather than from the first error.
- Whether the listener bound successfully is reported through telemetry named `self_hosted_healthz_listen`.

**Evidence**

`text/plain; version=0.0.4; charset=utf-8`

- Area: Self-Hosted Runner
- Names: `--health-port`, `/metrics`, `/healthz`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Runner lifecycle controls: scheduled retirement, idle release, outcome branch push

New flags retire a runner at a set time, release idle sessions, and control drain behaviour.

**What**

The runner gains a set of flags for managing how long it lives. `--retire-at` (or `SELF_HOSTED_RUNNER_RETIRE_AT`, an absolute Unix timestamp in seconds) releases active sessions, stops accepting new work and exits 0. `--release-idle-session-min` and `--startup-timeout-min` (default 15) are watchdogs, `--kill-session-after-min` waits for the current turn to finish first, and `--drain-wait-sec` sets drain time.

**Details**

- `--drain-wait-bg-tasks-sec` is a deprecated alias for `--drain-wait-sec`.
- `--push-outcome-on-release` pushes the session's tracked outcome branches to the source remote when a session ends without completing, and fetches them back if that session later resumes on a fresh worker.
- Retirement releases sessions through the same path as idle release.
- Release outcomes are reported through telemetry named `self_hosted_release_session`, with reasons including `released_false_backstop`.

**Evidence**

`SELF_HOSTED_RUNNER_RETIRE_AT`

- Area: Self-Hosted Runner
- Names: `--retire-at`, `SELF_HOSTED_RUNNER_RETIRE_AT`, `--release-idle-session-min`, `--startup-timeout-min`, `--kill-session-after-min`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Self-hosted runner hooks get the session UUID

Runner hooks now get CLAUDE_RUNNER_SESSION_UUID and CLAUDE_CODE_REMOTE_SESSION_UUID so your scripts can key on the session.

**What**

Checkout hooks, post-session hooks and the remote runner environment now export `CLAUDE_RUNNER_SESSION_UUID` and `CLAUDE_CODE_REMOTE_SESSION_UUID`, so your scripts can key on the session. Neither existed in 2.1.223.

**Details**

- The value is derived from the session id by decoding its trailing segment into a canonical UUID; if that does not decode, the variables are left unset.
- Only set on the self-hosted runner and remote session launch paths.

**Evidence**

`CLAUDE_RUNNER_SESSION_UUID`

- Area: Self-Hosted Runner
- Names: `CLAUDE_RUNNER_SESSION_UUID`, `CLAUDE_CODE_REMOTE_SESSION_UUID`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Sandbox network rules can specify a port

Sandbox allow and deny rules can now target a specific port instead of the whole host.

**What**

Domain allow and deny rules in the sandbox now parse a host pattern plus an optional port, so a rule can target a single port rather than the whole host.

**Details**

- Matching fails closed on both sides: a port-qualified rule matches a request with an unknown port when denying, and does not match when allowing.
- Otherwise the port must match exactly.
- Deny rules still come from `sandbox.network.deniedDomains` plus WebFetch-style `domain:` permission rules.

**Evidence**

`sandbox.network.allowManagedDomainsOnly is set and `

- Area: Sandbox
- Tier: Use it now
- Useful: 4/5
- Signal: 2/5

### plugin eval: report publishing flags reworked

Plugin eval publishes its HTML report by default; --publish-report now means require publishing.

**What**

`claude plugin eval` now publishes the HTML report by default when your account supports it. `--publish-report` changed meaning to "require publishing", and explains why if it is unavailable; `--no-publish` keeps the report local. Passing both is now an error. `--report` now means the report replaces the results directory, implying a default location.

**Details**

- Results from the LLM judge on long inputs now append an advisory suggesting a regex grader instead.

**Evidence**

`Error: --no-publish and --publish-report are contradictory — pass at most one.`

- Area: Plugin Eval
- Names: `--publish-report`, `--no-publish`, `--report`, `claude plugin eval`
- Tier: Use it now
- Useful: 4/5
- Signal: 2/5

### sandbox.ignoreViolations can now be scoped to specific commands

You can silence sandbox violations for one command instead of all of them.

**What**

Violation suppression is no longer all-or-nothing. A `"*"` key still applies to every command, and other keys are matched against the command text so you can silence violations for one command only.

**Details**

- Recorded violations now carry the command, its encoded form and a timestamp.
- The command text is recovered from a bounded cache of encoded commands, falling back to a decode that strips control characters.

**Evidence**

`ignoreViolations`

- Area: Sandbox
- Tier: Use it now
- Useful: 4/5
- Signal: 2/5

### Artifact tool now explains exactly which credential is blocking it

The artifact tool now names the exact credential blocking you, with a fix for each.

**What**

When you are not logged in, both the input check and the tool call stop immediately and name the credential source that is standing in the way: the `ANTHROPIC_API_KEY` environment variable, the `apiKeyHelper` setting, a token file descriptor, or a Unix socket, each with its own fix.

**Details**

- for the environment variable the advice is to unset it, or run `/logout` and decline the API key approval before logging back in

**Evidence**

`Unset the ANTHROPIC_API_KEY environment variable, or claude /logout then say "No" to the API key approval before login.`

- Area: Artifacts
- Names: `ANTHROPIC_API_KEY`, `apiKeyHelper`
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### Clearer denial when a tool needs approval but no prompt can be shown

When no prompt can be shown, denials now tell the model not to fake success.

**What**

In background and async contexts where a permission prompt cannot be displayed, tools requiring approval are now denied with an explicit instruction not to claim success or retry, replacing the generic "Permission to use X has been denied" wording. A structured decision reason is recorded alongside it.

**Evidence**

`Permission for this tool use was denied: it requires interactive approval, and permission prompts are not available in this session.`

- Area: Permissions
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### Clearer message when a tool needs approval and no prompt can be shown

In headless runs, denials now tell the model approval is impossible and not to retry.

**What**

In headless runs and async agents, a denial no longer just echoes the request back. The model is now told the action requires interactive approval, that permission prompts are not available in this session, and explicitly not to claim success or retry, with the original request appended.

**Evidence**

`it requires interactive approval, and permission prompts are not available in this session`

- Area: Permissions
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### Linux sandbox violations show the real command

Linux sandbox violations now show the real command, so ignoreViolations patterns match what you wrote.

**What**

Records of blocked syscalls in the Linux sandbox now carry the actual command text rather than an opaque internal id, using the same lookup macOS already used. This also means `ignoreViolations` patterns match against the command you wrote.

**Evidence**

`resolveCommandText`

- Area: Sandbox
- Names: `ignoreViolations`
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### Login errors for artifacts say which credential is in the way

Artifact login errors now name which credential is blocking you and how to fix it.

**What**

The single "needs claude.ai login" message split into three: sign in to a console account, a remote session whose launching machine is not signed in, and a session running on a credential injected by its host environment that takes precedence and cannot be changed from inside the session.

**Evidence**

`but this session authenticates with a credential injected by its host environment, which takes precedence and cannot be changed here`

- Area: Artifacts
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### Sharing a transcript retries with smaller payloads instead of failing

Sharing an oversized transcript now retries with trimmed versions instead of just failing.

**What**

When a transcript is too large to share, Claude Code now tries progressively stripped versions rather than giving up. It walks a ladder of candidates (full, without the last API request, raw transcript lines only, metadata only), size-checks each before sending, and posts the first that fits.

**Details**

- A successful reduced upload records "payload_stripped" so it's visible that content was trimmed.
- Size failures and `RangeError` during serialisation fall through to the next variant; the precheck reports `payload_too_large_precheck`.
- Blocked outcomes (essential-traffic-only mode, data residency, auth) still fail immediately with their own reason codes and are not retried.

**Evidence**

`payload_too_large_precheck`

- Area: Sessions
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### Two session-restore options are wired up but do nothing yet

Session restore passes budget-preserving options that the receiving code currently ignores.

**What**

Restoring a session after a fork or a pre-clear rewind now passes an option to preserve the live budget, and session restore spreads in an attention budget. Neither takes effect: the receiving function ignores that parameter, and the attention budget variable is initialised to null and never assigned.

**Evidence**

`preserveLiveBudget`

- Area: Session Restore
- Tier: Not switched on
- Useful: 1/5
- Signal: 4/5
- Present in the build but not switched on

### Storage backend plumbed through several call sites but not switched on

Several config and session lookups can accept a new storage backend, but none supply one yet.

**What**

Gateway model discovery, the computer-use lock, session-store loading of `~/.claude.json` and user settings, the settings-key presence probe and session transcript lookup all gained an optional new storage backend parameter. When none is supplied, the existing filesystem behaviour runs unchanged, and the computer-use lock call sites in this build supply none.

**Details**

- Uses namespaced keys such as a "gateway-models" cache key and a "computer-use-lock" state key.
- The lock has distinct error paths: an already-exists result means the lock is held, an invalid-argument result is thrown.
- Whether any given call site uses the new backend depends on what the caller passes, which varies across the build.

**Evidence**

`computer-use lock v5 create failed`

- Area: Internals
- Tier: Under the hood
- Useful: 1/5
- Signal: 4/5
- Present in the build but not switched on

### `self-hosted-runner decode-token` inspects a session token, verifying it by default

decode-token prints a session token's claims, verifying signature and expiry by default.

**What**

Decodes a session access token and prints its claims, reading it from an argument, from `$CLAUDE_CODE_SESSION_ACCESS_TOKEN`, or from stdin up to 16 KiB. Signature verification against the published key set and the expiry/not-before check with 60 seconds of clock skew are both on by default.

**Details**

- `--no-verify` skips the signature check, `--no-check-expiry` skips the time check, `--header` prints the token header instead of the claims.
- `--verify` still parses but does nothing, kept so older wrapper scripts do not break.
- Only ES256 and RS256 signatures are accepted.

**Usage**

`claude self-hosted-runner decode-token --header` **Evidence** `decode-token: signature verification FAILED`

- Area: Self-Hosted Runner
- Names: `self-hosted-runner decode-token`, `CLAUDE_CODE_SESSION_ACCESS_TOKEN`, `--header`, `--no-check-expiry`, `--no-verify`
- Tier: Use it now
- Useful: 2/5
- Signal: 3/5

### Self-hosted runner installs a code-sign shim

The runner writes a code-sign helper script, using CLAUDE_RUNNER_CLAUDE_BIN if you set it.

**What**

The self-hosted runner now writes an executable `.runner/code-sign` script that invokes `self-hosted-runner code-sign`. It uses the binary named by `CLAUDE_RUNNER_CLAUDE_BIN` if set, otherwise a built-in path. Neither the script nor that environment variable existed before.

**Evidence**

`exec "$BIN" self-hosted-runner code-sign "$@"`

- Area: Self-Hosted Runner
- Names: `CLAUDE_RUNNER_CLAUDE_BIN`, `self-hosted-runner code-sign`
- Tier: Use it now
- Useful: 2/5
- Signal: 3/5

### Two environment knobs for runner git fetches

Two env vars change how runner git fetches handle retries and stalled delta resolution.

**What**

On the runner and bring-your-own-cloud execution path, `CLAUDE_RUNNER_FAIL_FAST_FETCH` makes a git fetch fail immediately instead of retrying, and `CCR_DELTA_RESET` makes the fetch record HEAD beforehand and treat the "Resolving deltas" 100% progress line as a point that resets the stall timer. Both are read as plain booleans from the environment.

**Evidence**

`CLAUDE_RUNNER_FAIL_FAST_FETCH`

- Area: Self-Hosted Runner
- Names: `CLAUDE_RUNNER_FAIL_FAST_FETCH`, `CCR_DELTA_RESET`
- Tier: Use it now
- Useful: 2/5
- Signal: 3/5

### `claude eval --interactive` is documented

`claude eval --interactive` now appears in help; `--interview` remains a hidden alias.

**What**

The flag now appears in help output with text explaining that it runs the authoring interview, that this is already the default in a terminal, and that it needs an interactive terminal. `--interview` stays as a hidden alias.

**Evidence**

`Run the authoring interview (already the default in a terminal); requires an interactive terminal`

- Area: Plugin Eval
- Names: `--interactive`, `--interview`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Compact summaries are flagged as synthetic in SDK output

Compaction summaries are flagged synthetic in SDK and stream-json output so you can filter them.

**What**

Messages produced by compaction are now marked synthetic in SDK and stream-json output, so consumers can filter them out alongside meta and transcript-only messages.

**Details**

- The serialiser now calls a shared helper instead of computing the flag inline from meta and transcript-only checks.
- A local-command result that trims to an empty string after its stdout and stderr wrappers are stripped now returns nothing instead of emitting an empty assistant message.

**Evidence**

`isCompactSummary`

- Area: SDK Output
- Names: `stream-json`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Plugins can be installed from a zip archive

Marketplaces can offer plugins as zip downloads with an optional sha256 pin that doubles as the version.

**What**

A marketplace entry can now point at a plugin as an `archive`: an HTTPS URL for a zip file, with an optional `sha256` digest. The digest is checked on every download and also serves as the plugin's version identity, so a changed archive is a changed version. This source type is always available, not gated.

**Details**

- If the zip contains a single wrapping directory, it is stripped so the plugin's files land at the top level.
- Archives must be served over HTTPS.

**Evidence**

`HTTPS URL of a zip archive containing the plugin.`

- Area: Plugins
- Names: `archive`, `sha256`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Plugins can be installed from a zip archive over HTTPS

Install plugins from an HTTPS zip, with a sha256 pin checked on every download.

**What**

Marketplaces can now offer a plugin as a downloadable zip. A new source kind `archive` takes an HTTPS zip URL plus an optional `sha256` pin; when the pin is set every download is checked against it and the install is refused on mismatch, and the digest also stands in as the plugin's version when none is declared.

**Details**

- Downloads are hardened against server-side request forgery: HTTPS only, loopback, link-local and cloud-metadata hosts refused, at most 5 redirects, a 256 MB size cap, a 120 second timeout, and a fixed User-Agent of `Claude-Code-Plugin-Manager`.
- Headers configured on the enclosing marketplace url-source are only reused when the origin matches.
- Installed plugin records gained a `contentSha256` field, and the temp directory used during install has an "archive" naming case.

**Evidence**

`plugin archive URL policy rejected`

- Area: Plugins
- Names: `archive`, `sha256`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Plugins can be installed from an HTTPS zip archive

Zip plugin installs strip a single wrapping directory so the plugin lands at top level.

**What**

A marketplace entry can now point at a zip file over HTTPS instead of a git repo or npm package, with an optional sha256 digest that is checked on every download. When no version is declared, that digest is what identifies the version. If the zip contains a single wrapping directory, it is stripped so the plugin lands at the top level.

**Details**

- The URL is checked against a new guard that rejects loopback, link-local, IPv4-mapped IPv6, unique-local and cloud-metadata hosts, so a marketplace entry cannot make Claude Code fetch from inside your network.
- Authentication headers are inherited from the enclosing url-sourced marketplace only when the origins match.
- The error message for marketplaces configured in settings now lists this type alongside github, git-subdir, npm and url.

**Evidence**

`Archive URLs must use https:// and must not point at a loopback, link-local, or cloud-metadata host`

- Area: Plugins
- Names: `sha256`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Exiting no longer races history writes

Quitting right after a turn no longer loses your last history entry.

**What**

Both the periodic history flush and the flush on exit now wait for every pending write to finish before returning, so quitting immediately after a turn cannot drop the last entry.

**Evidence**

`history_save_exit_flush_failed`

- Area: Sessions
- Tier: You'll notice
- Useful: 4/5
- Signal: 1/5

### History search puts the cursor on the text you searched for

After a reverse history search, the cursor lands on the text you searched for.

**What**

After accepting a reverse history search, the cursor now lands on the last case-insensitive occurrence of your search term in the recalled line, instead of a fixed position. If the term is not found in the line, the cursor goes to the end.

**Details**

- Leaving the search without accepting now restores the input to its pre-search state.

**Evidence**

`history_search_accept`

- Area: Input
- Tier: You'll notice
- Useful: 4/5
- Signal: 1/5

### Prompt history remembers pasted content and how you submitted

Recalling an old prompt now restores its pasted attachments and remembers how you submitted it.

**What**

Recalling an earlier prompt from history now restores its pasted attachments, not just the visible text, and each entry records the submit mode it was sent with, such as bash mode. Setting `CLAUDE_CODE_SKIP_PROMPT_HISTORY` still turns recording off entirely.

**Evidence**

`submitMode`

- Area: Prompt History
- Names: `CLAUDE_CODE_SKIP_PROMPT_HISTORY`
- Tier: You'll notice
- Useful: 4/5
- Signal: 1/5

### Sandbox network denials now tell you why

Blocked network calls in the sandbox now tell you the host, port and reason.

**What**

When the sandbox blocks an outbound connection, it records a violation naming the host, the port and a reason instead of silently failing. Reasons include sandbox policy unavailable, malformed host, host is on the deny list, host is not on the allow list, user denied, and permission prompt failed.

**Details**

- Blocked HTTP requests get their own violation line with the URL query string redacted.
- A per-domain reason override can be supplied through `deniedDomainReasons`.
- Domain rules can now carry a `:port` suffix, matched against the port of the connection.

**Evidence**

`host is not on the allow list`

- Area: Sandbox
- Tier: You'll notice
- Useful: 4/5
- Signal: 1/5

### Eval-authoring guidance had two wrong field names

Plugin eval guidance now names the real fields, suite.plugins and costUsd.

**What**

The guidance for writing plugin evals now names the fields that actually exist: `suite.plugins` for checking the plugin loaded, and `costUsd` for the cost figure in `aggregate-result.json`. It previously said `plugins` and `cost_usd`.

**Evidence**

`The pilot's top-level \`costUsd\` in \`aggregate-result.json\``

- Area: Plugins
- Names: `suite.plugins`, `costUsd`, `aggregate-result.json`
- Tier: Use it now
- Useful: 2/5
- Signal: 2/5

## New Features

### Org-anchored consent for remote managed settings

Approvals for risky org-managed settings are now remembered per organization, so you get re-prompted far less.

**What**

When your organization ships managed settings, consent to the dangerous ones can now be recorded against the org rather than only against the local payload you saw. Consent is stored in a new `remote-settings-consent.json`, capped at 20 organizations and rewritten at most once a day per org, and you are not re-prompted while the org record's hash of the dangerous settings still matches.

**Details**

- Requests now carry the organization and account ids so the response can identify which org the consent belongs to.
- Without an org record, the previous behaviour applies: the locally consented payload is compared directly.
- Consent is not recorded if the signed-in identity changed part-way through the fetch.
- A login or logout during a fetch discards that fetch's result, reported as `reset_during_fetch`.

**Evidence**

`Remote settings: Cache was reset (login/logout) during this fetch; discarding its result`, `remote-settings-consent.json`

- Area: Managed Settings
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Cloud seeding tells you when there are too many untracked files

If you have too many untracked files, cloud seeding refuses and tells you which budget you blew.

**What**

When the untracked files at your git root exceed the seeding budget, the upload is refused and you are told why instead of quietly starting from the git bundle alone. There are two messages: one counting files against the 192-file budget, one counting bytes against the 100 MB transfer budget, and the byte one suggests adding a .gitignore entry.

**Details**

- Same gating as the seeding feature itself: the `tengu_violin_wood` gate plus `remoteFileMode` set to `container_sync`.
- The fallback is bundle-only seeding, and the existing seed telemetry records a `too_many_untracked` outcome.

**Evidence**

`untracked files exceed the v0 seed budget of`, `of untracked files exceed the v0 seed transfer budget of`

- Flag `tengu_violin_wood`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cloud Sessions
- Names: `.gitignore`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Skills can be loaded from a mounted GitHub repository

Skills can load from a mounted repo's .claude/skills, in cloud sandboxes only.

**What**

A new docs section describes loading skills from a mounted repository's root `.claude/skills` directory. Skills are discovered once per session from the code as it stood when the session started, and this works in cloud sandboxes only, not self-hosted.

**Details**

- Carries an explicit warning that repository skills are agent instructions running inside your trust boundary, so anyone who can commit to the repo can change agent behaviour.

**Evidence**

`Skills from a GitHub repository`

- Area: Skills
- Names: `.claude/skills`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Messages relayed between your own sessions are now labelled as such

Messages relayed from your other sessions are now tagged as peer sends rather than looking like scheduled triggers.

**What**

A message delivered to one session from another of your sessions, sent by the model through a coordinator SendMessage, now carries `subkind: "peer-send-message"` on its origin instead of being indistinguishable from a scheduled trigger. Anything reading `origin.subkind` should expect this additional value alongside `scheduled-trigger`.

**Details**

- These deliveries keep the same prompt authority as task notifications but are now distinguishable on the wire.
- The classifier stamps the new value when an inbound message arrives over the coordinator path, so the receive-side `crossSessionInbound` setting can apply to it.

**Evidence**

`so the receive-side crossSessionInbound setting can apply to it`

- Area: Cross-Session Messaging
- Tier: Under the hood
- Useful: 3/5
- Signal: 3/5

### Remote-control autostart decision is resolved in one place

One resolver now decides whether Remote Control starts and in which mode, weighing flags, settings and env vars.

**What**

Whether the remote-control bridge starts, and whether it runs full or outbound-only, is now decided by a single resolver that weighs the `CLAUDE_CODE_REMOTE` environment variable, the `--remote-control` flag, the explicit setting, a built-in default, and the `CLAUDE_BRIDGE_REATTACH_SESSION` and `CLAUDE_BRIDGE_REATTACH_OUTBOUND_ONLY` variables.

**Details**

- The outcome is reported as `tengu_rc_autostart_resolved`, carrying the explicit setting and where it came from, the default and its source, and the mode that won.

**Evidence**

`tengu_rc_autostart_resolved`

- Area: Remote Control
- Names: `CLAUDE_CODE_REMOTE`, `--remote-control`, `CLAUDE_BRIDGE_REATTACH_SESSION`, `CLAUDE_BRIDGE_REATTACH_OUTBOUND_ONLY`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Published plans are rendered from a plan template and tracked

Published plans now use their own page template and get their own tracked addresses.

**What**

Publishing a plan now requests a `plan` template from the HTML publisher and records the resulting page under a `plan` storage namespace, in the same registry used for prototypes. Existing plan pages are enumerated so a new plan does not reuse an existing address.

**Evidence**

`namespace: "plan"`

- Area: Artifacts
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Workshop file writes are counted in the turn summary

Workshop file writes are counted separately per turn and shown in the status display.

**What**

Writes to workshop files are now tracked separately, with a per-turn count of writes and lines added and removed, and the status display shows the write count.

**Details**

- Tool classification gains a workshop-write flag; the turn summary accumulates write count plus lines added and removed.
- Edits are keyed by tool use id so a rewind subtracts exactly the right amounts, and counts merge when summaries are combined.
- The status component reads all three values and includes the write count when deciding whether there is anything to display.

**Evidence**

`isWorkshopWrite`

- Area: Artifacts
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Peer messages carry the sender's permission mode

Messages between sessions now carry whether the sender was bypassing permissions or prompting.

**What**

A message sent between sessions now includes whether the sender was running in bypass or prompting permission mode, so the receiving side can see how permissive the sender was.

**Details**

- Rendered in the message envelope as `from-mode="..."`, limited to the two values `bypass` and `prompting`.
- Parsed back out on receipt and preserved when a message is re-encoded.
- Both the local socket peer client and the bridge sender accept the mode as their final argument.

**Evidence**

`["bypass", "prompting"]`

- Area: Cross-Session Messaging
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Local runner spawn writes into ./runner-setup and checks 127.0.0.1:8080

The setup wizard spawns a detached runner into ./runner-setup and warns it outlives your session.

**What**

The setup wizard's spawn tool starts a detached self-hosted runner process, defaulting its working directory to `./runner-setup/workspace`, its log to `./runner-setup/runner.log` and its pid file to `./runner-setup/runner.pid`. Companion tools read `http://127.0.0.1:<port>/healthz` and `/metrics` on port 8080 by default (0 disables the listener). The permission prompt warns that the runner will keep running after this session ends.

**Details**

- The spawn passes `--environment-secret-file`, `--capacity`, `--base-dir`, `--api-url`, `--health-port` and `--log-file`.
- The metrics tool reads only gauges prefixed `claude_code_self_hosted_runner_`, skipping histogram buckets, per-session idle seconds and poll error totals.
- Tail-log reads the trailing bytes of the log and redacts secrets before showing them.
- These tools are only available inside the setup and doctor wizards.

**Evidence**

`claude_code_self_hosted_runner_`

- Area: Self-Hosted Runner
- Names: `--api-url`, `--base-dir`, `--environment`, `--environment-secret-file`, `--health-port`
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Sandbox violations record the command that caused the blocked request

Blocked network requests are now recorded with the command that made them, not just dropped.

**What**

A network request blocked by the sandbox is now recorded as a violation with its method, sanitized URL, reason and the command that made it, instead of only being dropped. Filesystem violations on Linux and macOS also gained command text.

**Details**

- Applies only while the sandbox proxy and platform monitors are running.

**Evidence**

`deny http-request `

- Area: Sandbox
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Two warnings when a Bedrock region prefix cannot be confirmed

Bedrock now warns when it can't confirm a region prefix, or when models resolve elsewhere.

**What**

If inference-profile discovery is unavailable, setting a region prefix now warns that it is being applied without an availability check. If discovery works but some models resolve to a different region, a second warning names how many and states that the prefix is a preference, not a residency guarantee.

**Details**

- Warning text is assembled at runtime around the fixed fragment "is being applied without an availability check (inference-profile discovery is unavailable)."
- Both warnings only appear when `ANTHROPIC_BEDROCK_REGION_PREFIX` is set.

**Evidence**

`This is a preference, not a residency guarantee.`

- Area: Bedrock
- Names: `ANTHROPIC_BEDROCK_REGION_PREFIX`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Consent to dangerous remote settings is remembered per organization

Approving risky remote settings is now saved to a file so you aren't asked again.

**What**

When you approve remote settings that can run shell commands, set environment variables, install hooks or write CLAUDE.md, that approval is now written to a `remote-settings-consent.json` file so you are not asked again for the same content.

**Details**

- Records are keyed by organization uuid and store the account uuid, a hash of just the dangerous portion of the settings, and a timestamp. If that portion changes, the hash no longer matches and consent is requested again.
- The file is written with 0600 permissions and the number of records is capped.
- If the file is unreadable or was written by a newer version, it is left alone rather than overwritten.

**Evidence**

`Remote settings: Consent records file is `

- Area: Managed Settings
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Cowork warns when your default environment isn't in the org's list

You get a logged warning if your default self-hosted environment isn't in your org's list.

**What**

If you have a default self-hosted environment configured and start a session without picking one explicitly, Claude Code fetches your org's environment list in the background and logs a warning if the configured id isn't there. The id is still sent, because the server validates it when the session is created.

**Details**

- Warning text ends with "is not in the org's environment list".
- A pool fetch that fails outright is logged separately rather than blocking the session.

**Evidence**

`is not in the org's environment list`

- Area: Cloud Sessions
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Peer address shown in the session info panel

The session info panel now shows your peer socket address when cross-session messaging is on.

**What**

The session details list gains a "Peer address" row showing the messaging socket as `uds:<path>`. It appears only when cross-session messaging is enabled and `CLAUDE_CODE_MESSAGING_SOCKET` is set; this slot was previously always empty.

**Details**

- Cross-session messaging is gated by `tengu_harbor_kite` or the `CLAUDE_CODE_HARBOR_KITE` environment variable.

**Evidence**

`Peer address`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_MESSAGING_SOCKET`, `CLAUDE_CODE_HARBOR_KITE`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Invoking the prototype skill directly starts prototype mode

Invoking the prototype skill yourself now actually starts prototype mode.

**What**

Calling the prototype skill yourself now marks prototype mode as started, as invoking it any other way already did. This does not happen when the skill is only being preloaded or when the model scheduled the invocation itself.

**Evidence**

`prototype_started`

- Area: Elsewhere
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Sandboxed commands are tagged with an attribution id

Sandboxed commands now carry an id into the sandbox config so activity traces back to its command.

**What**

Commands run in the sandbox now pass an identifier through to the sandbox wrapper, where it appears as a command id in the macOS and Linux sandbox configuration, so sandbox activity can be traced back to the command that caused it.

**Details**

- A new sandbox attribution id option is threaded through the bash and PowerShell exec path into both sandbox wrapper entry points.
- The macOS sandbox log watcher was reworked to decode its command marker through a caller-supplied resolver, defaulting to identity, instead of a pre-built ignore map.

**Evidence**

`sandboxAttributionId`

- Area: Sandbox
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Plugin eval can validate and report its own JSON output

Plugin eval JSON output is validated before printing, and suppressed with a stderr warning if invalid.

**What**

`claude plugin eval --json` now checks its result against the expected shape before printing. If validation fails the JSON is not printed at all and a warning goes to stderr instead; if it merely differs from the expected shape a warning is printed and the result is still emitted.

**Details**

- On drift, a `result_schema_drift` counter fires and the result is still printed.
- On outright validation failure, output is withheld entirely and stderr carries the explanation.
- New outcome telemetry records pass and fail counts, how many cases hit run errors, and separates exits caused by a cost ceiling from exits caused by a case that failed to load.

**Evidence**

`warning: --json result withheld because it failed schema validation`

- Area: Plugin Eval
- Names: `claude plugin eval`, `--json`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Visible failure state when sharing a transcript doesn't go through

A failed transcript share now says so in the dialog and points you at /feedback.

**What**

A failed transcript share now shows in the dialog rather than only in telemetry. You get the line "Couldn't share the transcript." and, unless `DISABLE_FEEDBACK_COMMAND` or `DISABLE_BUG_COMMAND` is set, a dimmed hint "You can share details with /feedback instead." The dialog auto-closes after the usual delay.

**Details**

- Previously a failure produced only a `transcript_share_failed` telemetry event and no on-screen change.
- Implemented as a new `share_failed` UI state with its own render branch.

**Evidence**

`You can share details with /feedback instead.`, `Couldn't share the transcript.`

- Area: Transcript Sharing
- Names: `/feedback`, `DISABLE_FEEDBACK_COMMAND`, `DISABLE_BUG_COMMAND`
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Invalid `extraKnownMarketplaces` entries are dropped with a warning

Bad marketplace entries in your settings are now removed and reported as warnings instead of silently accepted.

**What**

Bad marketplace entries in your settings are now reported instead of quietly accepted. A non-object `extraKnownMarketplaces` value is deleted outright, and each entry is validated, with a mismatch between the map key and the marketplace name it declares called out by name. Offending entries are removed and surfaced as warnings against the settings file and path.

**Details**

- The warning text is "Invalid marketplace entry was ignored: " followed by the reason.
- Entries with an unrecognised source kind, or that carry plugins, are skipped rather than dropped.

**Evidence**

`Invalid marketplace entry was ignored: `

- Area: Plugins
- Names: `extraKnownMarketplaces`
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Sandbox network denials name the command that caused them

Blocked outbound connections now name the shell command responsible.

**What**

When the sandbox blocks an outbound connection, the violation record now includes the shell command responsible, so you can tell which command tried to reach the network rather than only seeing that something did.

**Details**

- The proxy's auth check returns a result object carrying an encoded command instead of a plain yes/no.
- The proxy Basic credential username may now be `srt` or `srt.<suffix>`; the SOCKS path previously required an exact `srt` match. The suffix decodes to the command identity, set from `SRT_ENCODED_CMD` when a command id is supplied.
- The decoded command is passed to the allowlist filter and to the denial callback, and violation records carry both the encoded form and a resolved command string.
- Requests failing the auth check still get the existing 407 and 403 responses.

**Evidence**

`SRT_ENCODED_CMD`, `encodedCommand`

- Area: Sandbox
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Remote Control tells you when a credential expired instead of just dropping

An expired Remote Control credential now closes with a clear message telling you to run /login.

**What**

Remote Control now separates auth failures from other disconnects. When the worker token expires or is rejected, the session closes with a dedicated code and the message points you at `/login`, instead of the previous generic re-authenticate wording.

**Details**

- New close code `4094`, "worker credential expired or rejected (code 4094)", used for `token_expired` and `auth_exhausted`.
- `epoch_conflict`, `epoch_stale` and `session_not_found` keep close code `4090` and recover through the poll loop.
- The cause-based mapping applies only when the transport is built with `causeTypedCloseCodes`; both v2 bridge call sites pass it as true, so it is live.
- The 404-exhaustion path now names its cause explicitly as `session_not_found`.

**Evidence**

`worker credential expired or rejected (code 4094)`

- Area: Remote Control
- Names: `/login`
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### MCP OAuth failures name the specific issuer mismatch

MCP OAuth failures now tell you it was an issuer mismatch instead of a generic error.

**What**

When an MCP server's OAuth flow fails because the server echoed back the wrong issuer, the failure is now reported with a specific reason rather than a generic one, making misconfigured servers easier to identify.

**Details**

- Three new failure reasons: `issuer_echo_denied`, `issuer_echo_mismatch` and `issuer_response_mismatch`, each raised from its own error class and checked ahead of the existing reasons.
- One authorization-server metadata discovery path now skips the library's built-in issuer check and validates the issuer itself afterwards, throwing the existing "AS metadata discovery failed: issuer mismatch" error.
- MCP connect telemetry gains a sibling-probe field, computed only in `auto` negotiation mode, on both the success event and `tengu_mcp_server_connection_failed`.

**Evidence**

`issuer_echo_denied`

- Area: MCP
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Prototype runs and publishes now report their own telemetry

Prototype runs and publishes now emit their own telemetry events with the artifact slug.

**What**

Claude Code emits two new events around prototype artifacts: one when a prototype run starts, and one when a prototype is published, carrying the artifact's slug and whether it was the first publish.

**Details**

- The publish event records `artifact_slug` and `is_first_publish`.
- Neither event name exists in the previous build.
- The functions that emit these events are new; which artifact flow calls them was not traced, so it is not clear from the build itself which user actions trigger them.

**Evidence**

`prototype_publish`

- Area: Elsewhere
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Remote Control errors are typed as auth or terminal

Remote Control failures are labelled auth or terminal, and auth ones point you at /login.

**What**

Bridge failures now record whether they were an auth problem or a terminal one, and auth failures show the `/login` hint. The token-refresh failure message changed from "re-authenticate, then re-enable Remote Control" to text ending "run /login to re-authenticate".

**Details**

- App state gains an error-kind field alongside the existing error, set to `"auth"` or the default `"terminal"`, and cleared wherever the error is cleared.

**Evidence**

`run /login to re-authenticate`

- Area: Remote Control
- Names: `/login`
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

## Improvements

### Clearer refusal when a name matches a device-gated remote session

Messaging a reply-only remote session now explains why, instead of a generic not-found.

**What**

Sending a message to a name that only matches a reply-only remote session now explains that specifically, rather than returning a generic not-found. The text states that the Claude Code Remote send_message connector cannot reach these sessions and that the "untrusted device" error it produces is misleading.

**Details**

- When the peer list itself fails to load, the fetch is now marked as failed so the caller hedges instead of flatly refusing.
- Only reachable when cross-session messaging is active in the session.

**Evidence**

`it cannot reach these device-gated sessions, and its "untrusted device" error is misleading`

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Cross-session sends take plain text without a summary

Messages to another session accept plain text without a summary; structured objects are rejected.

**What**

Messages addressed to another session skip the usual validation: plain text is accepted without the normally-required summary, and structured object messages are rejected outright with a note that only plain text can cross sessions.

**Details**

- Applies to recipients addressed through the two cross-session transports; ordinary in-session recipients are unaffected.
- Requires cross-session messaging to be available in the session.

**Evidence**

`structured messages cannot be sent cross-session — only plain text`

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Rate limit status is pushed to the remote client

Your rate limit status now reaches a connected remote client right away.

**What**

Rate-limit state now reaches a connected remote client immediately. On bridge connect the current state is written to the peer, and later rate-limit events are written to the bridge channel as well as the local stream. Forwarding failures are logged, not thrown.

**Details**

- The connect-time send is skipped if a rejection window has already elapsed, and is recorded as `bridge_rate_limit_forward`.
- Both paths build the event through one shared helper.
- The connect-time send is also guarded by an internal predicate whose source of truth sits outside this code path.

**Evidence**

`bridge_rate_limit_forward`, `rate_limit_event forward failed`

- Area: Sessions
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Forking a session tells the model the fork is separate

After forking, Claude is told the fork is a separate session reachable by name.

**What**

After forking, Claude is now told that the fork runs as its own session, that its work is invisible to the current conversation, and that it can be reached by name through the session roster. This branch previously attached nothing.

**Details**

- The message is produced only when cross-session messaging applies; otherwise nothing is attached.

**Evidence**

`The fork runs as its own separate session`

- Area: Sessions
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Peer lookups say when other machines' sessions could not be checked

If peer listing failed, the not-found message now says other machines could not be checked.

**What**

When you name a peer session that is not found, the refusal now adds a note if the listing of sessions reachable over the remote bridge timed out or failed, explaining those sessions can only be replied to from this machine and cannot be addressed by name.

**Details**

- Backed by a marker recording why the listing failed, with values `timeout` and `fetch_failed`.

**Evidence**

`and files can't be sent to it by name from this machine.`

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Failed peer listings are reported rather than looking empty

A failed peer listing is now reported as a failure rather than an empty roster.

**What**

When Claude Code cannot list the other sessions it can reach, the failure is now recorded as a failure instead of coming back as an empty roster. This matters for the ListAgents tool, which only records what the model saw when the listing actually succeeded.

**Details**

- Failure is set when auth preparation throws, when the list request throws, or when the server returns a non-200 status.
- Logs `[bridge:peers] auth prep failed:`, `[bridge:peers] list request failed:` and `[bridge:peers] list failed`.
- The population fetch carries the failure flag through and logs a "FAILED, not recordable" marker.

**Evidence**

`[bridge:peers] auth prep failed:`

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Messages from peer sessions are shown unless suppressed

Activity messages from a peer session with no named sender are now shown by default.

**What**

Activity messages originating from a peer session with no identified sender are now displayed by default rather than hidden by default.

**Details**

- The display check's boolean argument now falls back to true instead of false, so a caller must explicitly pass false to hide one.

**Evidence**

`observer-activity`

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Subagents cannot call `memory_write`

Only your main conversation can write memory documents; subagents are blocked from memory_write.

**What**

The new memory write tool was added to the list of memory tools blocked for subagents and custom agents, so only the main conversation can save documents to a memory store.

**Evidence**

`["memory_list", "memory_read", "memory_write"]`

- Area: Memory
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Remote Control sessions now explain that they are reply-only

Messaging a Remote Control session now explains it is reply-only until it messages you first.

**What**

Sending a message or file to a name that matches a Remote Control session on your account no longer just fails. The tool now says such sessions are reply-only from here and can be messaged only after they message you first, and that the Remote send_message connector cannot reach these device-gated sessions. A hedged version of the message appears when the list of sessions could not be fetched.

**Details**

- The `ListAgents` tool now also advertises `ListPeers` as an alias.
- The cross-device recipient schema variant behind this sits on the remote-config flag `tengu_harbor_kite`, whose fallback here is off, or the `CLAUDE_CODE_HARBOR_KITE` environment variable.

**Evidence**

`only in reply, after it messages you first`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_HARBOR_KITE`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### SendMessage skips the auto-mode classifier

Messaging another agent skips the auto-mode classifier and no longer counts toward denials.

**What**

Sending a message to another agent no longer goes through the auto-mode permission classifier, so it avoids classifier input parsing and does not count toward denial totals.

**Evidence**

`SendMessage`

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Exported Claude archives no longer need every index file

Importing an exported Claude archive now only requires conversations.json; missing indexes are rebuilt.

**What**

Importing an exported `.zip` only requires `conversations.json` now. If the combined `projects.json` is missing, projects are read from the individual `projects/*.json` files instead, and the archive summary is rebuilt from `users.json` plus per-conversation message counts rather than being required in the file.

**Evidence**

`export zip missing required entry`

- Area: Internals
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Clearer answer when a message target is not found

An unresolved message target now says which of three reasons applied, hedging if listing timed out.

**What**

If you message a session name that does not resolve, Claude now says which of three things happened: the name matches a Remote Control session that can only reply to you from elsewhere, the name genuinely does not exist, or the list of remote sessions did not load in time, in which case it hedges rather than claiming the name is wrong.

**Evidence**

`bridge population not ready within `

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Memory listings point at each store's index document

Memory listings now show each store's index document path, and reads carry a version.

**What**

Listing memory stores now includes an `index` field giving the path of each store's index document, and reading a memory entry can return a `version` alongside the content.

**Details**

- The list tool's parameter description was updated to mention the index document path.
- `version` on read output is optional and absent when the store does not supply one.

**Evidence**

`and the path of its index document`

- Area: Memory
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Guidance on when multiagent is worth it, and a corrected docs link

Multiagent docs gained a staged guide on when extra agents are actually worth it.

**What**

The multiagent docs gained a step-by-step section with Python examples: start with a roster of just `{"type": "self"}`, move reading-heavy sub-tasks to a cheaper worker model, and only then add dedicated specialists. The overview and skill entry point now point fan-out shaped work here.

**Details**

- The URL fetched for live docs was wrong and is corrected from `multi-agent.md` to `multiagent-orchestration.md`.

**Evidence**

`https://platform.claude.com/docs/en/managed-agents/multiagent-orchestration.md`

- Area: Agent SDK Docs
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### `vault_ids` documented as create-only, and deployments can be updated

Docs say vault_ids cannot change mid-session, and deployments now have an update endpoint.

**What**

Session update docs now state that `vault_ids` is rejected on update even though the SDK exposes it as a parameter, so only `agent.tools`, `agent.mcp_servers`, title, metadata and budget can change mid-session. Deployments gain an `UpdateDeployment` endpoint and `deployments.update` SDK method.

**Details**

- A deployment can carry a `budget` that is copied onto every session it fires; it can be cleared and added back at the deployment level.

**Evidence**

`` `vault_ids` is create-only (rejected on update) ``

- Area: Agent SDK Docs
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Task budgets and session budgets are no longer conflated

Docs now warn that task_budget is advisory tokens while session budgets are hard dollar caps.

**What**

The skill's quick reference and its feature-support notes now warn that the Messages API `task_budget` is advisory and counted in tokens, while a Managed Agents session budget is a hard, dollar-denominated cap enforced by the platform.

**Details**

- The model-tier restriction (Opus 5, Fable 5, Sonnet 5, 4.8, 4.7) applies only to the Messages API form; session budgets have no tier restriction.

**Evidence**

`Not the same thing as **Managed Agents session budgets**`

- Area: Agent SDK Docs
- Names: `task_budget`
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Artifact publish prompts now name claude.ai

Publish prompts now say your file becomes a private page on claude.ai instead of an "Artifact".

**What**

The permission prompt for publishing a file no longer describes it as rendering to an "Artifact". It now says the HTML or Markdown file is published as a page on claude.ai, on Anthropic's servers, private by default, and the prompt says whether it goes to a new page or a named existing one.

**Evidence**

`Publish an HTML or Markdown file as a page on claude.ai (Anthropic's servers), private by default.`

- Area: Artifacts
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Remote transcript upload detects rewritten history and skips suppressed sessions

Remote transcript uploads track message ids, so compaction no longer loses or duplicates work.

**What**

Uploading a session transcript to the remote side no longer decides what is new by comparing a stored index against the message count, which could miss or duplicate work after a compaction. It now anchors on message ids and tracks which it has already seen, logging when it detects that history was rewritten.

**Details**

- Messages are counted as seen without being uploaded once the session binding has detached.
- Uploads of compaction pairs are skipped entirely for sessions carrying history-backfill suppression, with a log line saying so.

**Evidence**

`[persist-remote] Skipping compact-pair upload: session carries history-backfill suppression`

- Area: Sessions
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Inherited plugins no longer force a lockfile refresh

Plugins inherited from elsewhere no longer force your plugin lockfile to refresh.

**What**

Claude Code now records, for each enabled plugin, whether it came from this session's own config. Lockfile freshness checks only require a matching lock entry for those plugins, so plugins inherited from elsewhere no longer trigger a relock.

**Evidence**

`fromOwnConfig`

- Area: Plugins
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Long tool results are clamped in the memory tool output

Memory tool output is trimmed to 10 lines unless you run in verbose mode.

**What**

Memory tool results no longer flood the terminal. At most 10 lines are shown, each line is cut at 200 characters with an ellipsis, and a dimmed "+N more lines" note is appended. Running in verbose mode disables both limits.

**Details**

- Errors are unwrapped from their `<tool_use_error>` wrapper, with the first line shown in the error colour and the rest clamped the same way.
- Wired in for the memory_write tool family.

**Evidence**

`<tool_use_error>`

- Area: Memory
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Remote bridge retries once after refreshing an expired token

A remote bridge session that hits an expired token now refreshes and retries once.

**What**

A remote bridge session that gets a 401 during startup now refreshes the OAuth token and tries again, instead of failing. The refresh runs at most once per access token, and if it lands on the final attempt an extra call is made with the fresh token.

**Details**

- Reports `bridge_repl_v2_init_401_refresh` with a flag saying whether a token was actually refreshed or no refresh was available.
- Disconnect reasons "untrusted_device" and "session_stale_relogin" are now classified as auth problems rather than terminal failures, so they take the recoverable path.

**Evidence**

`bridge_repl_v2_init_401_refresh`

- Area: Sessions
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Session lookup can match a project by the directory recorded in its transcript

Sessions in truncated or same-named project folders now resolve using the recorded working directory.

**What**

Finding the right project folder for a session no longer depends only on the folder name. Claude Code reads the working directory recorded inside the transcript files and compares it, optionally ignoring case, which disambiguates project folders whose names were truncated.

**Details**

- Reads a relocated path ("relocated" / "relocatedCwd") from the end of the file, falling back to "cwd" via a fast per-line scan of the head.
- The recorded path is sanitised the same way project folder names are before comparison.
- Transcript lookup by session id now also walks worktrees.

**Evidence**

`relocatedCwd`

- Area: Sessions
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Clearer warning when writing into an unsynced memory-store copy

Writing into a stale local copy of a memory store now warns that nothing syncs it.

**What**

Writing into a directory that belongs to a project memory store now gives one of two messages. If memory stores are configured through the `CLAUDE_MEMORY_STORES` environment variable you get the existing overwrite warning; otherwise you get a new message saying the directory is a leftover local copy that nothing syncs or recalls.

**Details**

- Which message you see is decided entirely by whether `CLAUDE_MEMORY_STORES` is set.

**Evidence**

`This file's directory is a leftover local copy of a project memory store that is no longer synced.`

- Area: Memory
- Names: `CLAUDE_MEMORY_STORES`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Read-only memory stores are refused up front

Saving to a read-only memory store now fails immediately and lists stores you can write to.

**What**

Saving to a memory store that is read-only in the current session now fails immediately with the reason `read_only` and a list of the stores you can write to, instead of appearing to succeed and not persisting.

**Details**

- The system prompt describing the memory index now names the store that owns each entry and marks read-only indexes.
- Only reached when connected memory stores are in play; which flag enables those is not settled by anything in this build.

**Evidence**

`is read-only in this session; changes will not persist.`

- Area: Memory
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Reattaching to a remote session can skip replaying history

Reattaching to a remote session can skip resending history the client already has.

**What**

A reattaching bridge can now install only the live transcript writer and skip syncing the historical conversation, which avoids re-sending a transcript the remote client already has.

**Details**

- The REPL passes the suppression flag through and marks that suppression was applied; the skip is logged.

**Evidence**

`[bridge:repl] Persistence backfill suppressed — installing live writer only`

- Area: Sessions
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Sandbox credential matching ignores ports and schemes in host entries

Sandbox credential host entries written with a port or scheme now still match.

**What**

Host entries controlling which credentials get injected into a sandbox are now normalized to a bare host pattern and de-duplicated before matching, so an entry written with a port or scheme still matches, and repeated entries collapse.

**Details**

- Applies to the environment variable, AWS key-pair and masked-file credential resolution paths.
- Only affects the sandbox credential configuration path.

**Evidence**

`degradeToUnsetNames`

- Area: Sandbox
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Shell commands run from a skill preamble get distinct IDs

Commands run from a skill preamble get their own IDs, so results no longer collide.

**What**

Each command validated from a skill's preamble now gets its own tool-use ID rather than sharing the caller's, so results and permissions no longer collide between them.

**Details**

- IDs take the form parent id + `:preamble-` + index; the Bash tool finds that marker in the id and recovers the parent id from the text before it.
- The separate `:inner` suffix keeps its existing meaning.

**Evidence**

`:preamble-`

- Area: Skills
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Teammate message failures are labelled, and inbox write failures reported

Failed teammate messages now say the recipient was unreachable, including when their inbox write failed.

**What**

When SendMessage cannot find a teammate, the teammate has left, the roster has duplicate or unreadable entries, the failure is now tagged as not reachable. A new case covers the recipient's inbox write returning nothing, and tells you the message was not sent.

**Evidence**

`Failed to write to ${e}'s inbox \u2014 nothing was sent. Try again, or message the lead.`

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### A locally configured self-hosted pool always appears as a target

Your configured self-hosted pool now appears in the picker even if the server omits it.

**What**

The environment picker now shows your configured self-hosted pool even when the server's pool list does not include it, so a freshly configured pool is selectable straight away.

**Details**

- Applies to pool ids beginning `ccpool_`; the synthesized entry reports zero live runners and no creation date.

**Evidence**

`self_hosted_pool`

- Area: Self-Hosted Runners
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Blocked-by-proxy responses are detected from a second header

Proxy-blocked requests are recognised from a second header, so more networks get the proper message.

**What**

When a request is refused by a network proxy allowlist, Claude Code now recognises the block from either of two response headers, so more environments get the proper "blocked by egress policy" handling instead of a generic HTTP 403.

**Details**

- An HTTP 403 counts as blocked when `x-proxy-error` is `blocked-by-allowlist` or when the new `x-deny-reason` header is `host_not_allowed`.
- Previously only the first header was checked, and only at the GitHub comments call site; the check is now a shared helper.

**Evidence**

`host_not_allowed`

- Area: Internals
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Feedback drafts explicitly cover the model's behaviour, not just the product

The feedback tool now says it covers Claude's own behaviour, not just product bugs.

**What**

The description of the feedback tool now says it covers Claude Code the product or the model's own behaviour in the current session, so complaints about a bad answer are in scope rather than just product bugs.

**Details**

- New text: "Queue a draft feedback report about Claude Code — the product OR the model's own behavior in this session — for the user to review and send later."
- Still controlled by the `feedbackDrafts` setting: `notify` (default), `quiet`, or `off`, where `off` disables the tool entirely.

**Evidence**

`Queue a draft feedback report about Claude Code \u2014 the product OR the model's own behavior in this session \u2014 for the user to review and send later.`

- Area: Feedback
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Bash tool told that command output may not reach you

Claude is told you may not see command output, so it should summarise results.

**What**

The Bash tool description now states plainly that command output is shown to the model but not reliably to the user, which should push the model to summarise results rather than assume you saw them.

**Details**

- Added line: "- Command output is displayed to you, not reliably to the user."
- It replaces one of the fragment lists previously spread into the assembled description.

**Evidence**

`- Command output is displayed to you, not reliably to the user.`

- Area: Bash Tool
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Memory tool output no longer shows up in transcript search

Memory tool contents no longer clutter transcript search results.

**What**

The memory read and memory-list tools now report empty search text, so their contents are excluded from transcript search indexing. Read results also carry a `version` derived from the document's content.

**Evidence**

`extractSearchText`

- Area: Memory
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Published artifacts handle a third theme state

Published pages now handle an unstamped theme, so they respect your browser's light or dark setting.

**What**

The publishing prompt and the artifact-design guidance now treat the viewer as having three theme states rather than two: explicitly dark, explicitly light, and an unstamped default where only the browser's `prefers-color-scheme` decides. Generated pages are told to define a full light palette on `:root`, guard the dark media block so an explicit light choice wins, add a dark override, and set an explicit background on `body` so a transparent page does not pick up the host's colour.

**Evidence**

`guarded as \`:root:not([data-theme="light"])\``

- Area: Artifacts
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Artifact publish errors say whether retrying can help

Artifact publish errors now say whether retrying will help, based on the HTTP status.

**What**

When Claude Code cannot read back a page to verify a live edit, republish or publish, the message now branches on the HTTP status. Any 4xx other than 408 and 429 is reported as permanent with advice not to retry, 403 adds a note to retry at most once because a concurrent republish can cause it, and reads blocked by the environment's network allowlist say so.

**Evidence**

`An HTTP ${zt.status} failure is not transient, so retrying this live-edit cannot succeed \u2014 WebFetch the page to check its state.`

- Area: Artifacts
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Bedrock inference profile lookup failures are logged

Bedrock profile lookup failures are now logged instead of silently falling back to built-in models.

**What**

When listing Bedrock inference profiles throws, an error-level log now says it is falling back to the built-in model list and includes the underlying error text. Previously the failure was silent and you only saw the fallback models.

**Evidence**

`Failed to list Bedrock inference profiles, falling back to hardcoded models: `

- Area: Bedrock
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Remote environment picker marks self-hosted entries

The environment picker labels self-hosted entries and shows a dimmed live runner count.

**What**

After you set a default environment the confirmation says either "remote environment" or "self-hosted environment", and self-hosted entries in the list now show a dimmed count of live runners next to them.

**Evidence**

`alive_runner_count`

- Area: Self-Hosted Runners
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Trailing slashes in permission and sandbox path rules

Permission and sandbox path rules now strip trailing slashes consistently, including UNC paths.

**What**

Path rules for permissions and sandbox filesystem access now strip trailing separators the same way everywhere, with separate handling for Windows paths and for UNC network paths. A new internal option controls whether that cleanup is also applied to the part of a path after a glob.

**Evidence**

`evenAfterGlob`

- Area: Permissions
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Sandbox AWS credential pairs get precedence rules and scope warnings

Conflicting AWS credential pairs in the sandbox now resolve predictably and warn about risky setups.

**What**

The sandbox's forwarding of AWS credentials via `credentials.awsPairs` now resolves conflicts predictably and warns about risky setups. Later entries override earlier ones with the same target, each environment variable can fill only one slot, and the losing slot is disabled with a placeholder rather than silently taking the wrong value.

**Details**

- Deliberately empty "suppressor" entries are preserved so they keep implicit AWS auto-pairing switched off.
- Pairs whose key id or secret are not masked as whole values are either dropped or forwarded with a warning.
- New warning when SigV4 re-signing is configured without `injectHosts`, because the real secret is then in scope at every entry in `network.allowedDomains`.
- New warning when a session token belonging to a forwarded pair is not itself forwarded.

**Evidence**

`SigV4 re-signing with the real secret is in scope at EVERY host in network.allowedDomains`

- Area: Sandbox
- Names: `credentials.awsPairs`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Sandbox proxy credentials are unique per sandbox instance

Concurrent sandboxes now get unique proxy usernames instead of sharing one identity.

**What**

Proxy settings injected into the sandbox (`HTTP_PROXY`, `ALL_PROXY`, `CLOUDSDK_PROXY_USERNAME`, and the socat/ssh ProxyCommand) now use a username with an instance suffix rather than the fixed `srt`, so concurrent sandboxes no longer share one identity. If the suffixed name would exceed 255 bytes it falls back to plain `srt`.

**Evidence**

`CLOUDSDK_PROXY_USERNAME=`

- Area: Sandbox
- Names: `HTTP_PROXY`, `ALL_PROXY`, `CLOUDSDK_PROXY_USERNAME`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Plugin archive downloads are checked on every redirect hop

Plugin archive downloads re-check policy at every redirect and strip headers on cross-origin hops.

**What**

Installing a plugin from an archive URL now re-applies the URL policy at each redirect, and refuses the install if any hop fails it. Headers inherited from the marketplace are stripped when a redirect crosses to a different origin, and a sha256 that does not match aborts the install.

**Details**

- Marketplace catalog redirects must stay on the same origin, or be https to a host that is neither loopback nor a cloud metadata address.
- An archive that does not actually contain the component paths its marketplace entry declares is rejected, with advice on repackaging.

**Evidence**

`plugin archive redirect policy rejected`

- Area: Plugins
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### MCP OAuth discovery cache is re-validated against policy

Cached MCP OAuth details are rechecked against policy, so moved servers no longer authenticate stale.

**What**

Cached OAuth discovery details for MCP servers are now re-checked against policy before reuse and kept only for a fixed window. If the cached authorization server fails the check, stored credentials are invalidated and the cached entry is cleared instead of being reused, so a server that has moved or been disallowed no longer silently authenticates against a stale endpoint.

**Evidence**

`Returning cached discovery state (authServer: `

- Area: MCP
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Memory paths capped at 1024 bytes and reserved names refused

Memory paths over 1024 bytes and reserved names like skills or hooks are now refused.

**What**

A memory path longer than 1024 bytes is now rejected, and callers that ask for unsafe segments to be rejected also refuse reserved directory names such as skills, commands, agents and hooks. The refusal message was rewritten and no longer echoes the raw path unescaped.

**Evidence**

`and are not reserved names (skills, commands, agents, hooks, and similar`

- Area: Memory
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Plugin marketplace downloads get a size cap and redirect checks

Marketplace downloads now cap response size and vet redirects before following them.

**What**

Marketplace fetches now enforce a maximum response size and inspect redirects before following them, and errors redact the fetched URL out of the technical details shown to you.

**Details**

- Plugin entries are checked for declared component paths, limited to 32 entries and rejecting URLs and traversal-style segments.
- Archive extraction can now descend into a single wrapper directory when that directory contains a `.claude-plugin` directory.

**Evidence**

`beforeRedirect`

- Area: Plugins
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Settings refresh skips notifying when a login or logout races it

Logging in or out mid-refresh no longer lets stale settings overwrite your new state.

**What**

If you log in or out while a remote settings fetch is in flight, the result is discarded instead of being applied over the newer state. The refresh snapshots an auth version before fetching and returns early if it changed.

**Details**

- Logs "Remote settings: Refresh superseded by a login/logout reset; not notifying".
- A pending reset callback now also runs on the failure path, and chains onto an in-flight refresh when one exists.

**Evidence**

`Remote settings: Refresh superseded by a login/logout reset; not notifying`

- Area: Settings
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Self-hosted environments get a labelled section in the picker

The environment picker now shows a labelled divider above your self-hosted entries.

**What**

The session/environment picker now shows a labelled divider row above your self-hosted entries instead of a blank spacer, so it is clear where that group starts.

**Evidence**

`— Self-hosted environments —`

- Area: Self-Hosted Runners
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Remote worker conflicts say whether the registration was stale or superseded

Worker registration conflicts now say whether the registration was stale or superseded.

**What**

When the server rejects a worker registration with HTTP 409, Claude Code reads the reason from the response and reports either a stale registration with no live successor or being superseded by a newer worker, instead of one generic conflict.

**Details**

- The two outcomes log separately as `cli_worker_epoch_stale` and `cli_worker_epoch_mismatch`.
- Reading the response body is skipped when the client has already closed.

**Evidence**

`cli_worker_epoch_stale`

- Area: Sessions
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Question prompts are classified using the whole prompt, not just the question text

Question prompts are classified using labels, descriptions and headers, not just the question text.

**What**

The automatic classification of an AskUserQuestion prompt now sees each option's label, description and preview, the header, and whether multiple selections are allowed, instead of only the joined question texts. Malformed payloads are handled rather than throwing.

**Details**

- A payload that cannot be read is substituted with `[malformed AskUserQuestion input]` and classification continues.

**Evidence**

`[malformed AskUserQuestion input]`

- Area: Permissions
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Bug reports carry the last API request

Bug reports you send now include the most recent API request for context.

**What**

Feedback bundles now include the most recent API request, giving more context when a report is investigated.

**Details**

- The captured bundle gained a `lastApiRequest` field.
- Raw transcript capture no longer takes the shortcut of reading the whole file when it is small enough.

**Evidence**

`lastApiRequest`

- Area: Feedback
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Peer session listing reports truncation

Peer session listings now say when there are more sessions than shown.

**What**

Listing peer sessions used to return a partial list silently once it exhausted its page budget. It now tells the caller and the log line that more sessions exist.

**Evidence**

`pages (more sessions exist)`

- Area: Cross-Session Messaging
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Reattaching to a vanished bridge session skips history backfill

Reattaching to a vanished remote session mints a fresh one without backfilling missing history.

**What**

When you reattach to a remote bridge session that no longer exists, Claude Code mints a fresh session instead, and that session is now flagged to skip backfilling history it does not have. The replacement also gets a neutral fallback title rather than inheriting the old one.

**Details**

- The flag is threaded through the transport options, the `bridge-session` transcript entries, the session-state maps and the clear/save helpers, so it survives save and restore.
- Triggered by a reattach-target-gone callback; nothing else in the build sets the flag.

**Evidence**

`noHistoryBackfill`

- Area: Sessions
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Bridge disconnects say what kind of failure happened

Dropped bridge connections now say what kind of failure happened rather than one generic message.

**What**

A dropped repl bridge connection now records an error kind alongside the error message, and close code 4090 distinguishes a session worker registration that has gone stale with no active worker holding it from a session the server does not have, instead of showing one generic message for both.

**Evidence**

`the session worker registration went stale \u2014 no active worker holds it (code 4090)`

- Area: Sessions
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Stricter cleanup of JWT masking rules

Malformed JWT masking options in credential rules are now discarded.

**What**

Deny-mode credential masking rules now discard a `decode` value that is not `"jwt"`, and discard `maskClaims` unless it is an array of strings, on top of the existing cleanup of the extract, no-match and duplicate-masking options.

**Evidence**

`maskClaims requires decode \u2014 without a decode format there is no token to read claims from. Set decode, or omit maskClaims.`

- Area: Sandbox
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Memory store listing shows each store's index

The Memory tool's store list now ends each line with the store's index.

**What**

The Memory tool's list of connected stores now ends each line with the store's index, after the id, description and writable or read-only marker.

**Details**

- Appears under the existing "Connected memory stores" header; the index is assembled into the line at runtime.

**Evidence**

`Connected memory stores`

- Area: Memory
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Compaction boundaries can appear in streamed output

Compaction boundary markers can now show up in streamed output.

**What**

When a conversation is compacted, the marker for that boundary is now eligible to be emitted in streaming output alongside local command messages.

**Details**

- The filter accepts system messages with subtype `compact_boundary`; previously only `local_command` passed.
- A predicate can be registered to suppress them; with nothing registered it defaults to allowing them, so in this build they are emitted.

**Evidence**

`compact_boundary`

- Area: Compaction
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Team memory prompt no longer explains saving when nothing can be saved

With no writable team memory, Claude is no longer told how to save memories.

**What**

When your project has no writable team memory location, the instructions telling the model how to save memories are dropped from its prompt entirely, leaving only a statement that access is read-only.

**Details**

- The removed block is the "## How to save memories" section.
- What remains: "You have read-only access to team memory synced from your project. You cannot persist new memories in this session."
- A shorter one-step variant of the save instructions is used when no index is required.

**Evidence**

`You have read-only access to team memory synced from your project. You cannot persist new memories in this session.`

- Area: Memory
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Host credentials path normalised like every other sandbox rule

A trailing slash in your host credentials file path no longer changes whether the rule matches.

**What**

The file named by `CLAUDE_CODE_HOST_CREDS_FILE` is now run through the same path normaliser as sandbox allow/deny entries and permission deny rules, so a trailing slash no longer changes whether the rule matches.

**Details**

- Sandbox deny and allow filesystem entries, permission deny rules and the host credentials path all share one trailing-separator-aware normaliser when sandbox rules are built.
- Previously each of these handled paths its own way.

**Evidence**

`CLAUDE_CODE_HOST_CREDS_FILE`

- Area: Sandbox
- Names: `CLAUDE_CODE_HOST_CREDS_FILE`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Two more MCP connections skip issuer metadata validation

Two more MCP transports now skip issuer metadata validation, matching the rest.

**What**

Two MCP client setup paths now set the same issuer-metadata-validation skip that the other paths already used, so OAuth-backed MCP servers behave consistently across transports.

**Evidence**

`skipIssuerMetadataValidation`

- Area: MCP
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Command execution can redirect child output

Command helpers can now redirect child output instead of always buffering it in memory.

**What**

The shared command-running helpers now pass through `stdout` and `stderr` options when a caller supplies them, so child process output can be redirected instead of always being buffered in memory.

**Evidence**

`preserveOutputOnError`

- Area: Elsewhere
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Allowlist blocks recognised from a second proxy header

Artifact fetches recognise a second proxy header as a network allowlist block.

**What**

Artifact fetches now treat a 403 carrying `x-deny-reason: host_not_allowed` as a network allowlist block, alongside the existing `x-proxy-error: blocked-by-allowlist`, and other failures append the proxy's deny reason to the error text. The message makes clear the artifact itself is still accessible.

**Evidence**

`not reachable through this environment's network allowlist; your access to the artifact itself is fine (the boot check passed)`

- Area: Artifacts
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Sandbox proxy only buffers request bodies when there is one

The sandbox proxy only buffers request bodies when one exists, and tears down bad requests.

**What**

The sandboxed network proxy now copies a request body only for methods that carry one, or when a content-length or transfer-encoding header is present, and tears down both streams when a request is malformed or denied. Deny messages were reworded and encrypted-tunnel requests are now filtered against a decoded command name.

**Evidence**

`denied by sandbox policy`

- Area: Sandbox
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Drain loops should break on `budget_reached`, not just terminal stops

Skill guidance now says polling loops should break on any stop reason except requires_action.

**What**

The skill's client-patterns guidance changed its advice for loops that poll a session: break on any stop reason that is not `requires_action`, rather than only on terminal ones. `budget_reached` is listed as resumable only by changing the budget.

**Details**

- The sample loop comment moved from `// end_turn or retries_exhausted - both terminal` to also name `budget_reached`.

**Evidence**

`break // end_turn, retries_exhausted, or budget_reached \u2014 see list below`

- Area: Agent SDK Docs
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Broken marketplace entries say what is wrong with them

A broken marketplace entry now tells you what failed validation instead of a generic message.

**What**

A marketplace entry that fails validation is still listed as unsupported, but now carries the validation reason, and an install attempt reports it. Entries that genuinely declare a source type Claude Code does not support keep the old generic message.

**Evidence**

`This plugin's marketplace entry is invalid: `

- Area: Plugins
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Sandbox proxy logs blocked requests and repairs lost request framing

The sandbox proxy now logs each denied request with its method, URL and reason.

**What**

The sandbox's HTTPS proxy now logs each denied request with its method, URL and the reason it was denied, so a blocked network call is traceable rather than silently failing.

**Details**

- The proxy takes a denial callback; the log message is assembled at the time of the denial.
- When an incoming request has a body but the outgoing headers ended up with neither `content-length` nor `transfer-encoding`, the proxy sets `transfer-encoding: chunked` so the body is still framed correctly.
- If either side of the connection died while the request filter was running, the proxy now tears the connection down cleanly.

**Evidence**

`onFilterRequestDenied`

- Area: Sandbox
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### cloud CLI: piped stdin no longer silently overrides a task description

Piped input no longer silently overrides a task description you passed to --cloud.

**What**

Giving `--cloud` a task description while also passing `--environment` and piping stdin is now rejected instead of letting stdin quietly win. When an environment is given, creating the session also prints its id.

**Details**

- The error for public projects now tells you to drop `--environment` instead of mentioning Anthropic-managed environments.

**Evidence**

`Error: --environment with --cloud <description> cannot also take piped stdin. Pass the task as the description, or drop --cloud.`

- Area: Cloud Sessions
- Names: `--cloud`, `--environment`
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Archive-installed plugins get a real version instead of "unknown"

Plugins installed from an archive now show a real version instead of "unknown".

**What**

A plugin installed from a downloaded archive with no pinned checksum now gets a 12-character version derived from the hash of the content that was actually downloaded, rather than reporting "unknown". The log line says whether the hash was pinned or downloaded.

**Evidence**

`Using archive sha256 version for `

- Area: Plugins
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Plugin install retries inside a wrapper directory

Plugin installs now retry inside an archive's wrapper folder when declared paths are missing.

**What**

When a plugin archive's declared entry paths do not exist under the directory install picked, it now retries against the archive's single wrapper subdirectory, and can fall back to the archive root instead, logging which one it used.

**Details**

- Covers the common case of an archive whose contents sit one level down inside a single folder.

**Evidence**

`Plugin archive entry paths resolve at the archive root; using it instead of the promoted subdirectory`

- Area: Plugins
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Pastes that can no longer be loaded are handled explicitly

Pastes whose data can no longer be read are marked unavailable instead of lingering oddly.

**What**

Pasted content whose data can no longer be read is now marked unavailable rather than left in an odd state. Text pastes count alongside images and audio when clearing out abandoned pastes, and their placeholders are removed from what you see, shown instead as "Pasted text" or "Truncated text".

**Evidence**

`unavailable: !0`

- Area: Input
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Expired pastes are dropped from the prompt with an explanation

Expired pastes are dropped from your prompt with a notice instead of sending a placeholder.

**What**

If a pasted block is no longer available when you submit, it is now removed from the prompt and you get a notice saying so, instead of a literal placeholder being sent to the model.

**Details**

- The old placeholder text "[Pasted text #N — content no longer available]" has been removed.
- Pastes flagged as unavailable are excised from both the shortened and fully expanded prompt.
- The notice is built from "is no longer available and was removed from the prompt", with a plural form "are no longer available and were removed from the prompt", and labels the item either "Pasted text" or "Truncated text".

**Evidence**

`is no longer available and was removed from the prompt`

- Area: Input
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Archive-installed plugins are no longer mistaken for ref-tracking ones

Zip-installed plugins keep their pinned version instead of being re-fetched on every load.

**What**

A plugin whose cache directory is a 12-character hex name used to be treated as tracking a git ref, which meant it was re-fetched on every load. That check now also requires the plugin to come from a git source, so zip-archive plugins, whose cache directory is the first 12 hex characters of the archive digest, keep their pinned version.

**Evidence**

`sourceHasPinnedSha`

- Area: Plugins
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Clearer failure when a plugin uses an unsupported source type

A plugin source this version does not recognise now tells you to update Claude Code.

**What**

Installing a plugin whose marketplace entry has a source kind this version does not recognise now fails with a direct message telling you to update Claude Code, instead of a generic error.

**Details**

- Message: "This plugin uses a source type your Claude Code version does not support. Update Claude Code and try again."

**Evidence**

`This plugin uses a source type your Claude Code version does not support. Update Claude Code and try again.`

- Area: Plugins
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### MCP auth failures distinguish issuer mismatches

MCP OAuth issuer mismatches are now recorded separately from other auth errors.

**What**

OAuth failures against an MCP server now classify issuer-echo problems separately from generic auth errors, so logs and telemetry can tell them apart from cases like a redirect URI mismatch or a Microsoft Entra specific failure.

**Details**

- The token refresh path can now fail with the distinct reason `mcp_oauth_refresh_issuer_echo_denied`.

**Evidence**

`issuer_echo_mismatch`

- Area: MCP
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Analytics shutdown waits for queued work before closing sinks

Analytics now drain queued events before shutting down, so crash-path events have a chance to send.

**What**

Shutdown now drains a queue of pending analytics tasks, raced against a 200 ms timeout, before the event logging, Datadog and error-tracking sinks are closed. The first user of this is the MCP tripwire event, so an event fired on a crash path now has a chance to be sent instead of being dropped at exit.

**Evidence**

`registerPreFlushTask`

- Area: Telemetry
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Sandbox violation text is stripped of angle brackets

Angle brackets are stripped from sandbox violation text before it is stored.

**What**

Sandbox violation lines are normalized and have `<` and `>` removed before being stored, so violation text cannot smuggle tag-like markup into anything that later displays it.

**Evidence**

`addViolation`

- Area: Sandbox
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Agent versions are sequential integers, not timestamps

Agent versions are documented as counting up from 1, not timestamps.

**What**

The docs previously said each agent update produced a version numbered with a timestamp, and gave a timestamp example. They now say versions start at 1 and increment by one on each update.

**Details**

- Also tightened: `effort` is the only field exempt from full replacement when you supply a `model` object, and supplying `model` without `inference_geo` clears any pinned geography.

**Evidence**

`a sequential integer, starting at 1 and incrementing on each update`

- Area: Agent SDK Docs
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Plugin install from a marketplace now honours archive auth on one more path

Installing from a marketplace entry now forwards archive credentials on one more code path.

**What**

A remaining plugin install call site was brought in line with the others, so installing from a marketplace entry now looks up its source and forwards the archive credentials and the component paths declared in the manifest. Authenticated archive sources work on that path too.

**Evidence**

`entryDeclaresComponents`

- Area: Plugins
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Clearer error when `--bg` and `--environment` are combined

Combining --bg and --environment now explains they are different backends and shows the right command.

**What**

Passing both now says the two are different backends and shows the direct command to run instead, rather than reusing the older `--cloud` wording.

**Evidence**

`--bg and --environment are different backends. Use `claude -p '<task>' --environment <id>` directly to start a session on the environment.`

- Area: CLI
- Names: `--bg`, `--environment`
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### WSL settings are read without blocking the settings refresh

On WSL, reading inherited Windows settings no longer blocks the periodic settings refresh.

**What**

On WSL, the periodic settings refresh now reads the inherited Windows settings file asynchronously and awaits it, instead of doing a synchronous read on the interval.

**Details**

- Disposal is rechecked after each await so a refresh in flight does not act on a torn-down poller.

**Evidence**

`wslWindowsFile`

- Area: Elsewhere
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Eval reports shorten very long judge evidence

Eval reports now shorten huge evidence blocks and point at the full text on disk.

**What**

Generated evaluation reports now elide the middle of the evidence block shown to the judge above roughly 20000 characters, keeping the first 16000 and last 4000 and leaving a marker that points at the full text in the JSON artifact on disk. The judge prompt uses the same elision.

**Details**

- The aggregate results file is now written with a trailing newline.

**Evidence**

`aggregate-result.json`, `full text in aggregate-result.json`

- Area: Plugin Eval
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Wayland clipboard copies run one after the other

On Wayland, clipboard and primary-selection writes now happen in order, not simultaneously.

**What**

On Linux under Wayland, copying no longer writes the clipboard and the primary selection at the same time. The clipboard write completes first, and if a newer copy has begun in the meantime the primary-selection write is skipped.

**Details**

- Applies when `WAYLAND_DISPLAY` is set and `wl-copy` is on PATH.
- Both writes have their output discarded.

**Evidence**

`wl-copy`

- Area: Elsewhere
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Git clone errors no longer mangle URLs containing dollar signs

Clone errors no longer mangle text when the repository URL contains dollar signs.

**What**

When a clone fails, the repository URL is redacted out of the error and stderr text. Redacted URLs containing `$&`-style sequences are now inserted literally instead of being expanded into surrounding text.

**Evidence**

`--filter=blob:none`

- Area: Elsewhere
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Inline plugin downloads retry the final file move

Inline plugin downloads retry the final file move, cutting spurious install failures.

**What**

Downloading an inline plugin now uses the shared retrying rename when moving the finished download into place, which should cut spurious failures when the destination is briefly locked.

**Details**

- Replaces a direct rename of the partial download file, immediately before the "Downloaded inline plugin from" log line.
- Plugin manifest load failures now report through a helper bound to the span name, taking only the reason (`plugin_load_manifest_read_failed`, `plugin_load_manifest_json_invalid`), and reuse the already-resolved manifest lookup.

**Evidence**

`plugin_load_manifest_read_failed`

- Area: Plugins
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Publicly shared artifacts are labelled correctly

A publicly shared artifact now correctly reads "anyone with the link".

**What**

An artifact shared publicly now reports its audience as "anyone with the link" instead of falling through to the unrecognised-sharing fallback.

**Details**

- Share-mode parsing gained an explicit `public` case that reports the artifact as shared and live.
- Previously this mode hit the "others (unrecognized share mode — treating as shared)" branch, which treated it as shared but described it vaguely.

**Evidence**

`anyone with the link`

- Area: Artifacts
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Pasted content marked unavailable is skipped when expanding input

Pastes marked unavailable are skipped when your input is expanded.

**What**

The two places that substitute pasted text back into your input now skip entries flagged unavailable, so a dropped or expired paste is not inlined.

**Details**

- Previously only non-text entries were skipped.
- The input component now receives `getPastedContents` and `getInputMode`, image ids are minted through a shared allocator with an `onMinted` callback, and the command handler surface gains `getCurrentInput`.

**Evidence**

`getPastedContents`

- Area: Input
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### `claude plugin eval init --interactive` fails clearly without a terminal

Asking for the eval interview without a terminal now fails clearly and tells you the alternative.

**What**

Asking for the interactive authoring interview when there is no real terminal attached now prints an explanation and exits 1, instead of trying to run the interview. The message tells you to either run it in a terminal or drop `--interactive` and pass a case name to get a blank template. There are two wordings depending on whether you supplied a case name.

**Evidence**

`The authoring interview requires an interactive terminal (TTY). Run `claude plugin eval init` in a terminal, or drop --interactive and pass a case name (e.g. `claude plugin eval init my-case`) to write a blank template instead.`

**Usage**

`claude plugin eval init my-case`

- Area: Plugin Eval
- Names: `--interactive`
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Contradictory publish flags are rejected up front

Passing both --no-publish and --publish-report now errors before the run starts.

**What**

Passing both `--no-publish` and `--publish-report` now stops the run before it starts with an error, rather than starting and then behaving ambiguously.

**Evidence**

`Error: --no-publish and --publish-report are contradictory`

- Area: Plugin Eval
- Names: `--no-publish`, `--publish-report`
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Proxy tears down both sockets on a rejected request, and chunks bodyless forwards

The proxy now closes both sockets on a rejected request instead of leaving half-open connections.

**What**

When a request is denied or AWS SigV4 re-signing fails, the proxy now destroys the client socket and, if the original socket is a different one, fails that too, instead of leaving a half-open connection.

**Details**

- Separately, if an incoming request declared a body but neither content-length nor transfer-encoding survives into the outbound headers, the proxy sets `transfer-encoding: chunked`.

**Evidence**

`AWS SigV4 re-signing failed: `

- Area: Sandbox
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### A plugin entry that fails validation now keeps the reason

A marketplace entry with bad fields keeps its validation error instead of looking unsupported.

**What**

When a marketplace entry uses a source type Claude Code knows but gets its fields wrong, the entry is kept along with the validation error instead of looking identical to a source type this version simply does not recognize.

**Details**

- The message is compacted before storage: unrecognized field names are listed and sanitized, the text is truncated with an ellipsis, and extra items are rendered as "(+N more)".

**Evidence**

`Placeholder for source types this Claude Code version does not recognize, or a known type whose fields failed validation (then `error` `

- Area: Plugins
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### `claude plugin eval init` no longer suggests a flag that will not help

The no-terminal message for eval init dropped a flag suggestion that would not have helped.

**What**

The message shown when there is no terminal dropped its "(or pass --interactive)" suggestion, since the interview needs a terminal either way. It now just says to re-run in a terminal.

**Evidence**

`No TTY available \u2014 writing a blank template. Re-run in a terminal for the authoring interview.`

- Area: Plugin Eval
- Tier: You'll notice
- Useful: 1/5
- Signal: 1/5

## Bug Fixes

### Failed `@name` messages now tell you they failed

Direct messages you send with @name now warn you when delivery fails.

**What**

Sending a direct message by typing `@name` in the REPL now shows a 5 second notification when the write to the recipient's mailbox fails, instead of quietly doing nothing. Success shows the same confirmation as before.

**Details**

- Both paths now run through one shared helper, so the success and failure cases cannot drift apart.

**Evidence**

`Couldn't write to @`

- Area: Team Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Plan approvals report mailbox write failures instead of claiming success

Plan approvals sent to a teammate now report failure and retry instead of claiming success.

**What**

Sending an approval to a teammate's inbox can fail, and callers now notice. The approve-plan path returns failure with a retry hint saying the teammate is still awaiting approval, the inbox poller logs the error and re-queues the request, and the sandbox permission sync records a mailbox write failure rather than a success.

**Details**

- The write now returns the message id, or nothing on failure, which is what the three callers check.

**Evidence**

`the teammate is still awaiting approval`

- Area: Team Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Failed team mailbox writes are no longer treated as sent

Messages to teammates that failed to write now return an error instead of a false success.

**What**

Writing to a teammate's mailbox could fail while the caller reported success. Plan rejection now returns failure with a message saying nothing was sent, sending a message to a teammate returns the error `write_failed`, and permission forwarding emits a mailbox-write-failed degradation instead of an unconditional success event.

**Evidence**

`Failed to write the plan rejection to `

- Area: Team Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Mailbox write failures are reported instead of being assumed to succeed

Plan submissions and shutdown requests to another agent now fail visibly when delivery fails.

**What**

Writes to another agent's inbox can now fail visibly. Submitting a plan for lead approval throws a dedicated error when the write does not land, shutdown requests and shutdown rejections return a failure result naming the target, and the shutdown-approval confirmation is marked degraded. Previously all of these carried on as if the message had been delivered.

**Evidence**

`Failed to write the plan approval request to the lead's inbox \u2014 plan not submitted; try again`

- Area: Team Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Teammate mailbox failures are reported instead of silently swallowed

Claude is now told when a message to a teammate did not get delivered.

**What**

When a write to a teammate's inbox fails, callers now check the result and tell the model the delivery did not happen, for example that a confirmation could not be written. Previously most of these call sites ignored the failure.

**Details**

- The degraded-outcome path for a failed mailbox write went from one call site to nine.
- Lock releases are wrapped so a failed release logs a warning instead of escaping the surrounding cleanup block.
- Inbox write errors are now logged at error level and filtered before being surfaced.

**Evidence**

`The confirmation could not be written to team-lead's inbox.`

- Area: Team Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Teammate spawn fails loudly instead of opening an idle pane

Spawning a teammate now errors out instead of leaving an idle agent with no instructions.

**What**

If the initial instructions cannot be written into a new teammate's inbox, the spawn now throws rather than launching an agent that would sit doing nothing, and records an error counter.

**Evidence**

`subagent_teammate_prompt_write_failed`

- Area: Team Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Undelivered permission requests to a team lead now surface

A permission request that never reached your team lead now tells you to retry.

**What**

When a teammate's permission request cannot be written to the lead's mailbox, the failure was previously ignored and the request appeared to be in flight. The result is now awaited and a message tells you the mailbox write failed and to retry the tool call. Two call sites use slightly different wording, one of which adds "or ask the lead directly".

**Evidence**

`The permission request could not be delivered to the team lead (mailbox write failed). Retry the tool call.`

- Area: Team Messaging
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Bridge event checks fail closed when their config is malformed

If the bridge event rules are unreadable, only fully verified events are accepted.

**What**

If the configuration controlling which bridge events are accepted fails to parse, verification now falls back to the strictest setting (accept only fully verified events, no other statuses allowed) and reports "malformed_config" once, rather than proceeding on a partial config.

**Details**

- Unverified events that are not control requests are dropped with a warning that classifies the payload as `control_request_other` or `other_payload_type`.
- Drops are reported as `bridge_stray_event_attestation`, deduplicated to once per class per process so a flood does not spam telemetry.

**Evidence**

`bridge_stray_event_attestation`

- Area: Editor Bridge
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Generic 5xx text from the peer service is now treated as retryable

Server errors while listing peer sessions are recognised properly and treated as retryable.

**What**

Errors while listing peer sessions were only recognised as server-side when formatted as "HTTP 5xx". Any message containing a 5xx status code now matches too, so these are correctly treated as not your fault and bucketed as a server error rather than surfacing as an actionable failure.

**Evidence**

`bridge_http_5xx`

- Area: Team Messaging
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Refused cross-session messages are dropped before attachments are built

Messages refused from another session are dropped before their attachments are fetched.

**What**

Messages arriving from another session are checked against the peer and coordinator policy before their attachments are fetched and before they are queued, so a refused message is logged and skipped instead of being half-processed.

**Evidence**

`bridge:repl: dropped before attachment materialization`

- Area: Team Messaging
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### REPL bridge survives `/clear` instead of dropping

Running /clear no longer disconnects the editor bridge; it follows you onto the new session.

**What**

Clearing the conversation no longer tears down the REPL bridge. The bridge now repoints onto the new session, carrying its session id, last sequence number, dialog kinds and grouping id, and flags that history should not be backfilled.

**Details**

- It also subscribes to reset, resume and spare-claim transitions.
- Archiving the old session changed from an immediate call to a deferred, error-logged one that can be cancelled.
- New telemetry records when the bridge disables itself automatically (`tengu_bridge_repl_auto_disabled`) and when a compaction happens while it is disabled.
- Queued SDK events are forwarded with their own error logging rather than being lost silently.

**Evidence**

`tengu_bridge_repl_auto_disabled`

- Area: Editor Bridge
- Names: `/clear`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Sandbox credential restrictions no longer lost when settings are merged

Managed limits on which AWS credentials the sandbox may use survive settings merges now.

**What**

Restrictions on which AWS credential forms the sandbox may use now survive merging of managed settings into child settings, instead of being dropped at the merge boundary.

**Details**

- Inheritance now carries the `sandbox.credentials.sigv4` deny list, whose values are `streaming`, `presigned` and `sigv4a`.
- A normalized `awsPairs` list of allowed AWS key pairs is carried through the same merge.

**Evidence**

`sigv4a`

- Area: Sandbox
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### MCP OAuth discovery state no longer wiped during a concurrent re-save

A race no longer wipes working MCP OAuth discovery data during a concurrent save.

**What**

Invalidating stored OAuth discovery data now bails out instead of clearing it when the stored entry has no authorization server metadata, which is the signature of another save writing just the URL at the same time. This prevents a race that could throw away working discovery state.

**Details**

- Invalidating discovery no longer clears the step-up scope along with it.

**Evidence**

`invalidateCredentials('discovery') preserved: concurrent URL-only re-save`

- Area: MCP
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Trailing slashes no longer change glob-permission warnings on Linux

Trailing slashes in Read and Edit permission rules no longer change the wildcard warning on Linux.

**What**

The check that warns about wildcard patterns in Read and Edit permission rules now strips trailing slashes from deny rules and absolute paths before looking for wildcards, so `/foo/bar/` and `/foo/bar` behave the same. Allow and deny rules are walked as tagged pairs. Linux and WSL only; other platforms produce no warnings.

**Evidence**

`Failed to get Linux glob pattern warnings: `

- Area: Permissions
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Pasted content that can no longer be loaded is kept as a placeholder

Resuming a session keeps unavailable pastes as placeholders so surrounding text stays aligned.

**What**

Resuming a session with pasted text or images no longer drops entries whose content is gone and rewrites the visible text around them. Each missing paste now becomes a placeholder record with empty content, marked unavailable, so the rest of the conversation still lines up.

**Details**

- New paste ids are minted by a session-wide allocator that scans existing text and image paste references, including the stored image paste id list, so a restored session cannot reuse an id that is already taken.

**Evidence**

`pastedContents`

- Area: Paste Handling
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Status messages from nested sessions are passed up again

Compaction status from a nested session is now shown in the session you are watching.

**What**

A status message produced inside a nested session used to be dropped. It is now re-emitted to the outer session along with the permission mode and the result or error from compaction, so a compaction that happened deeper down is visible where you are.

**Details**

- The compaction error is only attached when an internal check allows it, so it can be stripped before the message leaves the session.

**Evidence**

`compact_result`

- Area: Compaction
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Background settings poll no longer clobbers a login or logout

Logging in or out mid-refresh no longer gets overwritten by stale background settings.

**What**

If you log in or out while a background remote-settings refresh is still running, that stale refresh no longer applies its result.

**Evidence**

`Remote settings: Background poll superseded by a login/logout reset; not notifying`

- Area: Settings
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Transcript forwarding over the bridge no longer duplicates or stalls

Forwarding your transcript to a connected editor no longer duplicates messages or stalls.

**What**

Messages forwarded to a connected SDK client are now tracked by message id in a set rather than by position alone, so nothing is sent twice, and a failure in one pass is logged instead of stopping forwarding. A conversation reset rewinds the offset, and reconnects that ask for no history backfill skip the replay.

**Evidence**

`[bridge:sdk] transcript forward failed: `

- Area: Editor Bridge
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Proxy survives malformed requests and broken sockets

The sandbox proxy no longer crashes on malformed requests or broken sockets.

**What**

The local sandbox proxy now attaches error handlers to client requests, responses and upstream responses, so a stream error is logged instead of crashing it. Malformed client requests get 431 Request Header Fields Too Large when headers overflow and 400 Bad Request otherwise, then the socket is destroyed after a one second timer that does not hold the process open.

**Evidence**

`431 Request Header Fields Too Large`

- Area: Sandbox
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Rate-limit headers from before a login change are discarded

Rate-limit info from before you logged in or out can no longer overwrite fresh state.

**What**

Rate-limit tracking now carries a version marker that increments when login state resets, so a response that was already in flight when you logged in or out cannot overwrite the fresh state. The reset also returns status to allowed and clears overage flags.

**Evidence**

`anthropic-ratelimit-unified-grace-status`

- Area: Rate Limits
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### MCP stdio connections remember how the last child process ended

Reconnects to a local MCP server now know whether the previous process exited or was killed.

**What**

When an MCP server started over stdio is torn down, Claude Code now records whether its process had already exited or was still running, and the reconnect path reads that back on the next automatic connect. This gives reconnection a view of whether the previous server died or was killed mid-flight.

**Details**

- Keyed by a hash over the command, working directory, sorted environment and arguments, so each distinct server configuration gets its own record.
- Recorded only if the transport had finished starting; the outcome is stored as "hard_closed" for an already-exited child or "survived" for a live one.
- The entry is deleted once consumed. The map is capped and evicts oldest-first.

**Evidence**

`hard_closed`

- Area: MCP
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Conversation resets are never dropped from a full event queue

A conversation reset can no longer be discarded when the event queue is overloaded.

**What**

When the bounded queue of task events fills up, it no longer evicts a conversation reset. Resets and null-status system frames are skipped as eviction candidates and the next eligible entry is evicted instead, so a reset cannot be lost under load.

**Details**

- Tearing down the remote-control bridge while a reset is still undelivered now reports that fact and resets the bridge's own state.

**Evidence**

`bridge_conversation_reset`

- Area: Editor Bridge
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### HTTPS proxy denials report why, and dead sockets are torn down

Blocked proxy requests now report why, and dead connections are closed rather than written to.

**What**

When the proxy's request filter blocks a tunnelled request, it now reports the method, url, reason and the encoded command to the denial handler instead of failing quietly. Once the upstream connection resolves, the socket is destroyed if either end has already gone away, rather than continuing to write to a connection that no longer exists.

**Evidence**

`onFilterRequestDeny`

- Area: Sandbox
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Flag-conflict error names the flag you actually used

The cloud-session conflict error now blames --environment, the flag you actually typed.

**What**

When a self-hosted pool is selected, the cloud-session flag conflict error now blames `--environment` rather than `--cloud`, and drops the claude.ai/code reattach hint that did not apply.

**Evidence**

`--environment`

- Area: CLI
- Names: `--environment`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Mailbox write failures are logged instead of thrown away

Failing to create another session's inbox directory is now logged rather than ignored.

**What**

Sending a message to another session's mailbox now catches and logs a failure to create the inbox directory rather than failing silently.

**Details**

- The failure is logged at error level and the write returns without proceeding.
- Marking messages as read now releases its lock through a shared helper instead of inline unlock-and-log code.

**Evidence**

`[TeammateMailbox] writeToMailbox: failed to ensure inbox dir: `

- Area: Team Messaging
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Plan temp files left by interrupted writes are now swept

Leftover temp files from interrupted plan writes are now cleaned up by the retention sweep.

**What**

The retention sweep that deletes old plans previously only matched files ending in `.md`, so a plan write interrupted partway through left a temp file behind forever. When invoked with a store handle it now also matches files containing `.md.tmp.`.

**Evidence**

`tengu_retention_sweep`

- Area: Plan Mode
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Editor bridge waits for a pending conversation reset

The editor bridge pauses writing until a queued conversation reset actually lands.

**What**

When a conversation reset is still queued and undelivered, the bridge now stops writing messages and stops forwarding queued events until it lands, instead of writing into a conversation that is about to be cleared. If the process shuts down with a reset still undelivered, that is logged with an `undelivered_at_teardown` outcome and bridge state is torn down.

**Evidence**

`undelivered_at_teardown`

- Area: Editor Bridge
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Pasted content that has expired is reported instead of silently dropped

If a pasted attachment has expired, you get told instead of quietly sending a prompt without it.

**What**

On submit, pasted entries that are no longer available are removed and you get an immediate notification rather than a prompt that quietly lost its attachment. If nothing is left after stripping, or the input was a bash or slash command, the submit is aborted and your original text is restored.

**Details**

- Editing across multiple inputs also renumbers colliding pasted-text ids, so pastes from different inputs no longer overwrite each other.

**Evidence**

`pasted-text-unavailable`

- Area: Paste Handling
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Restored input renumbers pasted attachments

Restored input renumbers old paste markers so they can't point at the wrong attachment.

**What**

When input is restored from history or a resumed conversation, pasted-content markers whose numbers clash with ones this session already issued are re-numbered, and the visible text is rewritten to match. An old paste marker can no longer point at the wrong attachment.

**Details**

- Each session now tracks which paste ids it minted, which is what makes a collision detectable.

**Evidence**

`allowCurrentEpochSkip`

- Area: Paste Handling
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Bash mode no longer swallows pasted non-image content

A !-prefixed line containing pasted text is no longer misfiled as a shell command.

**What**

Typing a `!`-prefixed line that also contains pasted text is no longer recorded as a shell command, and the bash history no longer offers back entries whose pastes were not images. Pasted content that is unavailable is preserved rather than dropped.

**Evidence**

`[Image`

- Area: Bash Mode
- Names: `!`
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Pasted images in the question dialog no longer collide with other pastes

Images pasted into a question prompt no longer collide with pastes made elsewhere.

**What**

Images pasted into an AskUserQuestion prompt were numbered from a counter local to that dialog, starting at 1, so their ids could clash with pastes made elsewhere in the same session. They are now allocated from the session-wide paste id pool, seeded with the ids already attached to that question.

**Details**

- Text being typed into the question's inputs is registered as another source of in-use ids while the dialog is open.
- Auto-advance when you are away, and skip/accept telemetry, are unchanged.

**Evidence**

`ask-user-question-text-input`

- Area: Paste Handling
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### MCPB archive errors truncate the offending file name

Bad extension archives can no longer flood your terminal with a giant file name.

**What**

Errors from validating an extension archive (unsafe path, oversized file) now show the entry name cut to 120 characters, so a hostile archive cannot flood your terminal with one enormous name.

**Evidence**

`Unsafe file path detected: `

- Area: Plugins
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Replayed sessions no longer show empty command output

Replaying a stored session no longer shows blank assistant messages for empty command output.

**What**

When a stored session is streamed back, a local command whose output is blank once trimmed now produces nothing instead of an empty assistant message. Compact summaries are also now treated as synthetic entries, matching how meta and transcript-only entries are handled.

**Evidence**

`local_command`

- Area: Elsewhere
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Clearer message for a relative memory path

The error for a relative memory path now suggests a properly joined path.

**What**

The refusal you get for passing a relative memory path now quotes a correctly joined suggested path instead of gluing a prefix onto it.

**Details**

- Memory conflict errors now also carry the conflicting path.
- Pre-creating the team-memory directory for team-scope stores was dropped from the load path.

**Evidence**

`Memory paths are absolute and start with "/"`

- Area: Memory
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

## In Development

### MCP OAuth checks the issuer a server echoes back, telemetry only for now

MCP OAuth now checks a server's echoed issuer, but only records mismatches rather than blocking.

**What**

During MCP OAuth discovery Claude Code now validates itself that the issuer a server echoes back matches the URL that was asked for, at three points: the RFC 9728 chain, the legacy path fallback and refresh rediscovery. Controlled by `tengu_mcp_issuer_strict_echo`, whose in-source fallback is "observe": mismatches are recorded and the connection proceeds. Only a remote config value switches it to enforce, where a cross-origin echo fails the connection.

**Details**

- The SDK's own checks are turned off (`skipIssuerMetadataValidation`, `skipIssuerValidation`) in favour of the in-house comparison, on SSE and HTTP transports.
- Mismatches emit `tengu_mcp_oauth_issuer_echo_mismatch` with site, mode, outcome, an origin relation of `same_origin`, `cross_origin` or `unparseable`, the differing parts (scheme, host, port, path, query, fragment, userinfo, normalization_only) and hashed issuers.
- In enforce mode the failure raises `IssuerEchoCrossOriginError`, surfaces as a new `issuer_echo_denied` outcome for token refresh and revoke, and is excluded from retry.

**Evidence**

`tengu_mcp_issuer_strict_echo`, `Issuer echo cross-origin mismatch (RFC 8414 \xA73.3): expected `

- Flag `tengu_mcp_issuer_strict_echo`: Not enough to say (read for one account on one subscription tier against v2.1.224; this account: no value returned, anonymous baseline: no value returned, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: MCP
- Tier: Under the hood
- Useful: 3/5
- Signal: 3/5

### MCP servers checked for the issuer they echo back

Claude checks whether an MCP server points authentication elsewhere, logging mismatches without blocking.

**What**

When connecting to an MCP server over OAuth, Claude now compares the authorization-server address it asked about against the `issuer` value the server returns in its discovery metadata, catching a server that points authentication somewhere else. In this build the check only records telemetry: the gate `tengu_mcp_issuer_strict_echo` decides the mode and its in-source fallback is false, so absent a remote value the result is observe-only.

**Details**

- In `observe` mode a mismatch is logged with outcome `proceeded` and the connection continues.
- In `enforce` mode the connection raises an issuer echo cross-origin error and authentication fails with reason `issuer_echo_denied`.
- Mismatches are classified before reporting, including `same_origin` and `unparseable` plus a reason per differing part of the URL.
- None of this exists in 2.1.223; the whole check is new.

**Evidence**

`tengu_mcp_oauth_issuer_echo_mismatch`

- Flag `tengu_mcp_issuer_strict_echo`: Not enough to say (read for one account on one subscription tier against v2.1.224; this account: no value returned, anonymous baseline: no value returned, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: MCP
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Memory prompts can describe connected memory stores

Memory prompts now describe each connected store and its index by name instead of mount paths.

**What**

When memory stores are connected, the automatic memory prompt now builds a per-store view describing each index as a memory index inside a named store, including read-only ones, instead of the older team-mount path phrasing. Without connected stores it falls back to the previous prompt.

**Details**

- A separate memory-tools prompt variant exists, reported in telemetry as `memory_tools`.
- Prompt-load telemetry now carries how many stores are connected, whether an index loaded, whether the tools are writable, and which prompt variant was used.

**Evidence**

`memory_tools`

- Area: Memory
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Session info reports the messaging socket path

SDK and stream-json startup output can report the messaging socket path when the inbox is running.

**What**

The system/init message sent to SDK and stream-json consumers now carries `messaging_socket_path`, read from the running cross-session messaging module rather than from the environment. It is only set when that messaging inbox started, which is itself gated off by default.

**Evidence**

`messaging_socket_path`

- Area: Cross-Session Messaging
- Names: `messaging_socket_path`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Incoming peer messages get their own transcript rendering

Messages from another session render as an @sender header with indented body instead of raw tags.

**What**

A message sent from another session is displayed as an "@ sender" header with the body indented below, instead of the raw wrapper tag. Only reachable when cross-session messaging is enabled (`tengu_harbor_kite` gate or `CLAUDE_CODE_HARBOR_KITE`).

**Details**

- The renderer strips the `<cross-session-message ...>` wrapper and reads the sender from its `from=` and `from-name=` attributes.
- Sender addresses are humanised: a `uds:` address becomes the socket file's basename with `.sock` removed, a `bridge:` address becomes "(untitled)", and anything unresolved shows as "peer".

**Evidence**

`</cross-session-message>`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_HARBOR_KITE`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Loop file can come from stored state instead of disk

Your loop.md can come from stored state when a storage handle is available, otherwise from disk.

**What**

The lookup for `loop.md` gained an async variant that, after checking `.claude/loop.md` in the config directory, falls back to a stored `loop-file` value, trimmed and ignored if empty. The stored path only runs when a storage handle is passed in; otherwise the plain file lookup is used.

**Evidence**

`loop.md`

- Area: Internals
- Names: `loop.md`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Automatic memory context can scan a whole memory directory

Memory context can list every file in your memory directory instead of one file.

**What**

A new loader builds an automatic memory context block listing every memory file under the user memory directory, with paths relative to it and flagged as differing from disk, alongside the existing single-file version. It returns nothing when `CLAUDE_CODE_REMOTE` is set or the session is a cowork one; which of the two is used is decided by those same environment checks.

**Evidence**

`AutoMem`

- Area: Memory
- Names: `CLAUDE_CODE_REMOTE`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Scheduled tasks can resolve `loop.md` defaults asynchronously

Scheduled tasks resolve loop.md content asynchronously and skip a fire rather than run unresolved.

**What**

When a storage store is available, the scheduled-task fire path queues fires one after another and awaits an asynchronous resolution of `loop.md` default content. If that resolution fails, the fire is logged and skipped rather than running with unresolved content. Without a store, the existing synchronous resolution runs.

**Details**

- The skip is logged with the prefix "[ScheduledTasks] loop.md v5 expansion failed; fire skipped: " followed by the error.

**Evidence**

`[ScheduledTasks] loop.md v5 expansion failed; fire skipped: `

- Area: Scheduled Tasks
- Names: `loop.md`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Server-supplied CLI arguments are validated on self-hosted runners

Self-hosted runners now reject malformed or blocklisted CLI arguments handed down by the server.

**What**

Arguments the server hands to a self-hosted runner session are now checked against a name pattern and a blocklist before becoming CLI flags. Rejected ones are skipped and logged as "malformed" or "blocked", and the count of arguments actually applied is logged.

**Details**

- Values starting with `-` are passed as `--name=value` so they are not parsed as a separate flag.
- Log lines are assembled from a template beginning "[runner:session] Skipping ".

**Evidence**

`[runner:session] Skipping `

- Area: Self-Hosted Runners
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Startup tip for /ultrareview

A startup tip suggests cloud branch review and shows how many free reviews you have left.

**What**

A new startup tip suggests the cloud multi-agent branch review and appends your remaining free-review count when it is known. It shows at most once every 8 sessions and is skipped once you have run ultrareview.

**Details**

- Requires ultrareview eligibility and the `allow_remote_sessions` capability.
- Per-surface visibility comes from the `tengu_ultrareview_awareness` gate, whose fallback is empty, leaving the tip off unless a value is supplied remotely.

**Evidence**

`for a cloud-based multi-agent review that finds and verifies bugs in your branch`

- Flag `tengu_ultrareview_awareness`: Not enough to say (read for one account on one subscription tier against v2.1.224; this account: no value returned, anonymous baseline: no value returned, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Code Review
- Names: `/ultrareview`
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Memory extraction can be told to save only personal memories

Memory extraction can be told to save only personal memories, with nothing visibly setting that mode.

**What**

The memory-extraction prompt has a new mode that tells the model the project-shared memory tools are unavailable and that only personal-directory memories should be saved. It is switched by a flag passed in by the caller, and nothing in this build shows what sets that flag.

**Details**

- Related team-memory prompts dropped the branch saying a private directory would still persist.
- The memory upkeep prompt can now skip its "Read the index" step.

**Evidence**

`so skip anything the scope guidance marks as shared with the project`

- Area: Memory
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Cross-session messaging explains why it is unavailable

Messaging another session now tells you why it failed instead of silently doing nothing.

**What**

Sending a message to another session now returns a specific reason instead of quietly failing: one refusal when the feature is off in this session, another when a cross-machine send would have to route through Anthropic servers. Cross-session messaging is gated on `tengu_harbor_kite` (off by default) or the `CLAUDE_CODE_HARBOR_KITE` environment variable, and is off entirely on Windows.

**Details**

- The cross-machine route additionally requires a first-party provider and that nonessential traffic is not disabled; failing either gives the second refusal.

**Evidence**

`Cross-session messaging is not available in this session.`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_HARBOR_KITE`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### /ultrareview tips after reviews and commits

Code review and post-commit tips can point you at a deeper cloud review, off by default.

**What**

Code review can append a line suggesting `/ultrareview` for a deeper cloud-based review, with a matching post-commit tip and a count of free reviews in the slash menu. Nothing shows without remote configuration: the `tengu_ultrareview_awareness` flag defaults to nothing, which is read as no surfaces enabled. It also requires the remote-sessions entitlement.

**Details**

- Skipped for skill preloads and model-scheduled runs, and suppressed once you have run `/ultrareview` yourself.
- Each surface is capped at a few impressions; impressions are reported as `tengu_ultrareview_awareness_shown`.

**Evidence**

`Tip: For a deeper cloud-based review, try /ultrareview next time`

- Flag `tengu_ultrareview_awareness`: Not enough to say (read for one account on one subscription tier against v2.1.224; this account: no value returned, anonymous baseline: no value returned, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Code Review
- Names: `/ultrareview`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Memory-consolidation prompt gains two variants

Memory consolidation prompts gained prune-only and tools-unavailable variants for different session types.

**What**

The prompt used for memory consolidation runs picked up two options. One replaces the index-maintenance step with a pure prune step and drops the instruction to read the index file. The other adds a note that the memory tools are not available during the run, so it should only touch the memory directory. The second is requested only when the session is not running under `CLAUDE_CODE_REMOTE`.

**Details**

- Which variant is used is decided at runtime by the caller; the underlying feature is otherwise unchanged.

**Evidence**

`# Dream: Memory Consolidation`

- Area: Memory
- Names: `CLAUDE_CODE_REMOTE`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Progress and failure messages for cloud file seeding

Cloud file uploads show progress and a clear outcome, falling back to the git bundle on failure.

**What**

While a cloud session uploads the files it starts from, you see a progress line built from how many uploads have settled out of the total. On finish you get one of four outcomes: a summary, a timeout note, an unavailable note, or a could-not-finish note. Each of the failure cases ends by saying the session starts from the git bundle alone. This only appears on the container-sync file path behind `tengu_violin_wood`.

**Evidence**

`Cloud file sync is unavailable for this session; starting from the git bundle alone`

- Flag `tengu_violin_wood`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cloud Sessions
- Names: `container_sync`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Remote file store gains create preconditions and cancellation

Remote file creates can refuse to overwrite an existing file and both calls accept cancellation.

**What**

The remote file store's create call takes an options argument that can attach a `not_exists` precondition, so a create fails rather than overwriting, and both create and update now accept an abort signal. Nothing observed in this build requests the precondition, so whether any caller uses it is not settled here.

**Evidence**

`"not_exists"`

- Area: Remote File Store
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Memory store listing gains each store's index path

Listing memory stores can also report each store's index document path.

**What**

Listing memory stores previously returned each store's id, description and whether it is writable. It now also returns the path of that store's index document, the pointer file the model is asked to keep current with one-line entries. Behind the `tengu_haze_glass` gate, which is false in this build.

**Evidence**

`whether each is writable or read-only, and the path of each store's index document`

- Flag `tengu_haze_glass`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Memory
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Socket peers are identified by pid, start time and ancestor chain on Linux

On Linux, incoming session messages can be traced to the sending process and its ancestors.

**What**

Incoming connections on the cross-session messaging socket can now be attributed to the process tree that sent them. New helpers read `/proc/<pid>/stat` for the process start time, used as a liveness token, and walk up to 12 ancestor pids; the pid and chain are captured once per connection and passed into message handling.

**Details**

- The `/proc` paths are assembled at runtime, so on non-Linux the reads simply fail and the values come back undefined.
- Connection handling sits inside the cross-session messaging path, which is off by default.

**Evidence**

`[peer-cred] peer pid unavailable (fd=`

- Area: Cross-Session Messaging
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5

### Second LSP tool schema, one strict shape per operation

The language-server tool gained a per-operation schema, but nothing chooses it over the old one.

**What**

A new schema for the language-server tool splits it into a separate strict object per operation (goToDefinition, findReferences, hover, documentSymbol, workspaceSymbol, goToImplementation, prepareCallHierarchy, incomingCalls, outgoingCalls), each with its own field descriptions, so arguments are validated per operation instead of against one flat shape. The old flat schema is still in the build and nothing in this group decides which one the tool presents.

**Details**

- The workspaceSymbol variant drops the "(workspaceSymbol only)" qualifier from its `query` description, since the field now exists only there.

**Evidence**

`The symbol name or partial name to search for. Most language servers return no results for an empty query.`

- Area: LSP Tools
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### Status text for a large workflow

Status text for an oversized workflow exists, pointing at /workflows to stop it.

**What**

Status strings for a large workflow are added, built from a prefix constant plus fixed text, with the longer form pointing at `/workflows` to stop it. Nothing in this group shows when it is displayed.

**Evidence**

`Large workflow`

- Area: Workflows
- Names: `/workflows`
- Tier: Not switched on
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### Two keybinding tables have a compiled-out entry

Two shortcut lists have a hole where a conditional keybinding was compiled out.

**What**

The chat keybinding map, between the cycle-mode and image-paste bindings, and the confirm-dialog shortcut list each contain an empty placeholder where a conditional entry was removed at build time. Whatever shortcut belongs there does not exist in this build.

**Evidence**

`chat:imagePaste`

- Area: Terminal UI
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### New artifact login messages are defined but not yet shown

Three clearer artifact login messages are written but never displayed; you still see the old one.

**What**

Three replacement messages explaining why artifacts need a claude.ai login are defined, covering the plain case, a remote session authenticating through the machine that launched it, and a credential injected by the host environment that takes precedence and cannot be changed. Only the definitions and the module export table appear in this build, with no call site, so the old single message is still what you see.

**Details**

- A fourth builder interpolates the name of the competing credential source into the message.

**Evidence**

`Artifacts need a claude.ai login, but this session authenticates with a credential injected by its host environment, which takes precedence and cannot be changed here. Start a session that is signed in to claude.ai to publish or read artifacts.`

- Area: Artifacts
- Tier: Not switched on
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### Two new remote-config gates for bridge event forwarding

Two new server-controlled switches govern whether reset and rate-limit events get forwarded.

**What**

Two new flags, `tengu_luminous_seal` and `tengu_composed_quail`, each falling back to on in this build. The first decides whether conversation reset events are emitted and forwarded and whether they survive the task-event filter; the second decides whether rate limit events are forwarded. Nothing in the build sets either one, so the real values come from server config.

**Evidence**

`tengu_luminous_seal`

- Flag `tengu_luminous_seal`: Off by default, switched on for this account (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: off) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Flag `tengu_composed_quail`: Off by default, switched on for this account (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: off) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Remote Bridge
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5

### AskUserQuestion dialog fallback cannot fire in this build

A fallback to the question dialog when auto-classification is unavailable can never fire here.

**What**

New code lets a question fall back to the interactive dialog when the auto-mode classifier is unavailable or the conversation is too long for it, instead of failing. The condition negates a helper that always returns true here, so the branch is unreachable as shipped.

**Details**

- the separate classifier switch remains, readable from `CLAUDE_CODE_AUTO_MODE_CLASSIFY_ASK_USER_QUESTION` or the `classifyAskUserQuestion` field of the auto mode config

**Evidence**

`Auto mode classifier transcript too long for AskUserQuestion, falling back to the question dialog`

- Area: Auto Mode
- Names: `CLAUDE_CODE_AUTO_MODE_CLASSIFY_ASK_USER_QUESTION`
- Tier: Not switched on
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### Directory sync can apply a file seed and record the result

A directory can be seeded with a listed set of files, skipping anything already there.

**What**

New logic waits for a list of files to seed into a directory, creates each one under a destination whose real path is checked, and then records a summary of what happened. It only creates files: anything already present is skipped and anything escaping the destination path is rejected. Nothing in this build was observed turning it on.

**Details**

- Polls for the file list, applies mode bits to each created file, and writes a ready record counting files applied, skipped and missing.
- Outcomes are reported as complete, timeout, or bad_manifest when the file list is malformed, each with a matching telemetry event.

**Evidence**

`tengu_dir_sync_apply_start`

- Area: Cloud Sessions
- Tier: Not switched on
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### Remote bridge peers documented as reply-only

Remote bridge sessions are described as reply-only and unreachable by name.

**What**

The tool that lists other agent sessions now describes remote bridge sessions as ones you can only reply to, and states that no connector can reach them by name. This wording is only reachable when the cross-session feature is on, via the `tengu_harbor_kite` server flag or `CLAUDE_CODE_HARBOR_KITE`.

**Evidence**

`reply-only`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.224; this account: on, anonymous baseline: on, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Cross-Session Messaging
- Names: `CLAUDE_CODE_HARBOR_KITE`
- Tier: Not switched on
- Useful: 1/5
- Signal: 2/5
- Present in the build but not switched on

## Internal Changes

### Git credentials are supplied by an inline helper instead of a script

Remote git operations no longer write a credential helper script to disk, passing an inline function instead.

**What**

Remote and cloud git operations no longer write a helper script to disk. They pass an inline shell function on the command line that emits a fixed username and reads the password from a named environment variable, after clearing any existing credential helper for the remote.

**Evidence**

`printf 'username=unused`

- Area: Git
- Tier: Under the hood
- Useful: 3/5
- Signal: 3/5

### Startup no longer waits on org-memory discovery

Startup no longer waits on org memory discovery; the wait depends only on your configured memory stores.

**What**

The startup wait for a first memory store pull used to also trigger when org-memory discovery marked stores as pending. That latch and its setter were removed, so the wait now depends only on whether `CLAUDE_MEMORY_STORES` is set.

**Evidence**

`markDiscoveredStoresPending`

- Area: Memory
- Names: `CLAUDE_MEMORY_STORES`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Artifact skill text no longer promises a per-viewer consent prompt

Artifact docs no longer claim each viewer is asked for consent before a page updates itself.

**What**

Workshop and whiteboard skill and template text used to say the browser asks each viewer for consent before a page may update itself. That sentence is gone; only the server-side writer gate is described now.

**Details**

- The whiteboard template sends a one-way analytics message when a board is sent to Claude, which the surrounding shell forwards only if the event name is on its allowlist.

**Evidence**

`the shell enforces the writer gate server-side`

- Area: Artifacts
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Memory store descriptors gain visibility and prompt-index fields

Memory store records can now carry a visibility setting and a prompt index path.

**What**

The schema describing a memory store now carries a path, a read-write or read-only mode, a kind and a grouping id, plus optional visibility and prompt-index fields bounded to 64 and 512 characters. Both new fields fall back to undefined on bad input, so a malformed value degrades instead of failing the whole parse.

**Evidence**

`grouping_id`

- Area: Memory
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Control-tag sanitizing rules are now shared

The rules that neutralise control tags in subagent output are now shared with a new assistant-output scanner.

**What**

The rules that neutralize control tags and permission-escalation phrasing in subagent output are now one shared list, reused with fresh pattern copies by a new scanner for assistant output. Subagent behaviour is unchanged.

**Details**

- The escalation set covers settings JSON, `bypassPermissions`, `--dangerously-skip-permissions` and permission allow/deny phrasing.

**Evidence**

`escalation-pattern`

- Area: Permissions
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Bridge close codes distinguish auth failure from recoverable stalls

Remote bridge disconnects now use distinct close codes for session problems versus expired tokens.

**What**

When the remote-control connection hits a terminal request-path condition, the close code now reflects the cause: session or epoch problems close with 4090, expired or exhausted tokens with 4094.

**Details**

- Both places that build the client opt into typed close codes, so the mapping is what runs; the untyped path still closes with 4090 for everything.

**Evidence**

`terminal request-path condition`

- Area: Remote Control
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Tool-search telemetry records the real mode and a finer skip reason

Skipped tool-search events now report the real mode in effect and a more precise skip reason.

**What**

When tool search is skipped, the event now reports the mode actually in effect, derived from the `ENABLE_TOOL_SEARCH` environment variable and one of `tst`, `tst-auto` or `standard`, rather than always reporting standard. It also tells apart a request with no tools from one where the search tool is not registered.

**Evidence**

`no_tools_in_request`

- Area: Tool Search
- Names: `ENABLE_TOOL_SEARCH`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Remote settings responses carry the identity that fetched them

Recorded consent is now attributed to the identity that actually fetched the remote settings.

**What**

The remote settings fetcher now returns the identity used for the request on every path, including a cached 304, an empty 204 or 404, a parsed settings body and the caught-404 fallback. The caller hands it to the consent-recording step, so a recorded consent is attributed to the identity that actually fetched the settings.

**Evidence**

`consentIdentity`

- Area: Settings
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Telemetry for how remote-control autostart was decided

Whether remote control auto-started, and why, is now recorded.

**What**

The autostart decision now reports both its value and where the value came from, so the reason a session did or did not start in remote-control mode is recorded.

**Details**

- The event records the explicit setting and its source, the default and its source, plus mirror, reattach and the resolved result of off, mirror or full.
- The default still comes from the `tengu_cobalt_harbor` flag, which is off unless remote config says otherwise.
- One of the possible sources, a persistent remote session, cannot be reached: the check that would select it always returns false in this build.

**Evidence**

`persistent_remote_session`

- Flag `tengu_cobalt_harbor`: Off in both readings (read for one account on one subscription tier against v2.1.224; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Remote Control
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Worker shutdowns carry a typed reason

Background worker exits now carry a typed reason such as expired token or epoch conflict.

**What**

Fatal exits now pass a reason such as session not found, token expired, auth exhausted, or an epoch conflict or stale epoch, instead of terminating with no explanation.

**Details**

- The remote bridge maps auth-related reasons to WebSocket close code 4094 and the rest to 4090, so recovery can pick the right path.

**Evidence**

`causeTypedCloseCodes`

- Area: Remote Control
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### 409 conflicts read their cause from a header

Conflict responses now read their cause from a header, distinguishing a live worker from a stale registration.

**What**

Conflict responses are now attributed from an `x-ccr-conflict-reason` header, falling back to parsing the reason out of the JSON body, which separates being superseded by a live worker from a stale registration with no successor.

**Evidence**

`x-ccr-conflict-reason`

- Area: Remote Control
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Plans and session records read through the keyed store

Plans and session records are now addressable through the keyed storage layer, not just as files.

**What**

Plans and session files are now addressable through Claude Code's keyed storage layer rather than only as raw files on disk.

**Details**

- Two namespaces were registered, `plan` (keyed by name) and `session` (keyed by file), both classified as plain.
- Priming plan slug collisions lists entries from the store, but only when a store handle is passed and the plans directory is the default one; otherwise it still reads the directory directly, and it also falls back to that on a store error logged as "primePlanSlugCollisions: v5 list failed: ".
- The old raw-file reader for plan workshop documents was deleted.

**Evidence**

`primePlanSlugCollisions: v5 list failed: `

- Area: Internals
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Ultrareview promo surfaces now report impressions

Each place showing the ultrareview prompt now reports that it appeared; the entitlement gate is unchanged.

**What**

Each place that shows the ultrareview prompt now fires an event naming that surface. The prompts themselves remain behind the existing remote-sessions entitlement check.

**Evidence**

`tengu_ultrareview_awareness_shown`

- Area: Code Review
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Settings presence checks can read through the newer settings store

Checks for explicitly set settings keys can now read from the newer settings store.

**What**

The check for whether a key such as `cleanupPeriodDays` is written explicitly in a settings file is now asynchronous and can be given a reader, in which case user settings come from the newer store instead of the file on disk. A read failure is logged and treated as unknown rather than as absent, and the retention cleanup gate became async to match.

**Evidence**

`rawSettingsKeyPresence: v5 user-settings read failed`

- Area: Settings
- Names: `cleanupPeriodDays`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Artifact share status reports the viewer's role

Artifact share status now reports the viewer's permission role as well as the share mode.

**What**

Share status responses now include the permission role alongside the share mode.

**Evidence**

`role: r.data.perm?.role`

- Area: Artifacts
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### New MCP error code

MCP clients now recognise a new denied-issuer-echo error code.

**What**

`ISSUER_ECHO_DENIED` was added to the recognised error codes of both MCP client generations, alongside the existing rejected-auth-header and rejected-bearer codes.

**Evidence**

`ISSUER_ECHO_DENIED`

- Area: MCP
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### SendMessage records why a message was not delivered

Undelivered agent messages now record why, such as an unreachable recipient or a failed mailbox write.

**What**

The tool that sends messages between agents now reports a delivery outcome to telemetry instead of failing silently, using `not_reachable` when the recipient is not in the roster or cannot be resolved and `mailbox_write_failed` when the write itself fails.

**Evidence**

`mailbox_write_failed`

- Area: Agents
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Session history check reads raw bytes

Checking whether a session has history is faster because it scans raw bytes instead of decoding everything.

**What**

Deciding whether a stored session has any history now scans raw record bytes for the user and assistant type markers instead of decoding every record to text, and pages more aggressively: 64KB for the first page, 4MB for later ones.

**Details**

- The filesystem transcript locator gained an optional store backend, so it can also find transcripts held in a session store rather than only on disk.

**Evidence**

`"type":"assistant"`

- Area: Sessions
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Linux sandbox mount computation reworked around symlinks

Linux sandbox write paths now match both literal and resolved forms, so symlinked directories behave consistently.

**What**

On Linux, sandbox write paths have trailing slashes stripped before binding, with a bare `/` preserved, and allowed write paths are matched against both their literal and fully resolved forms so symlinked directories are shadowed consistently.

**Details**

- Applies to the bubblewrap sandbox only.
- The path lists, including directories considered for read-denial, are now computed on demand rather than upfront.

**Evidence**

`allowedWritePathsBothForms`

- Area: Sandbox
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Settings validation knows which file it is checking

Settings warnings can now name which settings file caused them.

**What**

The settings validator is now given the file path as well as the warning collector, so warnings can name the settings file they came from.

**Evidence**

`skipMcpServerEntryFilter`

- Area: Settings
- Tier: Under the hood
- Useful: 3/5
- Signal: 2/5

### REPL bridge failures record whether they were an auth problem

Bridge failures are now labelled as authentication or terminal, though nothing reads the label yet.

**What**

Bridge failures are now tagged as either an authentication problem or a terminal one, alongside the existing error. Missing OAuth token, missing organization and unenrolled trusted device count as auth; everything else defaults to terminal. Nothing user-visible changes yet.

**Evidence**

`replBridgeErrorKind`

- Area: Remote Control
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### Remote-control registration reports a bridge epoch

Remote control registration and heartbeats now include an identifier for the current bridge lifetime.

**What**

Registration and heartbeat payloads sent by the remote-control bridge now include a `bridge_epoch` value identifying the current bridge lifetime.

**Details**

- The special case that pushed `thinking_tokens` system messages straight into the bridge SDK stream, along with its dedicated error log, was removed; those messages now go through the normal enqueue path.

**Evidence**

`bridge_epoch`

- Area: Remote Control
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5

### Workshop document writes counted apart from scratchpad writes

Session metrics count workshop document edits separately from scratchpad edits.

**What**

Session metrics now count workshop document writes and their added and removed lines separately, reported only when non-zero.

**Details**

- Plan-mode end-of-turn guidance now takes the publish wording from its caller rather than using a fixed string.

**Evidence**

`workshopWriteCount`

- Area: Artifacts
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5

### Dropped unverified bridge events are now counted

Unrecognised bridge events rejected by device attestation are now logged and counted instead of vanishing.

**What**

When device attestation enforcement rejects an event it does not recognise, it is now logged as a warning and counted once per process with a key for the kind of payload, instead of vanishing. Only reached when attestation is configured to enforce.

**Evidence**

`[bridge:attestation] DROPPING unverified `

- Area: Remote Control
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5

### Runner ingress tokens are written per epoch

Runner session tokens are written to per-epoch files instead of one shared file.

**What**

The runner now writes its session ingress token to a file named with an epoch suffix instead of one shared file. The write is best effort, and a failure logs that staged-file fetches are degraded.

**Evidence**

`.session_ingress_token.e`

- Area: Runners
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5

### MCP telemetry identifies servers by a hash, not the base URL

MCP telemetry identifies servers by a hash rather than their raw URL.

**What**

The helper that tagged MCP events with a server's base URL was replaced, in both of its copies, by one that also carries a hashed server key. Server identity in telemetry no longer depends on the raw URL.

**Details**

- The hash is attached to `tengu_mcp_listen_reopen` events and is the preferred key for per-server rate limiting when forwarding logs, with the base URL used only as a fallback.

**Evidence**

`mcpServerKeyHash`

- Area: MCP
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### MCP events forwarded to Datadog, with identifying fields removed and a per-server cap

MCP events are forwarded for logging with identifying fields stripped, and skipped entirely if analytics are off.

**What**

MCP events are now admitted to log forwarding, and every payload has its identifying fields deleted before send. Forwarding is skipped entirely when analytics are disabled.

**Details**

- Newly allowed: `tengu_mcp_degraded`, `tengu_mcp_list_changed`, `tengu_mcp_listen_reopen`, `tengu_mcp_sdk_generation`, `tengu_mcp_server_connection_failed`, `tengu_mcp_server_connection_succeeded`, `tengu_mcp_tripwire`.
- Stripped fields include the server name, base URL, hashed server key, attribution hashes, hashed error details and a server digest.
- Five of the MCP events are capped per event-and-server pair at 10 per 60 seconds, tracked over at most 200 keys; the number of suppressed events rides along on the next one that gets through as `droppedSinceLastForward`.

**Evidence**

`PEER_RATE_BOUND_EVENTS`

- Area: Telemetry
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### MCP client version mismatch now reports before it throws

An MCP client version mismatch now reports the two versions before raising its error.

**What**

When the MCP runtime resolves to one client generation but the loaded module is from the other, a once-per-process `tengu_mcp_tripwire` event is emitted, carrying the expected and loaded generations (normalised to v1, v2 or other), immediately before the existing error is raised. The event name was added to the allow-listed list.

**Evidence**

`tengu_mcp_tripwire`

- Area: MCP
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Simpler transcript edit operation

Removing messages from conversation history is now a plain drop-and-append with no boundary trimming.

**What**

The operation that removed messages from conversation history was replaced with one that simply drops the named messages and appends, without the earlier filtering and trim-to-boundary step.

**Evidence**

`remove-uuids-and-append`

- Area: Compaction
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Plugin manifest loading is instrumented

Plugin manifest loading now reports whether the file was unreadable, invalid JSON or the wrong shape.

**What**

Loading a plugin manifest now reports success, and reports failures separately for an unreadable file, invalid JSON and a manifest that does not match the expected shape. Success is not reported when the caller is only probing for a manifest.

**Evidence**

`plugin_load_manifest_schema_invalid`

- Area: Plugins
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Settings warnings are assembled asynchronously

Settings warnings now wait for retention config before deciding whether to warn about paused cleanup.

**What**

Collecting settings-file warnings now waits for the retention configuration to be determined before deciding whether to show the notice that transcript retention cleanup is paused.

**Evidence**

`Transcript retention cleanup is paused until the settings errors above are fixed`

- Area: Settings
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### MCP connection failures report a sibling probe result

Failed MCP connections now report an extra probe result for easier diagnosis.

**What**

The telemetry event for a failed MCP server connection carries one more field describing the probe, giving more detail when a server will not connect.

**Details**

- `probeSibling` joins the existing `negotiationMode`, `probeFellBack`, `errorCode` and `errorClassName` fields.
- The field name does not appear in the previous build.

**Evidence**

`probeSibling`

- Area: MCP
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Telemetry forwarding now strips identifying fields and caps MCP event volume

Internal metrics forwarding strips identifying fields, buckets tool names and caps per-server MCP volume.

**What**

The internal metrics forwarder, reached only on first-party accounts, now removes a list of potentially identifying fields before sending, collapses tool and feature names into generic buckets, and limits how often per-server MCP events can be sent.

**Details**

- Deleted fields include the MCP server base URL and name, the MCP tool name, a hashed error message, and two hashed skill names.
- Any tool name collapses to `mcp` when the event is flagged as MCP, where previously only names beginning with `mcp__` were collapsed. Names beginning with `skill__` become `skill`, and feature names beginning with `tool_skill_` become `tool_skill`, except `tool_skill_tool`.
- Five MCP events are capped at 10 forwards per 60 second window per server, tracked across at most 200 server keys, and the count suppressed since the last send rides along as `droppedSinceLastForward`.

**Evidence**

`stripPiiFieldsForDatadog`

- Area: Telemetry
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Auto-memory prompt drops a self-narration step

The memory prompt no longer asks Claude to narrate a save-or-not verdict before acting.

**What**

The prompt behind persistent memory no longer asks the model to state a save or no-save verdict to itself before acting. The requirement to save in the same reply is unchanged.

**Evidence**

`did the user's latest message teach you a durable, applicable lesson?`

- Area: Memory
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### MCP telemetry is rate limited and tool names bucketed

MCP telemetry now drops events past a rate limit and reports how many were dropped.

**What**

Forwarded events for MCP-style tools now pass through a sliding window per event and server; over the limit, events are dropped and counted, and the next event that gets through reports how many were dropped since the last one.

**Details**

- The window map is size-bounded and evicts its oldest key.
- Any tool name beginning `skill__` is reported as "skill", and feature names starting `tool_skill_` collapse to "tool_skill", except `tool_skill_tool`.
- A denylist of fields is stripped from every payload before it is sent.

**Evidence**

`droppedSinceLastForward`

- Area: MCP
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### PDF page extraction and several caches read through the storage layer

PDF page images and several caches now read through a shared storage layer instead of raw files.

**What**

Extracted PDF page images are now listed and read back through the shared storage abstraction rather than direct filesystem calls, with a guard that fails if the extraction directory sits outside the session's tool-results store. Model-capability caches and the closed-issues cache gained the same storage-backed paths; all of them fall back to the previous filesystem path when no storage handle is supplied.

**Evidence**

`PDF extraction directory is outside the session tool-results store`

- Area: Internals
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Cached data moves to a keyed storage backend

Changelog, model and issue caches now read from a keyed storage backend when one exists.

**What**

Caches for the closed-issues list, model capabilities, gateway models and the changelog now look themselves up by a namespaced key and read from a storage backend when one is available, falling back to the previous plain-file reader when it is not.

**Details**

- Whether the backend path or the file path runs is decided by whether a backend is present at runtime.
- Failures coming from the backend are now reported distinctly from unexpected errors.

**Evidence**

`closed-issues cache read failed (v5 backend error)`

- Area: Internals
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Bridge websocket closes record why

Remote bridge socket closes now record a cause alongside the numeric code.

**What**

The close event for the v2 remote bridge transport now carries a cause alongside the numeric close code, and the deferred-close path stores both.

**Evidence**

`tengu_bridge_repl_ws_closed`

- Area: Remote Control
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Auto-mode telemetry marks MCP tools

Auto-mode approval events now note whether the tool came from an MCP server.

**What**

Two auto-mode events, subsequent approval and malformed tool input, now record whether the tool came from an MCP server, defaulting to false when the tool does not say. The malformed-input event also runs the tool name through the sanitiser.

**Evidence**

`tengu_auto_mode_malformed_tool_input`

- Area: Permissions
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### SendMessage telemetry marks blocked waits and resumes

Message delivery telemetry can flag when the sender blocked waiting for completion.

**What**

Message delivery telemetry can now flag that the caller waited synchronously for completion, alongside the existing route and duration fields.

**Details**

- Agent-resume branches report on both success and failure under the route `resume`.
- The evicted-agent mailbox path sets the blocked-wait flag.

**Evidence**

`blocked_wait`

- Area: Agents
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Grading results record whether a criterion was scored

Evaluation grader output now says explicitly whether each criterion was scored.

**What**

Evaluation grader output now carries an explicit flag for whether each criterion was actually scored, derived as the negation of the existing "with only" flag.

**Details**

- The result schema gains an optional boolean `scored` alongside `withOnly`, `judgeVotes` and `evidence`.

**Evidence**

`withOnly`

- Area: Elsewhere
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Mailbox lock-release failures name the operation

Failed mailbox lock releases now name which maintenance job failed.

**What**

Two mailbox maintenance paths now release their lock through a shared helper that takes an operation name, so a failed release is attributed to the specific job, such as pruning invalid entries or marking messages read.

**Evidence**

`pruneInvalidMailboxEntries`

- Area: Agents
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Auto-mode telemetry records whether the tool was MCP

More auto-mode permission events record whether the tool was from an MCP server.

**What**

Auto-mode decision, fallback-to-ask and denial-limit events now carry a flag saying whether the tool came from an MCP server, defaulting to false, alongside the existing tool name and denial counts.

**Evidence**

`tengu_auto_mode_denial_limit_exceeded`, `tengu_auto_mode_fallback_to_ask`

- Area: Permissions
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Concurrent-session pid file can go through the v5 store

Session registration can write its process-id file through the newer store when one is available.

**What**

When a storage handle is supplied, session registration writes its process-id file through that store and throws on failure; without one it falls back to writing the file directly to disk as before.

**Evidence**

`v5 pid-file write failed`

- Area: Internals
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### PDF extraction and the retention sweep take a storage handle

PDF extraction and the retention sweep now receive a storage handle instead of finding one themselves.

**What**

Both PDF page-extraction call sites now pass a storage handle through, and the retention sweep and one of its phases take that state as a parameter instead of looking it up themselves.

**Evidence**

`tengu_pdf_page_extraction`

- Area: Internals
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### ZIP extraction reports what it wrote

Plugin ZIP extraction now returns the exact files it wrote, not just a count.

**What**

The plugin ZIP extractor now returns the list of relative paths it extracted rather than just logging a count, so callers can act on the exact files.

**Evidence**

`Extracted ZIP to `

- Area: Plugins
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### PR review artifact assets moved, not changed

PR review skill docs and its HTML template moved location but are byte-identical.

**What**

The PR review skill documents and the bundled HTML template, including the staleness marker, decision pills and the in-page Approve on GitHub control, appear as new symbols but are byte-for-byte the same assets as the previous release. Nothing about generated PR review artifacts changes.

**Evidence**

`prr-stamp`

- Area: Code Review
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Version bump to 2.1.224

The version is now 2.1.224 everywhere it is printed.

**What**

The embedded version is now 2.1.224, with a new build time and git SHA, updated everywhere they are printed: version output, user agents, update messages, feedback bundles, the daemon start log and version warning, and the control-protocol binary version response.

**Evidence**

`BUILD_TIME: "2026-08-06T01:05:53Z"`, `2026-08-06T01:05:53Z`, `VERSION: "2.1.224"`

- Area: Internals
- Tier: Under the hood
- Useful: 2/5
- Signal: 1/5

### Exec wrapper can redirect child output streams

Child processes can have their output redirected, so Wayland clipboard writes stop spewing output.

**What**

The shared helper for running child processes now forwards `stdout` and `stderr` options, used for example to ignore output from Wayland clipboard writes.

**Details**

- These join the existing maxBuffer and stripFinalNewline passthroughs.

**Evidence**

`stripFinalNewline`

- Area: Elsewhere
- Tier: Under the hood
- Useful: 2/5
- Signal: 1/5

### Pasted image IDs come from a counter

Pasted image numbering comes from a counter instead of rescanning your whole conversation.

**What**

The code that worked out the next pasted-image ID by scanning every user message for the highest one is gone; the UI is handed the last issued ID directly, so IDs no longer depend on re-reading conversation history.

**Evidence**

`lastMintedPasteId`

- Area: Input
- Tier: Under the hood
- Useful: 2/5
- Signal: 1/5

### Plan-exit dialog rebuilt on plain hooks

The plan-exit dialog was rebuilt on plain hooks and its feedback box now gets paste cleanup.

**What**

The dialog shown when Claude proposes leaving plan mode was rewritten from heavily memoized generated code into ordinary hooks and inline callbacks. Behaviour and telemetry are unchanged, and its feedback field is now registered for paste cleanup.

**Evidence**

`tengu_plan_exit_dialog_shown`

- Area: Plan Mode
- Tier: Under the hood
- Useful: 2/5
- Signal: 1/5

### Schemas across the binary rewritten onto internal builders

Nearly every schema was rewritten onto smaller internal builders with no change in behaviour.

**What**

Nearly every declared schema in the build, covering tools, hooks, settings, MCP config and message envelopes, plugin manifests, browser listings, LSP operations and sandbox settings, was rewritten from the zod-style `object`/`string`/`enum`/`array`/`union`/`discriminatedUnion`/`preprocess`/`strictObject` calls onto compact internal equivalents. Field names, description text and validation messages are unchanged, so validated shapes and error output stay the same.

**Details**

- Some explicit unions became a helper call, and strict objects moved to a strict-object helper.
- The MCP v1/v2 module tripwire is untouched by the rewrite.

**Evidence**

`protocol must be an RFC 6455 token`, `url contains control characters that would be hidden in the approval dialog`

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Schema definitions rewritten to use direct helper calls

Schema definitions now call validation helpers directly; nothing behaves differently.

**What**

Most schema definitions were mechanically switched from a namespaced validation object to directly imported helpers, and several initialiser blocks were relocated intact. Validation messages, limits and defaults are unchanged, so nothing is visible from the outside.

**Evidence**

`segments must match [A-Za-z0-9._-]+ and must not be . or ..`

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Tool schemas rewritten onto shorter helpers

Tool schemas were mechanically shortened with no change to descriptions or constraints.

**What**

Tool input and output schemas across the product were mechanically converted to shorter schema helper calls. Descriptions and constraints are unchanged and no behaviour differs.

**Evidence**

`Processed result from applying the prompt to the content`

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Tool schemas moved onto shared constructors

Dozens of tool schemas were rewritten onto shared helpers purely to shrink the bundle.

**What**

Dozens of tool input and output schemas were rewritten to use short shared schema helpers. Descriptions and validation look unchanged; this is a size and consistency refactor.

**Evidence**

`project_search: knowledge-base query`

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Schema definitions switched to direct imports

Skill frontmatter and several other schemas now import validation helpers directly; nothing changed.

**What**

Tool input schemas, skill frontmatter, the Chrome install dialog and GitHub session schemas were rewritten to import validation helpers directly instead of through a namespace. No behaviour or message changed.

**Evidence**

`Unsupported schema: `

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Schemas rewritten onto internal helpers

More schemas were rewritten onto short helpers for bundle size only.

**What**

A large set of schema definitions was rewritten from the builder style onto short internal helpers. Shapes are unchanged; this is bundling only.

**Evidence**

`MCP tool execution result`

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Schema declarations rewritten to shorter helpers

Schema declarations across the bundle were shortened with no change to accepted values.

**What**

Schema definitions across the bundle were rewritten from long-form builders to compact helpers, covering tool inputs and outputs, dialog payloads, plugin manifests, scheduled job arguments, todo lists and lock files. Field names, descriptions and accepted values are unchanged.

**Evidence**

`Job ID returned by CronCreate.`

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### All schema definitions rewritten to a smaller builder style

Every schema in the bundle moved to standalone builder functions to cut size.

**What**

Every schema in the bundle, covering tool inputs and outputs, plugin manifests, settings, memory and artifacts, was rewritten from the namespaced schema builders to standalone functions. Field names, descriptions and validation behave the same; this is a bundle-size change.

**Details**

- The previous build has 1486 uses of the namespaced string builder and 569 of the namespaced object builder; this build has none of either, with roughly 963 direct object builder call sites.
- Example: a hook type that was a namespaced literal with the description "MCP tool hook type" is now a bare literal call, and its server field a bare string call described as the name of an already-configured MCP server to invoke.
- The error instance check switched to the corresponding direct class. Several hunks in this group are only the resulting reformatting.

**Evidence**

`w.object(`, `MCP tool hook type`

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Tool and config schemas rebuilt on internal builders

More tool and config schemas moved onto internal builders with identical shapes.

**What**

Dozens of schema definitions were rewritten from the bundled validation library's object API onto short internal builders. Shapes and descriptions are unchanged, so nothing behaves differently.

**Details**

- Covers object, strict object, string, array, enum, custom, boolean constructors and instance checks.
- Descriptions are byte-identical, for example the MCP refresh tool's field still reads "Optional server name: refresh only this server. Omit to refresh all connected servers."

**Evidence**

`Optional server name: refresh only this server. Omit to refresh all connected servers.`

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Remote Control startup source labels moved to a lookup table

Labels for where the remote-control startup setting came from now live in one table.

**What**

The short names reported for where the `remoteControlAtStartup` setting came from (policy, flag, user) now come from one table instead of scattered strings. The warning that repository-scoped settings cannot enable Remote Control is unchanged.

**Evidence**

`policySettings: "policy"`

- Area: Remote Control
- Names: `remoteControlAtStartup`
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Schema construction moved off a shared namespace

All 686 namespaced schema calls became direct helpers, which explains most of this release's diff.

**What**

All 686 namespaced schema calls were replaced with direct helper functions. Behaviour is unchanged, but it accounts for the very large number of one-line schema diffs in this release.

**Evidence**

`w.custom(`

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Counter for managed settings discarded mid-fetch

A discarded managed-settings fetch now gets its own error counter.

**What**

A separate error counter is now emitted when a managed settings fetch is thrown away because the cache was reset while it was in flight.

**Evidence**

`reset_during_fetch`

- Area: Settings
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### MCP version mismatch is reported before it throws

MCP version mismatches are reported to telemetry before the same error is raised.

**What**

The check that the loaded MCP client module matches the MCP version Claude Code expects now reports the expected and observed versions to telemetry before raising the same error as before.

**Evidence**

`MCP runtime accessor tripwire: resolved generation is v2 but the loaded client module does not carry MCP_TREE_ID v2`

- Area: MCP
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### SDK schema definitions restructured

SDK message and control-protocol schemas were restructured with no change to fields or defaults.

**What**

The message, hook payload and control protocol schemas are now built from directly bound helpers rather than one shared namespace object. Field names, defaults, optionality and descriptions are unchanged.

**Evidence**

`Controls Claude's thinking/reasoning behavior. When set, takes precedence over the deprecated maxThinkingTokens.`

- Area: Elsewhere
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Schema definitions rewritten with shorter helpers

Almost every input schema was shortened, which accounts for most of this release's diff size.

**What**

Almost every input schema in the bundle was rewritten to use short internal helpers instead of long-form calls, including Grep's parameters, the workflow tool, feedback drafts, notebook edits and session records. Field names, descriptions and validation are unchanged. This accounts for most of the diff size in this release.

**Evidence**

`Enable multiline mode where . matches newlines and patterns can span lines (rg -U --multiline-dotall). Default: false.`

- Area: Build
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

## Removed

### Private memory directory dropped from the prompt

Claude no longer mentions a private memory folder, and team memory is set up purely through the environment.

**What**

Claude no longer describes a separate private memory directory alongside team memory, and the path that discovered and mounted memory stores at runtime is gone. Team memory sync is now configured only through the environment.

**Details**

- All prompt branches mentioning a private memory area were removed; the remaining text states plainly that there is no separate private memory directory in the session.
- The store picker and its telemetry went with the discovery path; the team memory sync watcher now always reports its config source as env.
- The memory-type descriptions and the instruction to cite memory with tags moved into shared constants.

**Evidence**

`There is no separate private memory directory in this session.`

- Area: Memory
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### `conversation_reset` events are no longer forwarded

Conversation reset events no longer turn into outbound messages, so nothing downstream hears about a new conversation id.

**What**

Two query-event handlers dropped the branch that turned a `conversation_reset` event into an outbound message carrying a new conversation id. Anything downstream that watched for that message will no longer see it; only `apply_flag_settings` is still translated.

**Evidence**

`apply_flag_settings`

- Area: Session Events
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Bash prompt line about where command output goes

Claude is no longer told that command output goes to it rather than to you.

**What**

The Bash tool prompt no longer tells the model that command output is shown to it rather than reliably to you. The surrounding guidance bullets are unchanged.

**Evidence**

`- Commands are cheap to run and their errors are informative: run the straightforward command rather than perfecting it mentally first, and adjust from what it prints.`

- Area: Elsewhere
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Agent spawn counters deleted

The counter tracking spawned subagents was deleted and nothing replaced it.

**What**

The running count of how many subagents had been spawned is gone; nothing reads or replaces it.

**Details**

- The task registry and its no-op stub both drop `incrementTotalAgentSpawns`, `getTotalAgentSpawns` and `resetTotalAgentSpawns`.
- The subagent spawn path no longer increments anything.

**Evidence**

`getTotalAgentSpawns`

- Area: Subagents
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5
