# Claude Code v2.1.236

> Claude Code v2.1.236, released 19 Aug 2026 (2026-08-19). 200 entries read out of the shipped bundle. Unofficial, and not affiliated with Anthropic.

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

Thirty-nine entries are in this build but not switched on. A startup tip pointing at a `/design` command for UI mockups needs both an account entitlement and a feature flag that falls back to false. Cross-session idle notices are built end to end: any session here will accept and honour a request to be pinged when another goes idle, but the ability to ask sits behind the `tengu_harbor_kite` gate (forced on by `CLAUDE_CODE_HARBOR_KITE`, with a separate Windows gate). Gzipped API request bodies are decided per request, Bun-only and for string bodies of at least 4096 characters, but both remote gates default to off. Artifact types, a wire format for forwarding local hooks into a cloud session, and a per-model reminder text fed from `CLAUDE_CODE_TOASTY_THIMBLE` are all present and dark.

Of the 99 shipped entries, a few stand out. `ANTHROPIC_DEFAULT_MODEL` now resolves the session model after `--model` and `ANTHROPIC_MODEL` but before the built-in default, so an exported organisation value finally takes effect. PostToolUse hooks can attach a `classifierContext` string to a tool result for the auto-mode permission classifier, capped at 2000 UTF-16 code units per tool call. Self-hosted sandbox workers can now mount shared memory stores at `/mnt/memory/<store-name>/`, re-synced every 15 seconds by default. `CLAUDE_CODE_TUI_TRIAL=fullscreen` starts one session in the fullscreen renderer without saving the choice, and the fullscreen upsell now stops after three showings.

The `live_doc` field is gone from the artifact tool schema and its prompt block, replaced by a marker on the document skill. Private key redaction no longer skips PEM blocks: the patterns were shared across calls and carried a search position, and are now rebuilt on each scan. `--continue` and `--resume` failures print the underlying error and exit with status 1 instead of logging quietly and carrying on. The `live_edit`, `reply` and `resolve` artifact actions re-check that the approved input still names the same action, refusing if a hook or permission rewrite changed it.

Every running session now writes a one-element feature list into the local session registry marking itself able to send idle notices, with no flag check, so builds shipping today become discoverable ahead of the user-facing half. Remote control gained a `detachTerminal` action alongside the other no-ops, plus a helper that filters remembered surfaces against "mobile", "web" and "desktop". The web fetch agent decision moved from a module variable onto per-host state, with `CLAUDE_CODE_WEB_FETCH_AGENT` still winning over the `tengu_clever_orbit` gate. There is also a new endpoint builder for downloading an organization's plugin at an optional pinned version.

## 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.

### Host-supplied context lines in the safety classifier, with live and restored trust levels

The safety classifier now sees notes from the hosting app, treating live ones as possible user consent.

**What**

The application hosting the agent can attach a note after a tool result, and the safety classifier now sees it as its own transcript line. Two kinds exist: `host_context_live`, attached during this session, which may relay real user input and can satisfy a soft block's consent requirement, and `host_context`, restored from saved session state, which never establishes user intent. The classifier prompt describes both. Emission of the live variant has a kill switch, `tengu_disable_live_host_context`.

**Details**

- Notes reach the transcript from a post-tool hook or an in-process tool return.
- Entries are fed to the classifier as `{ type: "host_context", ..., context, live }`.
- The prompt text, both keys, and the entry shape are all absent from 2.1.235.

**Evidence**

`tengu_disable_live_host_context`, `EVa = "host_context_live"`

- Flag `tengu_disable_live_host_context`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Permissions
- Tier: You'll notice
- Useful: 3/5
- Signal: 5/5

### A startup tip pointing at /design for UI work

A startup tip pointing at a `/design` command for mocking up UI is present but nobody sees it.

**What**

A new startup tip with the internal id `claude-design-command` suggests running the `/design` command before building UI, reading "Working on UI? Run /design to mock up a few directions before you build". It is present but inert in this build: showing it needs the `allow_design_sync` account entitlement and then a feature flag whose fallback is false, so no one sees it yet. Alongside it, the tip engine gained two general behaviours that apply to all tips.

**Details**

- The tip carries priority 1 and will not reappear for 15 sessions once shown.
- The second gate is `tengu_omelette_fouet`, and with its fallback of false nothing in this build turns the tip on; the gate function itself shipped in 2.1.235, and only the tip is new here.
- Tips that advertise a command are now shown only when that command actually exists in the running build.
- The tip engine skips any tip recorded in its failed-tips list, so a tip that could not be displayed is not retried.
- This is a distinct tip from the existing `claude-design-contextual`, which links to a claude.ai URL and is gated by `tengu_cedar_plume`.

**Evidence**

`id: "claude-design-command"`, `claude-design-command`

- Flag `tengu_cedar_plume`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Slash Commands
- Names: `/design`
- Tier: Not switched on
- Useful: 3/5
- Signal: 5/5
- Present in the build but not switched on

### Asking another local session to ping you when it goes idle, built end to end but not offered to the model

Your session already honours idle-ping requests from other sessions; only asking for one is gated off.

**What**

A session can be asked to send exactly one notice when it next finishes its turn or exits. The receiving half is live on this build with no flag check at all: any session here will accept and honour such a request. Only the ability to ask is dark, because the tool parameter is added to the schema behind `tengu_harbor_kite` (fallback false; `CLAUDE_CODE_HARBOR_KITE` forces it on; Windows also needs `tengu_harbor_kite_win`, fallback false).

**Details**

- Both halves of the wire protocol, the subscription request and the idle notice itself, are handled unconditionally.
- Refusal messages, a subscription cap and `cross_session_notify_idle` telemetry (reasons include `subscribe_refused_principal`, `self_target_frame`, `own_inbox_unbound`) all ship in this build.
- Subscriptions can only be made from the main conversation, not from a subagent or teammate.
- The fallbacks are what applies with no server value; remote config supplies the real setting.

**Evidence**

`notify_when_idle is only available from the main conversation of this session (not from a subagent or teammate).`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.236; this account: on, anonymous baseline: on, compiled default: on)
- Flag `tengu_harbor_kite_win`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- 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

### PostToolUse hooks can pass context to the auto-mode permission classifier

Your PostToolUse hooks can send extra context to the classifier that decides permissions in auto mode.

**What**

PostToolUse hook responses accept a new optional `classifierContext` string, carried alongside the tool result to the classifier that decides permissions in auto mode. It is capped at 2000 UTF-16 code units shared across all hooks for a single tool call, honored only on synchronous hook responses, and dropped if the paired output rewrite is rejected or superseded. Anyone writing a hook on this build can use it.

**Details**

- Added to both the SDK-facing hook schema and the internal one, so it is documented to hook authors as well as accepted at runtime.
- The description states that relaying a genuine user statement may satisfy a consent bar, but never a hard boundary.
- No feature flag on the schema itself.

**Evidence**

`Host-asserted context shown to the auto-mode permission classifier alongside this tool call's result.`

- Area: Hooks
- Names: `classifierContext`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Self-hosted sandbox workers can now mount memory stores

Shared memory stores now mount on your own sandbox workers, with tunable sync interval and deletion handling.

**What**

Shared memory stores, previously documented as unsupported outside Anthropic-hosted sandboxes, now work on your own infrastructure. The Python, TypeScript, and Go worker downloads each attached store to `/mnt/memory/<store-name>/` and re-syncs it on an interval, default 15s with a 5s floor, tunable via `memory_sync_interval` / `memorySyncIntervalMs` / `MemorySyncInterval`. Deletion handling is set by `memory_sync_deletes` (`enabled` default, `log_only`, `disabled`).

**Details**

- It is a periodic copy, not a live mount: writes become visible to other sessions only after a sync, and conflicts resolve in favour of the store.
- `read_only` is enforced by the worker's own tools, not by filesystem permissions, and memory files are opened with `O_NOFOLLOW`.
- A new per-session credential, `ANTHROPIC_WORK_SECRET`, must be forwarded into sandbox-per-session containers.
- The `ant` CLI worker does not mount stores at all.
- `memory_store` is the only resource type self-hosted environments accept.

**Evidence**

`the worker opens memory files with `O_NOFOLLOW``

- Area: Sandbox
- Names: `memory_sync_interval`, `memorySyncIntervalMs`, `MemorySyncInterval`, `memory_sync_deletes`, `ANTHROPIC_WORK_SECRET`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Remote sessions can push a permission mode to the local session

A cloud session can now change your local session's permission mode, if the mode passes validation.

**What**

A cloud or remote session can now broadcast a permission mode change and have the local session adopt it, provided the mode is one of the allowed values and the local validator accepts it.

**Details**

- Rejected broadcasts are logged rather than applied silently, and accepted ones are logged too.
- The remote control-request path also counts successful `set_permission_mode` calls.
- Only runs in the remote/cloud session wiring; local-only sessions are unaffected.

**Evidence**

`[remote] Applied permission-mode broadcast: `

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

### `ANTHROPIC_DEFAULT_MODEL` sets the model when nothing else does

Set ANTHROPIC_DEFAULT_MODEL and it becomes your model whenever no flag or ANTHROPIC_MODEL applies.

**What**

The model for a session now resolves from the `--model` flag, then the `ANTHROPIC_MODEL` environment variable, then `ANTHROPIC_DEFAULT_MODEL`, and only then the model Claude Code ships with. An organisation or a shell profile that exports the variable will see it take effect in sessions that previously fell through to the built-in default, and the model list marks the choice so you can tell where it came from.

**Details**

- The model list shows the footnote "Set by ANTHROPIC_DEFAULT_MODEL" against the model the variable selected.
- The literal values `default` and `inherit` are treated as unset, so resolution continues past them to the shipped default.
- Startup telemetry records `ANTHROPIC_DEFAULT_MODEL` in its own field, separate from the existing `ANTHROPIC_MODEL` field.
- On Amazon Bedrock and Google Vertex, the check for whether Opus is available to the account is skipped whenever the variable is set.
- Sandboxed child processes spawned for bring-your-own-cloud setups have `ANTHROPIC_DEFAULT_MODEL` stripped from their environment, matching the existing handling of `ANTHROPIC_MODEL`.
- Invoke it inline for a single session, for example `ANTHROPIC_DEFAULT_MODEL=claude-sonnet-4-5 claude`.

**Evidence**

`Set by ANTHROPIC_DEFAULT_MODEL`, `t.model || process.env.ANTHROPIC_MODEL || V.ANTHROPIC_DEFAULT_MODEL`

- Area: Models
- Names: `ANTHROPIC_DEFAULT_MODEL`, `ANTHROPIC_MODEL`, `--model`
- Tier: Use it now
- Useful: 5/5
- Signal: 3/5

### A wire format for forwarding your local hooks into a cloud session, with nothing sending it

Your local hooks could be registered with a cloud session, but nothing sends the messages yet.

**What**

Message shapes are defined for a `claude --cloud` client to register its local hooks with the cloud worker: an inventory of hooks by opaque id (command or HTTP, matcher, timeout, which settings file it came from), a list of vetted in-container templates pinned by sha256 digest, and a lease in seconds, answered with the project directory, working directory, lease expiry, accepted and ignored counts, and whether it replaced a previous owner.

**Details**

- Hooks deliberately not forwarded are counted by reason: after-edit hooks, unsupported kinds, plugin-supplied, managed, and other.
- The hook callback request gained optional issue and deadline timestamps so a device can drop a stale or replayed delivery.
- Nothing in the build constructs the registration message or reads the new timestamps; the only references are the definitions themselves and the list of commands a client may send. This is a protocol landed ahead of its implementation, so there is no flag to turn on.

**Evidence**

`register_device_hooks`

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

### Artifact types (templates and starters), built end to end and off by default

You could start a private artifact from a published template, but two server switches keep it shut here.

**What**

A new publish mode lets Claude start a private artifact on claude.ai from a published "Artifact type", a template whose page and settings are fixed by its publisher: pass `type_url` and your own files land alongside the type's. It is held shut by two independently gated server flags, both defaulting off, plus a local-only check, so nothing here is reachable in this build without server config. Reading and publishing into an artifact that already came from a type are unaffected by these gates; only creation from a type is suppressed.

**Details**

- Creation needs both `tengu_cobalt_plinth_larch` (or the environment variable `CLAUDE_CODE_ARTIFACT_TYPES`) and `tengu_cobalt_plinth_bracken`; all three names are new in this build.
- Both flags are resolved once when the tool schema is built and frozen for the session, so they cannot change mid-session. With them off, `type_url` is not in the schema at all and the tool refuses with a message that artifact types are not enabled in this session.
- The create capability is computed as types-on AND the environment variable `CLAUDE_CODE_REMOTE` not being set, so a remote or web session never sees `type_url` even with both flags on.
- Creation posts to `/api/frame/types/<slug>/create` with one retry that honours retry-after on 409 and 429; failures include recovery text for the case where the artifact was created but the file publish failed.
- `type_url` cannot be combined with `url`, `pr_review`, `capabilities`, `contract`, `lang` or `force`, and creating twice from the same type in one session is refused.
- Results distinguish your own files from the type's files, and the type's page is injected as untrusted content inside its own wrapper.
- Plan mode and Cowork frames, the shared-workspace surface, each have their own deny paths, and new analytics record the create with counts of own files and type files.

**Evidence**

`Create a new private Artifact on claude.ai from an existing Artifact type`, `created_from_type`, `tengu_cobalt_plinth_larch`, `a publish with \`type_url\` always creates a new Artifact whose page and settings come from the type \u2014 remove \`url\`, \`pr_review\`, \`capabilities\`, \`contract\`, \`lang\`, and \`force\``, `tengu_cobalt_plinth_bracken`, `**Artifact types**: To start a new Artifact from a published Artifact type (people may call one a template or a starter), pass \`type_url\` (the type's link)`, `typeLocked: n.data.type != null && typeof n.data.type === "object"`, `type_locked`, `Artifact types are not enabled in this session`, `creating an Artifact from an Artifact type (\`type_url\`) is not available in this session — remove \`type_url\``, `CLAUDE_CODE_ARTIFACT_TYPES`, `CLAUDE_CODE_REMOTE`

- Flag `tengu_cobalt_plinth_larch`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Flag `tengu_cobalt_plinth_bracken`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Artifacts
- Names: `type_url`, `CLAUDE_CODE_ARTIFACT_TYPES`
- Tier: Not switched on
- Useful: 2/5
- Signal: 5/5
- Present in the build but not switched on

### Every session now advertises that it can send idle notices

Every session now advertises itself as able to send idle notices, with no flag guarding it.

**What**

Each running Claude Code writes a fixed one-element feature list into its entry in the local session registry, marking itself as able to send idle notices. This is written with no flag check, so builds shipping now become discoverable as idle-capable ahead of the user-facing half.

**Details**

- Sits alongside the existing peer protocol field in the same record.
- The consumer is the subscribe path, gated behind `tengu_harbor_kite` (fallback false), which refuses when the target does not advertise the feature and says "that session runs a version without idle notices".
- The reader only records the field when it parses as an array, so older peers without it degrade rather than break.

**Evidence**

`peerFeatures`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.236; this account: on, anonymous baseline: on, compiled default: on)
- Area: Multi-Session
- Tier: Under the hood
- Useful: 2/5
- Signal: 5/5

### Console session viewer documented as the no-code way to debug a session

Docs describe a Console session viewer for inspecting sessions, with deep links to single events.

**What**

The events reference now describes the Console session viewer, available to Developers and Admins: a filterable session list, a per-thread timeline minimap, a transcript grouped by model request with a filter box and JSON export, and a five-tab Inspector (Session, Events with a Deltas view, Tools, Resources, Threads) toggled with the `d` key. It also documents appending `?event={event_id}` to a session URL to link straight at one event, suggested for error reports.

**Evidence**

``Deep-link with `?event={event_id}` on the session URL``

- Area: Documentation
- Names: `?event=`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Token-reminder settings can now be delivered by the server directly

Token-reminder settings can now arrive from the server, but your env var and setting still win.

**What**

The three settings behind the total-tokens reminder (its mode, its budget, and whether it appears after your turn) are read first from a configuration blob the server sends, and only fall back to the feature-flag defaults `"padded-countdown"`, the default budget and on. The `CLAUDE_CODE_TOTAL_TOKENS_REMINDER` environment variable and the `totalTokensReminder` settings key still override both.

**Details**

- When a server-provided value is used, it is reported as `tengu_lapis_anchor_from_client_data`.
- A server value of the wrong type logs a warning and falls back to the flag default rather than failing.

**Evidence**

`falling through to GrowthBook`

- Area: Context Reminders
- Names: `CLAUDE_CODE_TOTAL_TOKENS_REMINDER`, `totalTokensReminder`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### AWS self-hosted environments documented as supported, with auth and session limits

Self-hosted environments on AWS are documented as supported, with auth and session-length limits spelled out.

**What**

The bundled platform availability reference no longer lists self-hosted environments on Claude Platform on AWS as "Not available". The self-hosted sandbox row now marks them supported and spells out how workers authenticate, how long a session can run unattended, and what a self-hosted session on AWS cannot do.

**Details**

- Workers authenticate either with IAM/SigV4 credentials or with an API key issued from the AWS Console that carries the `AnthropicSelfHostedEnvironmentAccess` managed policy.
- API keys generated in the Anthropic Console do not work against the AWS endpoint, so an existing Anthropic Console environment key has to be replaced with one of the two AWS methods.
- A session runs autonomously for at most 6 hours before it needs reauthentication; sending any user-role event continues it.
- Self-hosted environments on AWS cannot attach memory stores, and an attempt to do so is rejected at session create rather than failing later.

**Evidence**

`` `AnthropicSelfHostedEnvironmentAccess` managed policy ``, `AnthropicSelfHostedEnvironmentAccess`

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

### Headless sessions accept idle notices from peers

Non-interactive runs now accept and queue idle notices from peer sessions.

**What**

The non-interactive entrypoint now watches its own session state, records which peers are idle, and queues incoming idle notices for the model, waking the loop the same way an incoming peer message does.

**Details**

- Each notice and each recorded subscription is logged, including a count of subscriptions that arrived before the handler was mounted.
- No separate gate was found on this wiring.

**Evidence**

`enqueueIdleNoticesForModel`

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

### Resumed sessions explain why they did not re-arm comment replies on an Artifact

Resuming now says exactly why it did not re-enable automatic comment replies on each Artifact.

**What**

When you resume a session, Claude Code no longer silently re-enables automatic replies to comments on published Artifacts. It re-arms only what it can, and prints a reason for each one it skipped: you stopped watching it last time, this account cannot publish to that Artifact, another session may already be replying, it could not confirm no other session is replying, or you are already watching the cap of 5 Artifacts at once.

**Details**

- At most the most recent Artifact's comment monitor comes back on resume; a summary line names up to two Artifacts that were left unwatched.
- The skip reasons are internal codes with one sentence each: `recorded_stop`, `not_editor`, `holder_unknown`, `held_by_background`, `watch_cap` and `watch_cap_reconnect`.
- The watch limit of 5 comes from a constant interpolated into the message, so the sentence always matches the real cap.
- Re-arming calls are marked as resumes, and an Artifact recorded as armed carries forward the consent to publish replies, so you are not asked again.

**Evidence**

`a resumed session brings back at most its most recent Artifact's comment monitor`

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

### The `live_doc` publish field is gone from the artifact tool

You can no longer ask the artifact tool to publish a live doc; the document skill is marked live-doc backed instead.

**What**

The optional `live_doc` input and its gate branch are removed from the artifact tool schema, and the live-doc prompt block no longer ships in the tool description. The document skill now carries a marker saying it is live-doc backed instead.

**Details**

- The skill only appends its live-doc section when the live-doc module is present and a gate passes.
- That module handle is a build-time constant set to null and never assigned, so every live-doc branch in this build is unreachable. The same was true in v2.1.235, so what changed is the shrunken input schema, not the reachability.

**Evidence**

`liveDocBacked`

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

### A nudge when Claude has gone several turns without telling you anything

Claude can be nudged to check in when it has gone quiet for several turns, off by default.

**What**

When Claude works for several turns without saying anything to you, a hidden reminder can be added to the conversation: "The user hasn't heard from you in a while. As you continue, keep them updated when there's something to tell". It is off unless `CLAUDE_CODE_SILENT_TURN_REMINDER` is set or the model in use declares support for it, and no model in this build does, so by default nobody sees it.

**Details**

- The turn threshold comes from `CLAUDE_CODE_SILENT_TURN_REMINDER_TURNS`, otherwise a remote setting with a fallback of 5 turns. The value must be at least 1.
- At most 3 reminders are sent per quiet stretch.
- The reminder type is now registered across the attachment renderer, the reminder-kind list and the transcript, and has its own telemetry event.
- Whether a model declares the capability comes from server-supplied data, so it can be switched on remotely without a new release.

**Evidence**

`The user hasn't heard from you in a while. As you continue, keep them updated when there's something to tell`, `"batching_reminder_sent",`, `"The user hasn't heard from you in a while. As you continue, keep them updated when there's something to tell \u2014 a finding, a change of plan."`, `The user hasn't heard from you in a while.`

- Area: Conversation
- Names: `CLAUDE_CODE_SILENT_TURN_REMINDER`, `CLAUDE_CODE_SILENT_TURN_REMINDER_TURNS`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Credential refusal on cloud uploads is wired up but switched off

Cloud uploads can refuse uncommitted credential files, but the setting behind it defaults to off.

**What**

The two places that upload your repo for a cloud session now pass the option that refuses uncommitted credential files, reading it from a remote setting that falls back to off if it cannot be read. The option name existed before this release but was never passed to the upload path.

**Evidence**

`refuseUncommittedCredentialFiles: await y2().catch(() => !1),`

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

### One-time prompt offering to make auto mode your default permission mode

Claude can offer once to make auto mode your default permission mode, switched off in this build.

**What**

Claude Code can now ask, once, whether you want auto mode to become your default permission mode, and the interactive session checks at startup whether to queue that question behind whatever is already on screen. The prompt takes an accepted, declined or cancelled answer. Both the prompt and the startup check are present in this build but switched off, so nothing appears unless remote config turns them on.

**Details**

- Gated on the remote flag `tengu_maple_pier`, which falls back to off, so the prompt is invisible by default in this build.
- Shows at most once ever, tracked by `hasSeenAutoDefaultNudge` in config, and only after onboarding is complete.
- The startup check returns nothing unless `hasSeenAutoDefaultNudge` is unset and your user settings set a `permissions.defaultMode` to something other than auto, with no project, local, command-line or policy setting overriding it.
- Editor and SDK hosts can drive the flow: the VS Code notifications `auto_default_nudge_shown` and `auto_default_nudge_resolved` now go to a dedicated handler rather than being logged as generic events.
- A second dialog kind, `ide_onboarding`, is registered alongside it.

**Evidence**

`choose: make auto mode the default permission mode`, `tengu_maple_pier`

- Flag `tengu_maple_pier`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Permissions
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Version 5 session storage is decided once per process

Whether the newer session storage is used is decided once at startup and cannot change mid-session.

**What**

The newer v5 session-storage backend used to be consulted wherever a caller happened to check `tengu_hover_rest` or simply passed a v5 handle. This build reads the flag once at startup and pins the answer, and every v5 path now checks the pinned value too: session file writes, plugin listing, sidecar writes, session-metadata re-append, config backup listing, and skill and plugin existence checks. Passing a v5 handle is no longer enough, and the choice cannot flip mid-session.

**Details**

- The flag falls back to false, so it is off unless remote config turns it on.
- A later read that disagrees with the pinned value logs a warning and keeps the original decision.

**Evidence**

`tengu_hover_rest read `

- Flag `tengu_hover_rest`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Sessions
- Tier: Under the hood
- Useful: 3/5
- Signal: 4/5

### `read_db` can save fetched documents to a local folder

Artifact database reads can dump fetched documents into a folder on disk instead of into the chat.

**What**

Artifact database reads take an `out_dir` and write each returned document to disk as a JSON file instead of pulling it into the conversation. The result line reports how many documents were saved and how many were skipped.

**Details**

- The destination is resolved first: directories that cannot be resolved, and network paths, are refused outright.
- Every planned file is run through the same safety rules that govern file edits before anything is written.
- The approval prompt is rewritten for this case, telling you the documents will be saved as JSON files at the named location.

**Evidence**

`read_db saves only to local directories \u2014 out_dir cannot be resolved`, `read_db saves only to local directories \u2014 out_dir names a network path or cannot be resolved`

- Area: Artifacts
- Names: `out_dir`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### `write_db` can send a document from a local JSON file

Artifact database writes can take a local JSON file, so big documents skip the conversation.

**What**

Artifact database writes accept `file_path` pointing at a local JSON file whose top-level object becomes the document, so a large document no longer has to pass through the conversation. Exactly one of inline `data` or `file_path` is required; giving both or neither is a validation error.

**Details**

- The file is read under the normal Read permission rules, parsed as JSON, and must be a top-level object; failures are reported as read denied, file not found, invalid JSON, not an object, or too large.
- Swapping between inline data and a file after approval is refused and forces a fresh permission check.
- The write approval no longer blanket-covers files that need their own Read approval.

**Evidence**

`file_path does not hold valid JSON \u2014 write_db sends the file's JSON object as the document`, `saved as local JSON files (the destination follows the file-edit rules)`, `a local JSON file whose top-level object is sent as the document`

- Area: Artifacts
- Names: `file_path`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### Deny rules match batched tools by the original tool name

A disallowedTools rule now also blocks the batched wrapper around that tool.

**What**

A `disallowedTools` entry naming a tool now also blocks the batched wrapper around that tool. Previously a rule written against the original name did not stop the wrapped form.

**Details**

- Wrapper tools carry the name of the tool they wrap, and the permission check treats a match on that name as a match on the wrapper.
- The same lookup is applied to the plan-mode and read-only tool sets, so a wrapper cannot be used to run something those modes exclude.
- Applies whenever a batching tool is present; there is no flag.

**Evidence**

`if (a.underlyingV1ToolName !== void 0 && t.has(a.underlyingV1ToolName))`, `if (n.underlyingV1ToolName && w0(t, { name: n.underlyingV1ToolName }, r))`

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

### Fullscreen trial environment variable, and the upsell prompt now stops after three showings

You can start a throwaway fullscreen session with an env var, and the upsell stops after three showings.

**What**

`CLAUDE_CODE_TUI_TRIAL=fullscreen` starts a session in the fullscreen renderer without saving the choice; it is read once at startup and then removed from the environment. The prompt offering fullscreen can be forced with `CLAUDE_CODE_FORCE_FULLSCREEN_UPSELL`, and is suppressed once you have a `tui` setting, under tmux control mode, on Windows SSH, and after it has been shown 3 times.

**Details**

- A trial session reports its renderer reason as `upsell_trial_on` rather than `settings_on`, which is how the rest of the code knows the choice is not yet persisted.
- The showing count is tracked as `fullscreenUpsellSeenCount` against a limit of 3.
- The advertisement text is unchanged, but the check that decides whether to show it now also consults the crash auto-disable state.

**Usage**

`CLAUDE_CODE_TUI_TRIAL=fullscreen claude` **Evidence** `Try the new fullscreen renderer \u2014 flicker-free output, mouse support, auto-copy on select \xB7 /tui fullscreen`

- Area: Fullscreen
- Names: `CLAUDE_CODE_TUI_TRIAL`, `CLAUDE_CODE_FORCE_FULLSCREEN_UPSELL`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### Pinning artifacts in the picker, switched on by the server

The artifact picker gets a pinned tab and a `p` key to pin, if your server enables it.

**What**

The artifact list gained a fourth filter tab for pinned artifacts, a `p` key whose label flips between pin and unpin, and sorting that puts pinned artifacts first. The switch is not a local flag: the list response carries a `starsEnabled` boolean, and when the server omits it the tab is dropped, the pin action disappears, and a selected pinned filter is rewritten back to "all".

**Details**

- Per-artifact list data gained a pinned marker, an audience (owner, users, org, or public) and a last-viewed timestamp; ordering is pinned first, then most recently viewed, updated, or created.
- Pin state updates locally straight away and is reconciled against the server.
- The pinned tab is only offered once you actually have something pinned, or are already on it, and the empty state reads "Nothing pinned yet. Press p on an artifact to pin it."
- There is no environment variable or local flag to force it on; it stays invisible until the artifacts backend returns the field.

**Evidence**

`Nothing pinned yet. Press p on an artifact to pin it.`, `starsEnabled`

- Area: Artifacts
- Names: `p`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### Fullscreen is offered as a trial, and turns itself off after repeated failed launches

Accepting fullscreen now runs as a trial, and two crashed launches disable it on that machine.

**What**

Accepting the fullscreen renderer prompt no longer writes the setting straight away. Claude Code relaunches into a trial and only saves `tui: "fullscreen"` if the session stays healthy. Separately, each fullscreen launch is recorded before the first frame and cleared 10 seconds after it; a launch that dies first counts as a strike, and 2 strikes disable fullscreen on that machine for that version. `/tui fullscreen` retries, and `CLAUDE_CODE_NO_FLICKER=1` still overrides.

**Details**

- The renderer decision gained two new reasons: `upsell_trial_on` for a trial session and `crash_auto_off` for the self-disable.
- Strike state lives in the global config as `fullscreenBootPending`, `fullscreenBootStrikes` and `fullscreenAutoDisabled`, the last stamped with the build version (2.1.236). While the stamp matches the running version fullscreen stays off and the upsell is suppressed; an update clears it.
- One strike falls back to the classic renderer for that launch only. Pending records go stale after 10 minutes on the same host, 30 days across hosts.
- The canary applies to interactive sessions entering fullscreen via settings, trial, the Anthropic default, or the downsell/gb paths. It is skipped for env-forced fullscreen and when `CLAUDE_CODE_EXIT_AFTER_FIRST_RENDER` is set.
- When it trips, stderr says: "Claude Code's fullscreen renderer has repeatedly failed to start on this machine, so it has been turned off here. Run /tui fullscreen to try it again (this also resets after an update)."
- Bare `/tui` now explains why the classic renderer is active, distinguishing a sticky repeated-failure disable from a single unfinished launch.
- Telemetry: `tengu_fullscreen_crash_auto_off`, carrying sticky, strikes, new_strikes, pending age and the entry path fullscreen would have used.
- Neither path is behind a remote flag. Fullscreen-by-default itself still sits behind `tengu_amber_creek` and `tengu_pewter_brook`, both falling back to off.

**Evidence**

`crashAutoOff: !1,`, `crash_auto_off`, `Claude Code's fullscreen renderer has repeatedly failed to start on this machine, so it has been turned off here. Run /tui fullscreen to try it again (this also resets after an update).`, `(fullscreen was turned off on this machine after it repeatedly failed to start; /tui fullscreen retries)`, `tengu_fullscreen_crash_auto_off`, `upsell_trial_on`

- Flag `tengu_amber_creek`: Not enough to say (read for one account on one subscription tier against v2.1.236; this account: no value returned, anonymous baseline: no value returned, compiled default: on)
- Flag `tengu_pewter_brook`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.236; this account: on, anonymous baseline: on, compiled default: on)
- Area: Fullscreen
- Names: `/tui fullscreen`, `CLAUDE_CODE_NO_FLICKER`
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### Prompt history checks pasted content for secrets

A secret you paste into a prompt can now be kept out of your saved history.

**What**

The check that decides whether a prompt is too sensitive to save now sees the actual pasted text, not just a flag saying a paste happened, so a command with a secret pasted into it can be kept out of history. Only text pastes are inspected. Setting `CLAUDE_CODE_SKIP_PROMPT_HISTORY` still disables history entirely.

**Evidence**

`hasHiddenPastes`

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

### Uploading a repo to the cloud can refuse to carry uncommitted credential files

Uploading your repo to a cloud session aborts if uncommitted credential-looking files are present.

**What**

Before packaging your checkout for a cloud session, uncommitted files with credential-looking names abort the upload. Bundling also gained a cheaper tier that packages only the branch you have checked out plus the current commit, instead of every branch.

**Details**

- The refusal is reported as an upload outcome of `uncommitted_credentials`, or `credential_check_failed` when git cannot report the working tree state.
- The narrower bundle is reported as `current_branch_only`.
- The credential refusal only applies when the caller asks for it.

**Evidence**

`uncommitted_credentials`

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

### Wrap-up hint near the usage limit is now per-plan and once per window

The near-limit wrap-up nudge now depends on your plan and fires once per five-hour window.

**What**

The hint asking Claude to wrap up the current step as you approach the limit now uses a threshold that depends on your plan: 0.99 on Max 5x, 0.9975 on Max 20x, and a default otherwise. It fires at most once per five-hour window instead of every time you cross into the near-limit zone.

**Details**

- The hint is keyed to the window's reset time, so re-entering the zone in the same window does not re-trigger it.
- A usage reading is ignored unless both the reset time and the utilisation figure are finite numbers and the window has not already reset.
- Reachable on this build with no flag.

**Evidence**

`default_claude_max_20x`

- Area: Usage & Limits
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### macOS sandbox re-applies read and delete denials inside writable folders

On macOS, granting write access to a folder no longer reopens files you denied inside it.

**What**

The macOS sandbox profile is now assembled from explicit allow, deny and writable-root lists, and adds a further section that reinstates your denials for any path that happens to sit inside a folder Claude Code is allowed to write in. Granting write access to a directory no longer quietly reopens a file you had denied reads on inside it, and denied paths under a writable root can no longer be deleted or unlinked.

**Details**

- Rules that deny a path outright, or allow it only inside an otherwise denied area, are converted into both read denials and delete/unlink denials scoped to the writable roots.
- Glob patterns in those rules are expanded into regular expressions matching the folder itself and everything beneath it.
- The parent directory of each glob-based denial is added as its own literal entry, so the containing folder is covered as well as its contents.
- Paths you explicitly allowed get carve-outs in the generated profile and stay writable.
- The re-application step is marked in the profile itself with the comment `; File read: keep read-denied paths inside write roots in place`, and the delete rules use the sandbox operation named `file-write-unlink`.

**Evidence**

`; File read: keep read-denied paths inside write roots in place`, `file-write-unlink`

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

### Live collaborative-document branches are compiled out, not merely flagged off

Live collaborative-document branches sit in artifact publishing but can never run, even with server config.

**What**

The artifact publish result gained branches choosing between a gate-closed, streaming or remote suffix, plus a note about durable arming. Every one of them is reached through a module handle declared as `null` and a boolean declared as `false` that nothing in the build ever assigns, so none of it can run and no server-side config can enable it.

**Details**

- Unreachable in this build: the gate-closed result suffix, the live-edit and live-doc stream gates, the durable-arming subscription state and its status line.
- The same block holds a read-page-data constant that is likewise a compile-time false, which leaves the set of `read_page_data` schema names empty.
- These strings appear in the build and look like shipping features; they are excluded at build time rather than switched off by a flag.

**Evidence**

`LIVE_DOC_RESULT_SUFFIX_GATE_CLOSED`

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

### A short reminder can be spliced in after a batch of tool results

A short reminder can be slipped in after tool results, set by an environment variable or server config.

**What**

After a run of tool results, a short reminder text can be inserted into the conversation, chosen by matching the model you are using against a list of model name patterns. The text comes from the `CLAUDE_CODE_TOASTY_THIMBLE` environment variable, or from server-supplied config; with neither, nothing is inserted.

**Details**

- Pattern matching tries an exact model name, then the base model, then the longest wildcard pattern, then a catch-all `*`.
- Malformed config is logged once and ignored.
- It fires at most once per model per conversation, and is reported with the model, where the text came from, and its length.

**Evidence**

`tengu_toasty_thimble`

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

### Endpoint for downloading a plugin from an organization

There is now an address for downloading an organization's plugin, optionally pinned to a version.

**What**

A new API address builder for fetching a plugin belonging to an organization, with an optional parameter to pin a specific version.

**Evidence**

`/api/oauth/organizations/:orgUUID/plugins/`

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

### Gzipped API request bodies, gated off, with a retry-and-latch kill switch

Requests to the API can be gzip compressed, but both switches default off so nothing compresses.

**What**

Claude Code can now compress outgoing API request bodies with gzip, decided per request by a helper that records exactly why it declined. It only applies on the Bun runtime, only to string bodies of at least 4096 characters, and never over unix sockets, proxies, mTLS or custom CA setups. Both remote gates default to off, so nothing is compressed in this build without a server-side value, and a rejected compressed request falls back to sending uncompressed and stops compressing afterwards.

**Details**

- Enabled by either of two remote flags, `tengu_atomic_ocean` or `tengu_gzip_request_bodies`, both falling back to false.
- `CLAUDE_CODE_GZIP_REQUEST_BODIES` overrides the flags: set true it forces compression on unless the kill switch has already tripped, set false it forces it off.
- Skip reasons recorded per request are `env_off`, `not_bun_runtime`, `non_string_body`, `below_min_size`, `latched_off`, `flag_off`, and the transport reasons `unix_socket`, `proxy`, `mtls` and `custom_ca`.
- A compressed request answered with 400, 403 or 415 from what looks like an Anthropic or Cloudflare hop is resent uncompressed, and compression is switched off for the rest of the process.
- When the rejection looks like it came from a non-Anthropic intermediary and the uncompressed retry succeeded, the off state is written to settings as `gzipRequestBodiesLatchedOff` so later sessions skip compression too.
- The saved off state is honoured for about 7 days and then cleared, and per-session state tracks whether it is currently in effect.
- Telemetry events are `tengu_gzip_request_body_fallback` for a rejected compressed request and `tengu_gzip_request_body_latch_cleared` when the saved switch expires, alongside per-request outcome reporting.

**Evidence**

`tengu_atomic_ocean`, `return ((e.latchedOff = !0), (e.persistedLatchInEffect = !0), !0);`

- Flag `tengu_gzip_request_bodies`: Not enough to say (read for one account on one subscription tier against v2.1.236; this account: no value returned, anonymous baseline: no value returned, compiled default: not a boolean we can read)
- Area: Internals
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Hooks and host applications can attach classifier context to tool results

Hooks can attach extra context to a tool result that shows on its own transcript line.

**What**

The application embedding Claude Code, or a hook running inside it, can now supply extra context that travels with a tool's result message and is shown on its own line in the transcript. Hook output handling copies a `classifierContext` field out of a hook's response alongside the additional-context and rewritten-output fields it already reads, and messages carry a matching field that is read back when the tool result is assembled. Nothing changes for anyone who does not supply that context, and the path is guarded further before it is consumed.

**Details**

- Requires a host application or hook to actually provide the context; with none supplied the tool result is built exactly as before.
- Skipped for tools coming from MCP servers, the external tool servers Claude Code connects to, unless they are running inside the SDK.
- Each piece of context is paired to a rewrite sequence number, and entries whose rewrite never landed are discarded and logged rather than attached to the wrong result.
- When the joined context text is too long and gets truncated, that truncation is reported separately rather than silently swallowed.

**Evidence**

`auto_mode_host_context`, `classifierContext`

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

### Idle notices between sessions can carry the sender's permission mode, switched off here

Idle notices could tell the other session whether you are in bypass mode, but that stamp is off.

**What**

Messages sent between sessions on the same machine when one goes idle can now be stamped with the sending session's permission mode, as `bypass` or `prompting`. The stamping helper returns nothing unless the gate `tengu_harbor_kite_mode_emit` is on, and it falls back to false in this build, so no mode is attached.

**Details**

- The mode comes from the inbound session's current permission mode, falling back to the mode recorded when the session was disconnected.
- The receiving side already reads this field on both idle notices and idle-notification requests.
- The same gate covered the notification-request case in 2.1.235; this extends it to idle notices.

**Evidence**

`tengu_harbor_kite_mode_emit`

- 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.236; this account: on, anonymous baseline: on, compiled default: on)
- Area: Cross-Session Messaging
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Per-model reminder text delivered from server config

Reminder text can be latched into a conversation once per model from server config or an env var.

**What**

The reminder described above is a new attachment type that latches into a conversation once per model, taking its text from a server-supplied map of model pattern to reminder text or from `CLAUDE_CODE_TOASTY_THIMBLE`. With neither present, nothing is injected.

**Details**

- Injection is reported with the model name and whether the text came from the environment or the server.
- It writes a marker entry into the transcript when sent.
- The reminder is filtered out of the prompt when the turn is not a human turn.
- None of this machinery existed in the previous release.

**Evidence**

`tengu_toasty_thimble_applied`

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

### Remote control gains a detach-terminal action and per-surface tracking

Remote control gained a detach-terminal action and per-surface tracking for mobile, web and desktop.

**What**

The set of remote-control actions that currently do nothing gains `detachTerminal` beside `clearStatus`, `toggleQr` and `refreshDisplay`, and a new helper filters a stored list of surfaces seen against the allowed list "mobile", "web" and "desktop". Groundwork for showing remote-control tips per surface.

**Evidence**

`detachTerminal: t,`

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

### The message tool's `notify_when_idle` parameter and its supporting layer

Claude can ask another session to ping it when idle, but the parameter is hidden unless the gate is on.

**What**

The tool Claude uses to message another session on the same machine gained a `notify_when_idle` input that subscribes to a single notice when that session next goes idle or exits; sent without a message body it is a pure subscription. The parameter is only added to the tool schema when cross-session messaging is on, so with the gate off the model never sees it.

**Details**

- Gate: `CLAUDE_CODE_HARBOR_KITE` environment variable, otherwise `tengu_harbor_kite` (fallback false), plus `tengu_harbor_kite_win` (fallback false) on Windows. Absent server config, invisible.
- Refusal reasons each get their own sentence: `no-inbox`, `self-target`, `peer-unsupported`, `unreachable-namespace`, `cap`, `peer-gone`.
- Subscriptions live in a table with a maximum size, eviction and expiry.
- Sub-agents and teammates are refused, recorded as `subscribe_refused_principal`, under `cross_session_notify_idle` telemetry.

**Evidence**

`Ask a session ON THIS MACHINE to send you ONE notice when it next goes idle`

- Flag `tengu_harbor_kite`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.236; this account: on, anonymous baseline: on, compiled default: on)
- Flag `tengu_harbor_kite_win`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Cross-Session Messaging
- Names: `notify_when_idle`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Type-locked artifacts have their own read and publish rules, all inert in this build

Artifacts made from a template lock the template's files against edits, unreachable without two off switches.

**What**

An artifact created from a type carries a lock recording which files belong to the type. Claude is told those files cannot be edited here, and publishing an `index.html` to such an artifact is refused because the page comes from the type. None of it is reachable without the two artifact-type flags, both of which default off.

**Details**

- The lock is carried through artifact read-back and share status, and the read path stops taking the plain-owner fast path for a locked artifact.
- The tool description states verbatim that a type-owned file belongs to the artifact's type and can't be changed here.
- Whether any of this is visible is frozen once per session at schema-build time.
- Creating a new artifact from a type is additionally blocked inside remote sessions.
- None of these symbols exist in v2.1.235.

**Evidence**

`this file belongs to the artifact's type and can't be changed here`

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

### New stop reason for parked subagents

Subagents can now stop by being parked, treated as a normal stop rather than a failure.

**What**

Agents can now stop with a reason of `subagent_park`, and it is treated like an interrupt, a background move or a shutdown: an ordinary stop, not a teardown failure.

**Details**

- A `stop_kind` field is added to the telemetry recorded when an agent stops.
- Both strings are new in this build and the classifier path is unconditional.

**Evidence**

`subagent_park`

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

### Process-wide state replaced by per-session containers

Process-wide caches moved per session, groundwork for several sessions sharing one process.

**What**

The largest change in this release, invisible on its own: dozens of caches and counters that lived once per process now live in a container looked up from the current session. This is the groundwork for more than one Claude Code session sharing a single process without sharing state.

**Details**

- Affected: the OAuth token refresh cache, the clipboard tool probe, Linux and tmux terminal state, the telemetry interaction sequence counter, the sandbox and monitor socket registry, the git repository cache, and the analytics event ring buffer.
- The holder keys instances off the session object weakly, so they disappear with the session rather than living for the process lifetime.

**Evidence**

`RHb = new en(() => new SOd());`

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

### Session logs gained a versioned record for watched Artifacts

Which Artifacts a session watches for comments is now a versioned session-log record, cleared on fork.

**What**

Which Artifacts a session watches for comments is stored as its own session-log entry with a schema and a version number, replayed as last-wins when session state is rebuilt, and explicitly cleared when a session is forked or moved.

**Details**

- The record carries the session id and the list of Artifacts.
- Whether the monitor itself is flagged is not visible here; the recording and replay plumbing runs unconditionally.

**Evidence**

`"artifact-comment-monitor": "last-wins",`

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

### Auto-mode repository visibility moves to its own switch

Auto mode's awareness of whether your repo is public now has its own environment variable and config field.

**What**

Whether auto mode sees repository visibility is now decided by the `CLAUDE_CODE_AUTO_MODE_REPO_VISIBILITY` environment variable, falling back to a `repoVisibility` config field that must be exactly true. It is off unless you set one of them.

**Details**

- The helper that resolves this previously resolved the token-count reminder setting shown after a user turn; that logic now lives in its own function.

**Evidence**

`CLAUDE_CODE_AUTO_MODE_REPO_VISIBILITY`

- Area: Permissions
- Names: `CLAUDE_CODE_AUTO_MODE_REPO_VISIBILITY`, `repoVisibility`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Kill switch for Harbor request pacing

You can switch off Harbor request pacing with a new environment variable.

**What**

Pacing of Harbor requests now has an explicit off switch instead of a per-turn latch. Set `CLAUDE_CODE_HARBOR_KITE_PACING_OFF` to turn pacing off, or it can be turned off remotely by the `tengu_harbor_kite_pacing_off` flag.

**Details**

- The flag falls back to false, and the flag means "off", so pacing is on by default in this build unless the environment variable or remote config disables it.

**Evidence**

`tengu_harbor_kite_pacing_off`

- Flag `tengu_harbor_kite_pacing_off`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Internals
- Names: `CLAUDE_CODE_HARBOR_KITE_PACING_OFF`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Running out of budget now has its own result type

Hitting your USD budget now returns a distinct error subtype scripts can detect.

**What**

When a run stops because the USD budget is used up, the result comes back with `subtype: "error_max_budget_usd"` and `is_error: true`, so scripts can tell budget exhaustion apart from other failures.

**Evidence**

`subtype: "error_max_budget_usd"`

- Area: SDK
- Names: `error_max_budget_usd`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Self-hosted worker API: `run_one` replaced, `unrestricted_paths` removed

The worker API swaps run_one for handle_item and drops the unrestricted_paths file option.

**What**

The worker no longer exposes `run_one()` / `runOne()`. The replacement, `handle_item()` / `handleItem()` / `HandleItem()`, services one work item that has already been claimed and reads its identifiers from `ANTHROPIC_*` environment variables, so claiming is now the job of `run()` or the mid-level poller. The file-tool option `unrestricted_paths` is gone and raises if passed; use `allowed_roots` or `read_only_roots` instead.

**Details**

- Webhook-driven wake-up is rewritten to drain work through the poller, with a warning against awaiting that drain inside the HTTP request handler.

**Evidence**

`The old `unrestricted_paths` option is no longer accepted (passing it raises); add directories to `allowed_roots` instead.`

- Area: Self-Hosted Runner
- Names: `handle_item`, `allowed_roots`, `read_only_roots`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### `--continue` and `--resume` failures now say what went wrong

A failed --continue or --resume now tells you the error and exits instead of quietly carrying on.

**What**

If restoring a previous session fails, Claude Code prints an explicit exit message naming the underlying error and pointing you at `claude --resume` to pick a session or start a new one, then exits with status 1. Previously the failure was only logged and the process carried on silently.

**Details**

- Exit is recorded with reason `"other"`.

**Evidence**

`Run claude --resume to pick a session, or start a new one.`

- Area: Sessions
- Names: `--continue`, `--resume`
- Tier: Use it now
- Useful: 4/5
- Signal: 2/5

### A crash in the display now exits with an explanation instead of a raw stack trace

Interface crashes now print a readable message and tell you the classic-renderer env var.

**What**

If the code drawing the interface throws and cannot recover, Claude Code prints a message saying it exited after an interface error. When the fullscreen renderer was still starting up, it also says the next launch will use the classic renderer and that `CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1` forces that renderer.

**Details**

- Applies to normal interactive use. SDK and debug paths still rethrow the original error so tooling can see it.

**Evidence**

`Claude Code exited after an unrecoverable interface error`

- Area: Terminal UI
- Names: `CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1`
- Tier: Use it now
- Useful: 4/5
- Signal: 2/5

### Pin and rename artifacts from the /artifacts list

/artifacts gains a pinned view, p to pin or unpin, and ctrl+r to rename inline.

**What**

The artifact list adds a `pinned` view next to mine/shared/all with its own count, `p` to pin or unpin the highlighted artifact, and `ctrl+r` to rename one inline with a confirm step.

**Details**

- After a rename the list refreshes and re-selects the same artifact, so the cursor stays on the row you just renamed rather than jumping.
- Pinning and renaming each appear only when the session reports that it supports them; otherwise the keys do nothing and the `pinned` view is absent.

**Evidence**

`chord: "ctrl+r"`

- Area: Artifacts
- Names: `/artifacts`
- Tier: Use it now
- Useful: 4/5
- Signal: 2/5

### Pressing Enter on a typed slash command runs it

Pressing Enter on a typed slash command now runs the best match instead of sending text.

**What**

Submitting the prompt while a slash command is typed now resolves the best matching command and executes it, rather than sending the text as a message. The matcher behind command search was also rebuilt with an explicit ranking order.

**Details**

- Ranking runs exact name or display match, exact alias, shortest prefix, alias prefix, bucketed fuzzy score, then a caller-supplied boost.
- Callers can pass a filter to restrict which commands are considered.

**Evidence**

`displayPartKey`

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

### Cancellable waits when `/model` talks to a cloud session

You can now escape the wait when /model loads or switches models on a cloud session.

**What**

Fetching the model list and switching models on a cloud session are both waits you can now escape from instead of watching a spinner indefinitely. The picker shows a cancellable line with an escape shortcut while it loads, the underlying request carries an abort signal and a timeout, and cancelling gives you a specific message telling you what to do next rather than a generic failure.

**Details**

- While the list loads the picker shows `Loading models from the cloud session…` with an escape shortcut, and cancelling aborts the in-flight request and records a cancelled outcome.
- Cancelling a wait for the model list produces "Stopped waiting for the cloud session's model list — run /model again, or pass a model name, e.g. /model sonnet", which sits alongside the existing "Couldn't load the model list from the cloud session" message that still covers an outright failure.
- Cancelling a wait for a model switch shows `Stopped waiting for the cloud session` and tells you a notice will still arrive if the switch lands.
- Both cancellations record a `remote_wait_cancelled` outcome on the model-switch telemetry event.
- A switch rejected after you have already cancelled is suppressed rather than surfaced as an error, and a model-list response that arrives after an abort is discarded.
- Timeouts for control requests, the messages Claude Code sends to a cloud session and waits on, are now chosen per request type instead of through one special case for side questions.
- The timeout message dropped its `[RemoteSessionManager]` prefix and now reads as the control request name plus the elapsed seconds.
- Passing a model name directly, as in `/model sonnet`, skips the fetch entirely.

**Evidence**

`Loading models from the cloud session\u2026`, `Stopped waiting for the cloud session`, `Stopped waiting for the cloud session’s model list — run /model again, or pass a model name, e.g. /model sonnet`, `control_request '${e}' got no response after ${t / 1000}s`

- Area: Cloud Sessions
- Names: `/model`
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### Change detection ignores custom diff drivers and submodules

Change detection no longer runs your external diff tool or scans submodules, so it's faster.

**What**

The internal git call Claude Code uses to detect which files have changed now runs with `--no-ext-diff` and `--ignore-submodules=all`. A custom diff program set through git's `diff.external` setting is no longer invoked by that call, and activity inside submodules no longer affects the result. This removes a source of slowdowns and odd output for anyone with an external diff tool configured, and it applies to everyone with no flag or setting to turn it on.

**Details**

- `--no-ext-diff` stops git from handing the comparison to a `diff.external` driver from your git settings, so Claude Code's own diffing stays internal and predictable.
- `--ignore-submodules=all` means churn in submodules neither changes the set of files reported as modified nor slows the check down.
- Both arguments join the `-z` and `--no-renames` arguments already passed on the same invocation.

**Evidence**

`"--ignore-submodules=all",`, `--no-ext-diff`

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

### Exiting can no longer hang

Quitting no longer hangs; Claude Code force-kills itself and also handles hangup signals.

**What**

If the normal exit fails, Claude Code now kills its own process outright rather than sitting there. Shutdown also handles SIGHUP alongside SIGINT and SIGTERM, the UI teardown resolves or rejects the exit promise so anything waiting on it is released, and pending events are flushed before the internal event channel closes.

**Evidence**

`process.kill(process.pid, "SIGKILL")`

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

### Extra-usage panel now shows for team and enterprise accounts

Team and enterprise accounts now see the extra-usage panel, not just Pro and Max.

**What**

The panel showing extra usage previously appeared only on Pro and Max plans. It now renders for team and enterprise accounts too.

**Details**

- New helpers distinguish max, team, pro and enterprise, including the case of a team account sitting on the Max 5x plan.
- When the panel does bail out it now returns nothing rather than a false value.

**Evidence**

`"default_claude_max_5x"`

- Area: Usage & Limits
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### Private key redaction no longer skips blocks

Private key blocks are now reliably redacted instead of occasionally being skipped.

**What**

The patterns that find PEM private key blocks and replace them with `[REDACTED]` were shared across calls and carried a search position between them, which could skip a block or mark the wrong boundaries. They are now built fresh on each scan.

**Evidence**

`/-----BEGIN[ A-Z0-9_-]{0,100}?PRIVATE KEY(?: BLOCK)?-----/gi`

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

### A productivity-skills flag is declared but nothing reads it

A productivity-skills switch is named in the artifact code but nothing reads it.

**What**

The artifact module exports a new flag constant pointing at the gate `tengu_russet_pergola`, which does not exist in the previous build. Only the declaration and export are present; no code in this build reads the gate, so it does nothing yet.

**Evidence**

`PRODUCTIVITY_SKILLS_FLAG: () => Lhp,`

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

### A special thinking-block check is wired up but always answers no

Handling for a special kind of thinking block exists, but the check always answers no here.

**What**

New helpers ask whether an assistant message begins with a "special" thinking block, requiring both a thinking string and a signature string plus a registered matcher. Where the answer is yes, the message stops counting as thinking-only for context assembly, the streaming thinking panel is cleared rather than updated, and the message is treated as a real assistant turn.

**Details**

- The matcher variable starts as `null` and nothing in the build assigns it, so the check always returns false and none of those branches run.

**Evidence**

`redacted_thinking`

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

### Groundwork for serving artifacts and frames through the agent proxy

Address and header plumbing for serving artifacts and frames through the agent proxy exists but is unused.

**What**

New address builders under `/v1/code/agent-proxy` for `/frame` and `/artifact`, a request-header helper carrying an asset token in `x-frame-asset-token`, and a new `artifact_mount` constant. Nothing in this build is visible in the product yet.

**Details**

- No flag guards these helpers; they are new plumbing rather than a switchable feature.

**Evidence**

`x-frame-asset-token`

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

### Remote bridge waits for the disconnect notice, and an inbox fetch that is always off

Remote bridge shutdown waits for its disconnect notice, and a new inbox fetch always refuses.

**What**

Bridge teardown now waits for the pending disconnect notice on both the normal and skip-archive exits. A new message-fetch entry point is present but hard-disabled: it always returns a failure with the reason `feature_disabled`.

**Details**

- The polling client marks the worker generation as superseded before every fatal generation mismatch: repeated 404s, an expired session token, and exhausted authentication failures. That lets shutdown tell a replaced session apart from a live one.
- The flush is exposed on the transport alongside close.

**Evidence**

`feature_disabled`

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

### MCP protocol auto-negotiation now covers the ccr-proxy transport, off by default

The ccr-proxy MCP transport can negotiate protocol versions, but stays on legacy unless a flag is turned on.

**What**

The `ccr-proxy` MCP transport used to fall through to the legacy branch alongside sse, ws and ide. It now has its own check gated on `tengu_mcp_protocol_negotiation_ccr`: with the flag on it negotiates in auto mode with a probe, and with it off it stays on legacy exactly as before.

**Details**

- Fallback is false, so absent a server value nothing changes.

**Evidence**

`tengu_mcp_protocol_negotiation_ccr`

- Flag `tengu_mcp_protocol_negotiation_ccr`: Not enough to say (read for one account on one subscription tier against v2.1.236; this account: no value returned, anonymous baseline: no value returned, compiled default: on)
- Area: MCP
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### A new remote flag, on by default, controls message-input split repair

A new remote flag, on by default, reassembles split message-tool inputs when they are read back.

**What**

`tengu_deep_feather` is new and its fallback is true, so absent a server value it is on for everyone. It governs whether split message-tool inputs are reassembled, both when the tool reads its own input and when a prior tool call is re-read to recover its original message and idle-subscription values.

**Details**

- Effectively a kill switch: if remote config turns it off, previously split inputs stop being reassembled on the round trip.
- Notable as the only new flag here that defaults on rather than off.

**Evidence**

`tengu_deep_feather`

- Flag `tengu_deep_feather`: Not enough to say (read for one account on one subscription tier against v2.1.236; this account: no value returned, anonymous baseline: no value returned, compiled default: off)
- Area: Multi-Session
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Web fetch agent switch moved into per-host state

The web fetch agent decision moved per session; it still defaults off unless you set the variable.

**What**

The remembered answer for whether the web fetch agent is used now lives on a state object rather than a module variable. The decision is unchanged: `CLAUDE_CODE_WEB_FETCH_AGENT` wins if set, otherwise the `tengu_clever_orbit` gate decides, defaulting to off.

**Evidence**

`V.CLAUDE_CODE_WEB_FETCH_AGENT ?? nt("tengu_clever_orbit", !1)`

- Flag `tengu_clever_orbit`: Not enough to say (read for one account on one subscription tier against v2.1.236; this account: no value returned, anonymous baseline: no value returned, compiled default: on)
- Area: Web Fetch
- Names: `CLAUDE_CODE_WEB_FETCH_AGENT`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Bundled guide for upgrading the anthropic Python SDK to 1.x

A bundled guide walks your project through upgrading the anthropic Python SDK to 1.x.

**What**

A new reference document ships inside Claude Code, reached from the `/claude-api upgrade` flow, walking a project through the 0.x to 1.x migration step by step.

**Details**

- Steps covered: bump the pin, raise the Python floor to 3.10, move `httpx` objects that cross the SDK boundary to the `httpx2` fork or call `httpx2.alias_httpx()` at an entry point, await `.with_raw_response` body reads on async clients, port `completions.create` plus `HUMAN_PROMPT`/`AI_PROMPT` to Messages, drop `temperature`/`top_p`/`top_k`, rewrite raw `output_format={...}` as `output_config={"format": {...}}`, and supply an explicit Bedrock region.
- The document is a plain text constant in the bundle with no gate around it.

**Evidence**

`# Upgrading the `anthropic` Python SDK: 0.x → 1.x`

- Area: Docs
- Names: `/claude-api upgrade`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Dismissing a Claude-drafted feedback card offers to turn the feature off

Dismissing a Claude-drafted feedback card now offers to switch the feature off entirely.

**What**

Pressing dismiss on a Claude-drafted feedback card now asks "Turn off Claude-drafted feedback?", with `0` to turn it off and `escape` to keep it. Confirming shows "Claude-drafted feedback is off - turn back on in /config". The offer appears at most twice; anything other than `0` counts as a decline.

**Details**

- Declines are counted in the `feedbackDraftsTurnOffPromptDeclines` setting, and the prompt stops being shown once it reaches 2.
- New telemetry `tengu_feedback_turnoff_prompt_shown` and `tengu_feedback_turnoff_prompt_declined`.
- No gate of its own; it appears wherever Claude-drafted feedback cards already do.

**Evidence**

`Turn off Claude-drafted feedback?`

- Area: Feedback
- Names: `/config`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Plugin eval cases must have a prompt, and unfilled templates are rejected

Plugin eval cases now must include a prompt, and untouched init placeholders are rejected.

**What**

A plugin eval case that supplies only a session history file is now refused; you need `execution.prompt` in `case.yaml` or a `prompt.md` body. A case or grader still containing the placeholder line written by `init` is refused with an error explaining that.

**Details**

- Directory scanning for cases was rewritten, and paths written inside a case are now resolved with a check that they do not escape the case folder.

**Evidence**

`execution.prompt is required (a prompt.md body, or execution.prompt in case.yaml)`

- Area: Plugin Evals
- Names: `execution.prompt`, `case.yaml`, `prompt.md`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Agent toolset config entries are now typed per tool

Agent toolset config entries now use a typed shape per tool, affecting typed-language SDK callers.

**What**

Each entry in an agent toolset's `configs` array is now one of eight per-tool shapes, picked by a `type` field that appears in responses even when you omit it in requests. Dict-based SDKs (Python, TypeScript, Ruby) are unaffected; Go, Java, C#, and PHP callers must build entries from the per-tool types.

**Details**

- MCP toolset `configs` entries deliberately do not gain `type` or the web settings.

**Evidence**

`Code written against an SDK where all tools shared one config type must update how it constructs entries.`

- Area: SDK
- Names: `configs`, `type`
- Tier: Use it now
- Useful: 2/5
- Signal: 2/5

### Publishing an artifact now tells you how to reopen it

After publishing, you are told to use /artifacts or ctrl+] to reopen the artifact.

**What**

The note after a publish names the terminal ways back to the artifact: `/artifacts` to list what you own or were shared, with `o` to open and `c` to copy the link, and `ctrl+]` to reopen the most recent artifact from the session. The web gallery is mentioned second rather than first.

**Evidence**

`/artifacts lists the artifacts you own or were shared (o opens, c copies the link)`

- Area: Artifacts
- Names: `/artifacts`, `ctrl+]`
- Tier: Use it now
- Useful: 3/5
- Signal: 1/5

### Rename an artifact from the artifact picker

You can rename an artifact right in the artifact picker and it updates immediately.

**What**

The artifact list can rename an artifact in place through a "Rename artifact:" text field confirmed with enter and abandoned with escape, updating the list immediately and rolling back if the server call fails.

**Details**

- Renames go to a new `/api/frame/retitle/` endpoint, with its own telemetry.
- A network failure surfaces as "Couldn't rename artifact (network error)" and the old title is restored.
- The same screen carries a pin action on the `p` key, backed by `/api/frame/favorite/` (POST to pin, DELETE to unpin), which only appears when the server enables it.

**Evidence**

`Couldn't rename artifact (network error)`

- Area: Artifacts
- Tier: Use it now
- Useful: 3/5
- Signal: 1/5

### Feedback dialog says where to turn the feature off

The drafted-feedback disclaimer now points you to /config to switch it off.

**What**

The disclaimer shown for Claude-drafted feedback now tells you the feature can be switched off in `/config`.

**Evidence**

`Turn off Claude-drafted feedback anytime in /config.`

- Area: Feedback
- Names: `/config`
- Tier: Use it now
- Useful: 2/5
- Signal: 1/5

## New Features

### Cloud sessions can pick a model for you when you start one

Starting an interactive cloud session now picks a model and permission mode for you, announcing changes.

**What**

Starting an interactive cloud session now works out a model and permission mode for you rather than leaving both unset. It can seed a model automatically, keep or drop a mode from your settings, take a model from the repository, or displace a trusted plan mode, and it prints a `[remote]` notice when it changes something.

**Details**

- Two new permission-mode sources are recorded: `model_auto` when the seeded model decided the mode, and `model_auto_over_settings` when that decision overrode your settings.
- A `tengu_remote_model_gate_hint` event fires only when the decision actually did something, so a session that needed no seeding is silent.
- No flag guards the event itself.

**Evidence**

`tengu_remote_model_gate_hint`

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

### Published Artifacts get stable per-block anchor ids

Published Artifacts get per-block anchor ids so links can point at a specific block.

**What**

When publishing an Artifact, Claude Code now parses the document's HTML and gives each block-level element a short `b-<hash>` id derived from its tag and text, so links can point at a specific block. It runs only on documents carrying the `// DOC:anchors:begin` marker, and is gated on the flag `tengu_cobalt_plinth_quince`, whose built-in fallback is true, so it is on unless remote config disables it.

**Details**

- Ids are written into the source before publish, rewriting existing `id="..."` attributes or inserting new ones.
- The pass bails out and records telemetry when it cannot locate where the anchors script loads, when an id cannot be placed, when parsing throws, or when the text to hash exceeds 33554432 bytes.

**Evidence**

`[artifact] doc block ids skipped: too much text to hash`

- Flag `tengu_cobalt_plinth_quince`: Not enough to say (read for one account on one subscription tier against v2.1.236; this account: no value returned, anonymous baseline: no value returned, compiled default: off)
- Area: Artifacts
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Telemetry events are scrubbed against per-field allowlists for HIPAA accounts

On HIPAA-entitled accounts, unrecognised telemetry fields are replaced with a placeholder before anything is sent.

**What**

On accounts whose entitlement includes "hipaa", a new pass checks every telemetry field against an allowlist before anything is sent and replaces anything unrecognised with a fixed `hipaa_redacted` placeholder.

**Details**

- Recognised shapes are matched by regex for UUIDs, session ids and version strings, and by fixed enumeration for platform, architecture and client type.
- Any string field not on the list is redacted wholesale rather than passed through.
- A helper emits a HIPAA notice at most once per terminal.
- Keyed on the "hipaa" entitlement check, so accounts without it are unaffected.

**Evidence**

`hipaa_redacted`

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

### Which Artifacts you are watching is now written into the session log

Your session log now remembers which Artifacts you were watching for comments.

**What**

The set of Artifacts armed for automatic comment replies is saved with the session and read back when the session id changes, so a restored session knows what it was watching rather than starting blank.

**Details**

- Each record is keyed by Artifact slug and holds armed or stopped state, the time it was written, the title, and a marker when a background session owns it.
- The list is capped and de-duplicated before being written.
- Failures and rejected records are reported through the Artifact live-subscribe path with reasons `comment_monitor_intent_invalid`, `comment_monitor_intent_foreign`, `comment_monitor_intent_record_invalid` and `comment_monitor_intent_write_failed`.

**Evidence**

`comment_monitor_intent_write_failed`

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

### Blocked artifact reads retry through the session gateway

Artifact reads blocked by your network allowlist now retry through the session gateway instead of failing.

**What**

Reading a published artifact used to fail outright when the environment's network allowlist blocked the artifact content host. Token-bearing (non-public) reads are now retried through the session gateway before giving up. The retry needs gate `tengu_cobalt_plinth_sorrel`, whose built-in fallback is true, so it is active absent server config.

**Details**

- Public, tokenless artifacts are explicitly not relayed.
- Relay attempts and refusals are recorded, along with a per-host decline window so a failing relay is not hammered.
- When the relay cannot serve the read either, the failure says so rather than repeating the egress-blocked message.

**Evidence**

`artifact reads through the session gateway are not enabled for this session, or the artifact service no longer serves this version`, `asset_egress_relayed`

- Flag `tengu_cobalt_plinth_sorrel`: Off by default, switched on for this account (read for one account on one subscription tier against v2.1.236; this account: on, anonymous baseline: on, compiled default: off)
- Area: Artifacts
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### A healthy fullscreen trial saves the setting; an explicit change during it does not

A trouble-free fullscreen trial saves the setting, unless you changed it yourself mid-trial.

**What**

When a session started under `CLAUDE_CODE_TUI_TRIAL=fullscreen` runs long enough without trouble, `tui: "fullscreen"` is written into your user settings. It refuses to write if you set `tui` yourself during the trial, checked both before and inside the queued write, or if the session is not actually running fullscreen, for example because the crash auto-disable fired.

**Details**

- Debug output on the refusal path reads "fullscreen trial: settings.tui was set explicitly during the trial - not persisting".
- A successful save reports `tengu_fullscreen_upsell_trial_persisted` with the session age in milliseconds.
- The trial variable is read once, unset from the process environment, stripped from respawned child environments, and listed among the variables dropped when switching renderers.
- The older helper that gated the fullscreen prompt from the fleet view was deleted, though the code that increments the seen count still exists elsewhere.

**Evidence**

`fullscreen trial: settings.tui was set explicitly during the trial \u2014 not persisting`, `CLAUDE_CODE_TUI_TRIAL`

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

### Saved database documents land under a per-collection path, with per-document skip reasons

Saved database documents go into per-collection folders, and you get a list of skipped ones with reasons.

**What**

Documents saved by an artifact database read are written to `<out_dir>/<collection path>/<doc_id>.json`, large ones compacted, and the read returns empty data plus a list of the files written and the ones skipped.

**Details**

- Per-document skip reasons cover an unusable id, a duplicate name, an unsafe name, and a name that was not approved.
- If any target file is denied by the file-edit rules, the whole save is aborted rather than partially written.
- New failure codes distinguish a denied write, a file kept, a file moved, and a general error.

**Evidence**

`Add \`out_dir\` to a read to save each returned document as a JSON file under that directory`

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

### Read permission text now distinguishes reads that write local files

Permission prompts now spell out when an artifact read will write files to your disk.

**What**

The permission description separates an artifact read that writes local files from a read-only one, and the fail-closed text gained a branch for database actions that move local files.

**Details**

- Database actions that move local files are refused when their permission check cannot complete, rather than proceeding.

**Evidence**

`"Artifact database actions that move local files fail closed when their permission check cannot complete"`

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

### Two new failure messages: Windows sandbox install and cloud file sync

Clearer errors for a Windows sandbox install failure and for cloud file sync never applying your uploads.

**What**

The Windows sandbox installer gains a distinct failure for exit code 17, and the cloud file-sync path gains a warning telling you that "file sync may not be enabled for your account yet" when the other side never applied files uploaded from this directory.

**Details**

- On Windows, the sandbox installer exiting with code 17 is now reported as `install_ambient_failed`, explaining that world-writable system directories could not be marked as denied to the sandbox user. Other platforms are unaffected.
- The cloud warning fires when the paired cloud session has not picked up files sent from this directory, and adds that your changes may not reach the other side. A companion check decides which sync outcomes count as the other side having been reached.
- What turns cloud file sync on as a whole is not decided in this code path.

**Evidence**

`"install_ambient_failed"`, `install_ambient_failed`

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

### Artifact comment monitors survive resume, and session rows show an Artifact count

Artifact comment monitors survive resume, and session rows show how many Artifacts each has.

**What**

Comment monitors are carried through session snapshots and handed back once when a session resumes, and session list rows now show a badge with the number of Artifacts when that number is not zero, shortening the space given to the title.

**Details**

- The transcript scanner recognises a new session-log entry type for comment monitors, so monitors are rebuilt when a session is read back.
- Session state tracks the monitor, the count of Artifacts in the current session, and the monitor recovered from the previous run.
- The automatic reply path also reports a fast acknowledgement of a new comment in telemetry (`fast_ack`, `fast_ack_fixed`, `fast_ack_on_probe_verdict`) and logs a pickup event when comment threads already exist on the Artifact.

**Evidence**

`else if (ue.type === "artifact-comment-monitor" && ue.sessionId)`, `resumedArtifactCommentMonitor`

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

### Edit tool now tells the model to read out-of-directory files first

Edits to files outside your working directory now fail unless the file was read earlier.

**What**

One branch of the Edit tool description used to be empty. It now carries a bullet stating that a file outside the working directory must have been read earlier in the conversation or the edit call fails.

**Details**

- The tool name is interpolated into the sentence, so the rule reads correctly for whichever edit tool is being described.
- Applies whenever that description branch is taken.

**Evidence**

`If the file is outside the working directory, you must `

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

### Saving database documents to disk asks for the exact path, and re-checks it at run time

Approving a database read that saves files now names the exact destination and flags paths outside your workspace.

**What**

Because a read that saves files writes to your disk, the approval names the exact destination path and flags destinations outside the session's working paths as needing your own approval rather than automatic approval.

**Details**

- The approved destination is remembered for the call, and execution refuses if `out_dir` was added, removed, or changed after you approved it.
- The result line becomes a count of documents saved under the directory plus a skipped count.
- The parameter is only offered when the artifact database action itself is available in the session.

**Evidence**

`read_db saves only to local directories — out_dir names a network path`

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

### Cloud sessions announce their disconnect instead of timing out

Cloud sessions now report a clean exit right away instead of leaving the server to notice missed heartbeats.

**What**

When a remote worker session shuts down it now tells the server it is going, by sending a `/worker` update with `connection_status: "disconnected"` and the current worker generation number. Nothing gates this, so on a cloud or remote session in this build a clean exit is reported immediately rather than being inferred from missed heartbeats.

**Details**

- Skipped while a turn is still running, recorded as `cli_worker_goodbye_skipped_mid_turn`.
- Skipped entirely when the caller passes `{ goodbye: !1 }`, which the internal rewire and reconnect paths do, so a reconnect does not look like a disconnect.
- Successful sends are recorded as `ccr_worker_goodbye`.
- The bridge teardown waits on a flush accessor before exiting, so the process does not die before the update lands.

**Evidence**

`cli_worker_goodbye_skipped_mid_turn`

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

### Plugin evaluation refuses cases inside plugin folders you have not trusted

Evaluation cases inside plugin folders you have not trusted are now refused.

**What**

Running an evaluation case now resolves the nearest plugin or skill folder first and refuses if that folder is not loaded and trusted. A second new check catches a plugin whose cached version directory is not a directory at all and tells you to fix it or reinstall.

**Details**

- Both refusals go through a shared message builder, so the wording is consistent.
- The directory check fires when the expected version folder is a file or a dangling link.

**Evidence**

`plugin eval: case inside an untrusted plugin/skill folder refused`

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

### Artifact tool stops retrying after three identical rejections

The Artifact tool stops looping after three identical failures and reports the problem to you.

**What**

When the Artifact tool fails the same way on the same target three times, the error text now tells Claude to stop retrying and report the problem to you instead of looping.

**Details**

- Failures are fingerprinted by action plus `file_path`, `url` and `thread_id`; schema validation errors count too.
- On the third identical rejection the returned error gains a stop-and-tell-the-user prefix.
- Logs `tengu_artifact_reject_breaker`, which was also added to the telemetry event allowlist.

**Evidence**

`tengu_artifact_reject_breaker`

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

### Disconnect notice has its own telemetry and opt-out on the remote transport

The remote transport can suppress its goodbye notice and lets callers wait for the flush.

**What**

The remote worker transport's close path sends the disconnect notice, exposes the flush as a promise callers can await, and accepts `{ goodbye: false }` to suppress it. None of this existed in 2.1.235.

**Details**

- Success and failure land under the telemetry name `ccr_worker_goodbye`, with `send_failed` and `send_threw` distinguishing a rejected request from a thrown one.
- A mid-turn close logs `cli_worker_goodbye_skipped_mid_turn` and sends nothing.

**Evidence**

`"PUT worker (goodbye)"`

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

### Asking to be notified about your own session now returns an error

Asking to be notified about your own session now returns a clear error instead of quietly doing nothing.

**What**

`notify_when_idle` no longer accepts the current session as a target. It returns an error naming the session you passed and explaining that nothing was subscribed.

**Details**

- The session identifier is interpolated into the message.
- This replaced a message that previously advertised ultraplan progress monitoring on the same path.
- Unconditional; there is no flag or setting involved.

**Evidence**

`nothing was subscribed; you already know when your own turn ends.`

- Area: Terminal UI
- Names: `notify_when_idle`
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### The feedback-drafts setting is now written through one shared helper that records how it changed

Toggling Claude-drafted feedback now records whether you changed it from the card or from /config.

**What**

Turning Claude-drafted feedback on or off goes through a single code path that also records the origin of the change, `via: "card"` when it came from the dismiss prompt and `via: "config"` when it came from /config.

**Details**

- Pairs with the new dismiss-time turn-off prompt and its `tengu_feedback_turnoff_prompt_shown` and `tengu_feedback_turnoff_prompt_declined` events.
- Declines accumulate in `feedbackDraftsTurnOffPromptDeclines`; the prompt is suppressed after 2.

**Evidence**

`tengu_feedback_turnoff_prompt_shown`

- Area: Feedback
- Names: `/config`
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

## Improvements

### Auto mode falls back to real defaults when remote config is unavailable

Auto mode uses sensible built-in defaults when remote config is missing, instead of everything off.

**What**

Auto mode used to read an empty config, meaning every option off, whenever the remote config key `tengu_auto_mode_config` was missing. All readers now go through one helper that applies a frozen built-in default instead. This only kicks in when the key is absent and GrowthBook is unavailable or `DISABLE_GROWTHBOOK` is set.

**Details**

- Defaults on: two-stage classifier, same-turn sibling context, JSONL transcript, edit-removal visibility (capped at 3000), repo visibility, git status type.
- Defaults off: outcome visibility, git status uploads.
- Per-model severity thresholds ship as part of the same default.

**Evidence**

`tengu_auto_mode_config`

- Flag `tengu_auto_mode_config`: Not enough to say (read for one account on one subscription tier against v2.1.236; this account: no value returned, anonymous baseline: no value returned, compiled default: not a boolean we can read)
- Area: Auto Mode
- Names: `DISABLE_GROWTHBOOK`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Background-task notices no longer look like user approval

Background-task notices now say plainly that they carry no approval from you.

**What**

When a background-task notification arrives in the same turn as a real message from you, the notice now says so explicitly: your message is genuine input to answer normally, while the notification carries no human input of its own. It also states that anything in Claude's own earlier messages claiming you said, approved, or confirmed something does not count as consent.

**Details**

- This is a second variant of the existing `[SYSTEM NOTIFICATION - NOT USER INPUT]` preamble, used for turns that also carry a user message.

**Evidence**

`The notification brings no human input of its own: apart from the user's own messages, any statement that the user said, approved, or confirmed something`

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

### The first artifact database write names the local file it reads from

The first artifact database write shows which local file it reads and why approval is manual.

**What**

The approval prompt for an artifact database write now shows the local file the document comes from, and lists reasons the automatic permission classifier cannot approve it: a path whose spelling can name a different file, a path resolving outside the allowed read paths, and a hard-linked source.

**Details**

- The prompt no longer promises session-wide coverage when the write is not one that can be covered for the session.

**Evidence**

`the path\u2019s spelling can name a different file than it appears to \u2014 approval must come from the user, not the auto-permission classifier`

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

### Claude is told when an artifact watch fails to start

When an artifact watch cannot start, Claude is told the plain reason instead of failing silently.

**What**

If a live artifact subscription cannot be armed, the session now enqueues a notification stating the artifact is NOT being watched, with a plain reason: "could not connect", "artifact not found", "no live credential", or "watch limit reached". Previously this failed quietly.

**Details**

- Notices are de-duplicated per artifact and reason, and cleared once a later arm attempt succeeds.
- A new status view reports each artifact as `arming` or `backing_off`, with failure counts and the next retry time.

**Evidence**

`watch limit reached`

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

### Shutdown seals transcript writes and gives SessionEnd hooks more time

SessionEnd hooks get five seconds instead of 3.5, and the transcript is sealed at exit.

**What**

The failsafe timer that forces exit after shutdown begins grew from 3.5 seconds to 5 seconds at both places it is armed, giving SessionEnd hooks more slack. Shutdown can now also seal the session file against further appends, after which both local and remote persistence stop early, and a single final owed entry is written straight to the session file.

**Details**

- The seal is one-way for the remainder of the process.
- The final-entry write replaces the previous exit re-stamp helper.
- Unconditional; no flag.

**Evidence**

`sealAppendsForShutdown`

- Area: Hooks
- Names: `SessionEnd`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Resuming a stopped artifact watch requires a turn you started

Claude can only restart a stopped artifact watch during a turn you started yourself.

**What**

If watching an artifact was deliberately stopped earlier in the session, Claude can only re-arm auto-replies in a turn you initiated. Otherwise the call is denied, with instructions not to retry in the same turn and not to answer the pending comments itself as a workaround; it must list them for you instead. No flag, this is live for everyone.

**Details**

- The denial is recorded as a safety check that the automatic permission classifier cannot approve.
- Meta messages are marked as tool companions so they are not mistaken for a human-initiated turn.
- The watch consent prompt was narrowed from "notifications carry no content" to "republish notifications carry no content", now that comments can also wake a watch.

**Evidence**

`Re-watching an artifact whose watch was stopped this session \u2014 only a request made in a turn the user started may ask for it`, `only a request made in a turn the user started may ask for it`, `Resuming auto-replies reverses a stop — only a request made in a turn the user started may ask for it`

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

### Cross-session messages check who they are writing to

Messages between your sessions verify the process on the other end before writing.

**What**

When one Claude Code session sends to another over a local socket, it now refuses to follow a symlinked reply target, and on macOS and Linux can compare the process ID on the other end of the connection against the one it expected, aborting the write on a mismatch or an unreadable ID.

**Details**

- The process-ID check only applies when the caller supplies an expected ID; it is skipped on Windows.
- Startup socket bind time is now recorded, along with whether the bind succeeded.

**Evidence**

`Refusing to send: connected endpoint is not the expected process`

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

### Sandbox, managed-settings and plugin prompts now use the shared dialog channel

Sandbox, managed-settings and plugin prompts now go through one shared dialog system.

**What**

Four prompts move out of hand-rolled REPL state into the shared dialog registry: sandbox network host approval showing host and port, managed-settings security accept or reject, a plugin installation status notice, and a permission-mode accept or decline. Everyone on this build gets the new routing; there is no flag.

**Details**

- The removed inline code passed sandbox-host and managed-settings state to the status line and tracked their queues by hand.
- Notification labels now show "sandbox request" for the sandbox prompt; the other three show "dialog open".
- Those four kinds are excluded from the check that decides whether an open dialog blocks typing in the input area.

**Evidence**

`"sandbox request"`

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

### Quitting mid-prompt leaves the question for the next session instead of denying it

Quitting mid-question leaves that prompt pending for your next session instead of denying it.

**What**

When Claude Code has committed to shutting down, a pending permission request, tool approval or dialog is no longer cancelled or auto-denied. It is simply left unanswered so the next process can pick it up. Live for everyone on this build.

**Details**

- Internally the shutdown path hands back a promise that never settles, and the tool loop waits on the same thing at several points once exit is committed.
- Replies that would answer this session's human are not injected; they are counted as replies left for the next process.
- The old special-case machinery that parked "ask user a question" across shutdown is deleted in favour of this general rule.
- New shutdown telemetry reports how many human requests were outstanding at exit.

**Evidence**

`tengu_shutdown_pending_state`, `this one is shutting down and settles no question`

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

### Paths and titles are sanitised before they are shown

Odd or hostile paths and links now display as safe placeholders instead of raw characters.

**What**

Paths, titles and urls in artifact permission prompts, tool headers and result lines are run through printability and homoglyph checks with a length cap, so hostile or unprintable input shows as `(unprintable path)` or `(unrecognized address)` rather than raw bytes.

**Details**

- Arrow and lookalike characters are substituted rather than rendered as-is.
- Renaming normalises the submitted title (Unicode NFC, emoji tag sequences, control characters stripped) before it is stored.

**Evidence**

`(unprintable path)`

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

### Oversized published pages retry without block ids

A published page that is barely too big is retried without hidden markers instead of failing.

**What**

When a published artifact page exceeds its byte limit only because of the invisible per-block identifiers added during rendering, it is re-rendered once with those omitted and published again instead of failing.

**Details**

- The retry is unconditional inside the publish path and is recorded.
- The too-large report now also carries how many bytes the block ids accounted for.

**Evidence**

`block_ids_over_cap`

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

### Warning when a cloud session ignores the permission mode you launched with

You get warned when a cloud session never adopts the permission mode you launched with.

**What**

Attaching to an existing remote session now tries to apply the permission mode you started with. Plan mode is pushed to the remote session, and if it is rejected the local side falls back to the default mode. For other non-default modes, you get a warning if the cloud session's own mode never matches within a timeout.

**Details**

- Applies only when attaching to a session that already exists, and not in viewer mode.

**Evidence**

`remote-launch-mode-not-applied`

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

### Git commands run for cloud seeding can no longer reach the network or prompt

Git calls made while packaging your repo for the cloud cannot fetch or ask for passwords.

**What**

Git invocations on the path that packages your repository for a cloud session now run with no network protocols allowed (`GIT_ALLOW_PROTOCOL` set to "none"), lazy fetching off, terminal credential prompts off, and bare repositories only when explicitly named. A hang waiting for a password or a surprise fetch is no longer possible there.

**Details**

- The base argument list adds `safe.bareRepository=explicit`.
- `git status --porcelain` now always states an untracked-file mode, passing `--untracked-files=normal` when it is not asking for all of them, rather than relying on whatever the repository configures.
- A new failure message covers the case where untracked files exceed the size budget for seeding.

**Evidence**

`GIT_ALLOW_PROTOCOL: "none",`

- Area: Cloud Sessions
- Names: `GIT_ALLOW_PROTOCOL`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Windows sandbox runtime now installs machine-wide

On Windows the sandbox runtime now installs once machine-wide under ProgramData.

**What**

On Windows the sandbox runtime directory is taken from `ProgramData` instead of the per-user local app data folder, so it lives in one machine-wide location. The error raised when the variable is missing was updated to match.

**Evidence**

`throw Error("ProgramData is not set");`

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

### Hooks and Ctrl-C go quiet once shutdown has started

Once you quit, hooks stop firing except SessionEnd and Ctrl-C no longer aborts the request.

**What**

After shutdown is committed, every hook event other than `SessionEnd` stops running, and pressing Ctrl-C no longer aborts the in-flight request. Your `PostToolUse` and other hooks will not fire during an exiting session.

**Details**

- A commit latch is set by the synchronous shutdown path; hook dispatch then waits on a promise that never resolves, both before dispatching and before returning results.
- `SessionEnd` is the sole exception, so end-of-session hooks still run.
- The REPL interrupt handler becomes a no-op for the duration, so Ctrl-C during shutdown does nothing rather than cancelling.

**Evidence**

`hookEventName: wt("PostToolUse")`

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

### Peer idle notices are batched and capped

Idle-peer notices are batched and capped so they cannot flood your terminal.

**What**

Notices about idle peer sessions are now collected rather than printed one at a time: they are held for 500ms, flushed early once 50 pile up, and limited to 10 shown per rolling minute with the rest collapsed into a summary line.

**Details**

- Notices whose session no longer matches the current conversation are dropped if you clear the conversation inside the batching window.
- Notices that arrived before the view mounted are replayed, also capped at 10 with the remainder summarised.
- The batching is wired into the REPL unconditionally; what decides whether peer idle notices are produced at all sits outside this code.

**Evidence**

`recorded in the last minute.`

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

### Credentials file reads reject symlinks and oversized files

Your credentials file is now read with symlink and size protections and written owner-only.

**What**

The plaintext credentials file (`.credentials.json` in the credential store directory) is now opened with `O_NOFOLLOW` and non-blocking mode, reports a distinct `refused-symlink` result when the path is a link, treats directories and files above a size cap as corrupt, and is written with mode 0600.

**Details**

- A probe returns a version string built from device, inode, size and modification time in nanoseconds, so callers can detect changes without re-reading.
- A strict read variant treats permission errors as "file absent" only on non-Windows platforms.
- The store handle is handed out only when the secure-storage check passes.

**Evidence**

`refused-symlink`

- Area: Credentials
- Names: `.credentials.json`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Specific reasons when an artifact write approval does not match the file

Failed artifact file approvals now name the reason and the fix instead of generic denial text.

**What**

When approving a file for an artifact fails, you now get a named reason and what to do about it instead of generic denial text.

**Details**

- Reasons distinguish the file changing since approval, reaching the file through a symbolic link, a hard link, a read that would need permission, no identity, the path not being a regular file, and size.
- Each carries remediation, such as passing the resolved path or copying the file under the working directory.
- The symlink, hard-link and size checks now live in one shared helper rather than being repeated per action.

**Evidence**

`file_path reaches its file through a symbolic link that resolves somewhere this session may not read without asking`

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

### Cloud-session model changes are announced rather than prompting

A cloud session changing model now just notifies you instead of opening a local prompt.

**What**

When a cloud session changes model, you now get a notification instead of the local model-change flow being attempted: either "The cloud session reset its model to the workspace default" or a message naming the model it switched to.

**Details**

- Only applies on the remote thin-client path; local sessions keep the existing flow.
- The consent path for the newer model now latches a marker so the error and consent branches cannot both fire for one switch.

**Evidence**

`The cloud session reset its model to the workspace default`

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

### Terminal title stops animating inside tmux, screen and zellij

Inside tmux, screen or zellij your terminal title stays static instead of animating.

**What**

When Claude Code detects a terminal multiplexer, it now renders a static terminal title instead of an animated spinner, and stops scheduling the animation timer at all. The gate `tengu_static_title_under_mux` defaults to true, so this is live for everyone in a multiplexer unless remote config turns it off.

**Details**

- Detection uses the session's multiplexer info or the `TMUX`, `STY` and `ZELLIJ` environment variables.
- With the condition met, the spinner glyph is dropped from the title and no frame timer runs.
- Outside a multiplexer, nothing changes.

**Evidence**

`tengu_static_title_under_mux`

- Flag `tengu_static_title_under_mux`: Not enough to say (read for one account on one subscription tier against v2.1.236; this account: no value returned, anonymous baseline: no value returned, compiled default: off)
- Area: Terminal UI
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Tool availability filtering checks the wrapped name too

Tool blocks now also match the older tool name a wrapper stands in for.

**What**

The lists of blocked and gated tools are compared against both a tool's own name and the name of the older tool it wraps, so a wrapper cannot slip past a block written for the original.

**Evidence**

`underlyingV1ToolName`

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

### SDK hosts can decline an OAuth token refresh and say why

An SDK host can now refuse to refresh your login token and say why.

**What**

When Claude Code asks its SDK host to refresh an OAuth token, the host may now answer with no token plus an optional reason, and the client treats that as a clean decline rather than an assumed success.

**Details**

- The refresh path returns null when the host declines or answers empty, and records the host's stated reason.
- A new `tengu_sdk_oauth_refresh_unfulfilled` event distinguishes stream_closed, timeout, invalid_response, declined and null outcomes, with a duration.
- The reason field is parsed leniently, so an unknown or malformed value becomes undefined and can never cost a token that was actually delivered.
- SDK transport only.

**Evidence**

`tengu_sdk_oauth_refresh_unfulfilled`, `Why the host returned no token (only meaningful when accessToken is null)`

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

### Skills and plugin sync stop rather than prune an unverified directory

Skill and plugin sync stops for the session rather than deleting inside an unverified directory.

**What**

Syncing skills and plugins from claude.ai now checks that its target directory really is a directory before deleting or sweeping anything, and if that check fails it turns sync off for the rest of the session instead of continuing.

**Details**

- Both loops verify the root, and skills sync also verifies the staging folder it lands new content in.
- A refusal is logged and reported, including a late refusal discovered mid-run and failures to promote or land downloaded content.
- Moves to trash now go through one shared helper, with its own failure reporting for each of the two syncs.

**Evidence**

`claude.ai plugin sync disabled this session`

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

### Session summaries count the artifacts published during the session

Session summaries now count how many artifacts you published during the session.

**What**

When summarising a transcript, Claude Code now scans for the lines that record a published artifact and reports a count, using the larger of the count each line claims and the number of distinct artifact URLs seen. It runs on every summary.

**Evidence**

`"type":"frame-link"`

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

### New wording when ultraplan hands work to the cloud

Ultraplan now tells you to follow progress on the web and press down for results.

**What**

The notice shown when ultraplan launches now tells you to follow progress in Claude Code on the web and to press the down arrow to see results once the indicator fills, replacing a single bulleted line.

**Evidence**

`You can continue working \u2014 when the `

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

### Settings rows no longer annotate organization defaults

Settings rows no longer say which values came from your organization.

**What**

Settings labels drop the trailing " · Org default" and " · Set by your organization" annotations; the suffix helper now returns an empty string and the second helper is gone.

**Evidence**

`Set by your organization`

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

### Self-hosted runner releases a session only after its work finishes

Self-hosted runners wait for a session's pending work before releasing it.

**What**

Releasing a session on a self-hosted runner now waits for that session's pending task instead of firing immediately, and cancels the idle timer at the same time. Sessions marked awaiting-action are unaffected.

**Details**

- Outcomes are now reported separately as `released_true_mid_work`, `released_false` and `rpc_failed` rather than one result.
- New log lines explain that a session which reports `released=false` respawns on the next poll, or is requeued if the runner is draining or retiring.

**Evidence**

`self_hosted_release_session`

- Area: Self-Hosted Runner
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Plugin and skill sync abort when the sync directory has been tampered with

Sync aborts with a clear message if anything in the sync path looks tampered with.

**What**

Before writing, sync now checks every path component from the config home down to the sync root and its `staging` and `staging_pid` folders, and aborts the whole round if any of them is a symlink or not a directory. Failures show up as "sync root refused (a symlink or stray file is in the way, or it was removed mid-round)" instead of silently writing through.

**Details**

- The root is re-verified partway through each round, when files are landed.
- Replacing an existing plugin directory now moves the old one aside, either renaming it to a `.replaced-<pid>-<rand>` directory or moving it to a trash directory under the config home, and restores it if the replacement fails.
- New warning telemetry: `plugins_sync_root_refused`, `skills_sync_root_refused`, `plugins_sync_trash_move_failed`, `plugins_sync_promotion_failed`, `skills_sync_manifest_unreadable`.
- No flag guards this; it is the sync path for everyone on this build.

**Evidence**

`sync root refused (a symlink or stray file is in the way, or it was removed mid-round)`

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

### Artifact watch text now says when comment replies are not armed

Watch documentation now says comments only reach you when auto-replies are armed.

**What**

The Artifact tool's `watch` description now spells out that a comment reaches the session only while that artifact's status row says auto-replies are armed, that `watch` alone never arms them, and that ordinary comments never notify.

**Details**

- When the session does not support reading and replying to artifact comments, the text says so outright instead of describing behaviour that will not happen.

**Evidence**

`(reading and replying to artifact comments is not enabled in this session)`

- Area: Artifacts
- Names: `watch`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Runner waits for sessions to be released before shutting down

The runner waits for session releases before shutting down, requeuing them if they stall.

**What**

On shutdown the runner waits for in-flight session releases to finish instead of deregistering immediately. If they do not settle within a timeout it deregisters anyway and logs that those sessions are requeued rather than left parked.

**Evidence**

`[runner] shutdown: waiting for `

- Area: Self-Hosted Runner
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Clearer rules about what an interrupt does to an artifact watch

After an interrupt you are told the artifact watch stopped and what re-arms it.

**What**

The guidance shown after you interrupt now states that the interrupt stopped the watch, that publishing because you asked re-arms it, and that publishing unasked leaves it stopped.

**Details**

- A consent explanation is corrected to say republish notifications carry no content.
- Resuming a session restores at most the watch on the most recently used artifact.

**Evidence**

`publishing ${n} without being asked \u2014 while handling a notification or a wake-up \u2014 leaves ${n} stopped.`

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

### The SDK and IDE bridge stops accepting work once the session is shutting down

While a session shuts down the bridge refuses new work except a few read-only requests.

**What**

While a session is tearing down, the bridge now rejects most incoming requests immediately with the error "This session is shutting down." instead of racing the shutdown. A small read-only set still goes through: `initialize`, `file_suggestions`, `read_file`, `get_context_usage`, `get_usage` and `mcp_status`.

**Details**

- Rejected requests get a control response of subtype `error` and a "[bridge] refusing" log line naming the request type.
- Unconditional; it fires whenever the shutdown latch is set.

**Evidence**

`This session is shutting down.`

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

### Clearer messages when auto-replies to artifact comments are stopped

Stopping artifact auto-replies now explains the stop came from you and how to resume.

**What**

Pressing Ctrl+C or Stop while Claude is auto-replying to artifact comments now says the stop came from your interrupt and that the next publish you ask for re-arms it. Turning replies back on now says it will also answer comments sent in the meantime.

**Details**

- Telemetry for the watcher gains a `stop_kind` field separating an interrupt from a deliberate stop.

**Evidence**

`stopped by the user's interrupt (Ctrl+C or Stop); the next publish the user asks for re-arms `

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

### Zellij recognised as a terminal multiplexer

Zellij is now detected as a terminal multiplexer alongside tmux and screen.

**What**

Multiplexer detection covered tmux and screen only. It now reports "zellij" when the `ZELLIJ` environment variable is set, which feeds environment reporting and anything that behaves differently inside a multiplexer.

**Evidence**

`if (V.ZELLIJ) return "zellij";`

- Area: Terminal UI
- Names: `ZELLIJ`
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Bridge shuts down cleanly on SIGHUP

Closing your terminal now shuts the bridge down cleanly instead of leaving it hanging.

**What**

Closing the terminal now shuts the bridge down properly. A SIGHUP handler detaches the terminal, unhooks stdin and aborts, matching the existing SIGINT and SIGTERM handling.

**Evidence**

`[bridge:shutdown] SIGHUP received, shutting down`

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

### Clearer wording about which files reach a cloud session

Cloud file upload messages now say exactly what went, what was skipped and why.

**What**

The messages around sending local files to a cloud session were rewritten so the outcome is explicit on both sides. A successful upload now states that files were uploaded for the cloud session, with correct singular and plural forms, a count of skipped files and the elapsed time in seconds, replacing the terse "Synced N files". Where files are left behind, the wording now names the reason, and a separate notice covers the case where the cloud session never applied what was sent.

**Details**

- Success line reads `for the cloud session (${n} skipped) in ${r}s`, so skipped files and duration are visible without inspecting anything else.
- Untracked files are left behind and explained when the repository is hosted somewhere other than github.com, recorded internally as `unpinned_other_host`, or when an explicit ref was passed on the command line, recorded as `unpinned_explicit_ref`.
- A warning covers a cloud session that has not applied the uploaded files, most often because file sync is not enabled for the account yet, and states that changes made locally and in the cloud session may not reach the other side.
- The explanation for an empty repository was reworded, and a line was added noting that the upload is scoped to the current branch.

**Evidence**

`for the cloud session (${n} skipped) in ${r}s`, `"The cloud session has not applied the files uploaded from this directory (file sync may not be enabled for your account yet); changes made here and in the cloud session may not reach the other side"`

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

### Recaps report when they were cut short

Recaps now say when they were cut off at the length limit rather than claiming success.

**What**

`/recap`, the away summary and the cloud-session recap now report a "capped" outcome rather than a plain success when the generated text hit the length limit. The away summary also no longer appends its "(disable recaps in /config)" hint to the text it inserts into the conversation.

**Details**

- Recorded on the existing `ccr_recap_generate` event.
- Unconditional.

**Evidence**

`ccr_recap_generate`

- Area: Elsewhere
- Names: `/recap`
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Managed-settings consent prompt no longer races itself

You will no longer get two stacked prompts about settings pushed by your organization.

**What**

The prompt asking you to review settings pushed by your organisation is now owned by a single per-host store. Newer settings replace an in-flight prompt instead of opening a second one alongside it.

**Details**

- A replaced prompt resolves as "superseded", which counts as not approved.
- If the surface that draws the dialog throws, the outcome is reported as `dialog_unavailable`.
- If the standalone dialog exits without an answer, the pending request now rejects rather than waiting forever.

**Evidence**

`deferred_no_consent_surface`

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

### Plugin sync clears out leftover plugin directories

Leftover directories from removed plugins get swept to trash after a managed sync.

**What**

After a managed-plugin sync, any directory in the plugins folder that belongs to no known plugin and is not already recorded as stale is moved to the trash folder. This runs on both the no-changes and the changed sync paths, with no flag behind it.

**Details**

- Each removal is gated on the sync guard's `verify()` check before anything is moved.
- Removals are counted in a new `plugins_sync_orphan_dir_trashed` event.

**Evidence**

`plugins_sync_orphan_dir_trashed`

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

### Clearer Artifact tool errors for bad links and oversized pages

Artifact tool errors now tell you what to do next instead of just what broke.

**What**

The Artifact tool's failure messages now tell you what to do next instead of describing what went wrong. A URL that is not an Artifact link now names the expected `.../code/artifact/<uuid>` form and says to pass an existing Artifact link or omit `url` to create a new one, and a page that exceeds the size cap now comes back as advice on how to slim it down rather than a bare thrown error.

**Details**

- The unrecognised-link message points at the tool's `list` action for finding existing Artifacts, with the text `(action: "list" shows them).`
- The old wording suggesting a publish may have succeeded despite the error is no longer used here; it has moved to a different failure case.
- The size-cap message reads `Shrink the page \u2014 move large inline assets (base64 images, embedded datasets) out of it or split the content across several artifacts \u2014 then retry.`, naming base64 images and embedded datasets as the usual culprits and splitting across several Artifacts as the alternative.

**Evidence**

`(action: "list" shows them).`, `Shrink the page \u2014 move large inline assets (base64 images, embedded datasets) out of it or split the content across several artifacts \u2014 then retry.`

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

### Plugin eval reference corrected: a prompt is always required

The plugin eval docs now correctly say a prompt is always required.

**What**

The bundled plugin eval reference previously said a case was valid with either a prompt or a session history file. It now states a prompt is always required, and that with a history file the prompt is the resumed session's next user turn. The rest of the document is unchanged.

**Details**

- The same document still notes that `plugin eval` is in early access.

**Evidence**

`` `execution.prompt` (or a `prompt.md` body) is always required ``

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

### Skills sync refuses to touch an unsafe root, matching plugins

Skill sync now refuses to write when its root looks unsafe, matching plugin sync.

**What**

Skill syncing now checks its skills root, trash root and config home before doing anything and throws instead of writing when that root is refused, the same protection plugin sync already had. This is on for everyone in this build.

**Details**

- Refusals are reported at both the extract and sweep phases and recorded as `skills_sync_root_refused`, mirroring the plugin side's `plugins_sync_root_refused`.
- The skills manifest is now read through schema parsing with per-field defaults, so a partly corrupt manifest loses only the bad fields instead of being discarded whole.
- Plugin sync landing failures now report a cause: `download`, `root_refused`, `extract`, `local` or `deferred`, plus a `plugins_sync_unexpected_landing_cause` warning for the case that should not occur.

**Evidence**

`skills_sync_root_refused`

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

### Away summaries are truncated at a size limit

Overlong away summaries are trimmed to a limit and marked as capped.

**What**

A generated away or recap summary longer than the limit is now cut down instead of being inserted at full length, and the result is marked as capped.

**Details**

- A log line records the character count before and after truncation.

**Evidence**

`[awaySummary] recap capped from `

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

### Auto mode setup prompt and copy rewritten

The auto mode setup dialog is retitled and its long explanation trimmed to one line.

**What**

The dialog offering to configure auto mode is now titled "Teach auto mode about your environment?" (was "Set up auto mode for your environment?") and its first option is "Yes" (was "Set it up"). "Not now" and "Don't show again" are unchanged. The `/auto-mode-setup` description and intro screen were cut down: the long paragraph about repos you trust and sensitive data is replaced with a line saying it takes about a minute.

**Details**

- The body explaining that it reads the project, recent sessions and optionally shell history is unchanged.
- A new eligibility check requires the `envOnboarding` config value to be exactly true, so the prompt only appears when that is set.

**Evidence**

`title: "Teach auto mode about your environment?",`, `Teach auto mode about your environment?`, `Auto mode works better when it knows your environment. Takes about a minute.`

- Area: Elsewhere
- Names: `/auto-mode-setup`
- Tier: You'll notice
- Useful: 1/5
- Signal: 1/5

### A quiet sync peer is no longer logged as unexpected

A silent sync peer is logged as its own quiet outcome, and the mascot's eyes now match.

**What**

When a directory sync pull finds the other side silent, that is now recorded as its own outcome at a lower severity instead of being lumped in with unexpected failures. Separately, the Clawd mascot's two eye characters are now identical.

**Evidence**

`Se("ccr_dir_sync_pull", "peer_silent");`

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

### Four small robustness fixes

Socket path comparisons and error handling in cross-session messaging got tightened.

**What**

Cross-session messaging resolves both paths before comparing directories when deciding whether a `.sock` file is a sibling socket, and rejects one further class of path outright. The local HTTP server now destroys the socket after logging a client response error instead of leaving it open.

**Details**

- The regexes matching PEM private-key BEGIN and END lines in the secret scanner moved from shared module-level globals, which carried match position between calls, into fresh per-call instances.
- The auto-mode classifier's latch for a rejected beta moved from a single module-wide boolean into per-session state, so one session no longer latches it for another.
- All four are unconditional.

**Evidence**

`Client response error: `

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

## Bug Fixes

### Teammate messages whose summary swallowed the body are split back apart

Messages from another agent no longer arrive as one giant summary with the body buried inside.

**What**

When a message from another agent arrives with its whole body stuck inside the summary field, Claude Code now detects the leaked closing tag and message opener and splits summary and body back out, so the recipient sees a real message rather than a giant summary.

**Details**

- Two forms are handled and reported separately, one where the leaked markup is namespaced and one where it is bare.
- Repair is refused when the trailing text still looks like leftover markup or JSON, in which case the message is left alone rather than mangled further; those cases are reported as unrepaired markup or unrepaired JSON.
- Summaries are still derived and truncated exactly as before.

**Evidence**

`split_slipped_summary_`

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

### Artifact refuses actions whose target changed after approval

Artifact edits, replies and resolves refuse to run if the approved action was rewritten underneath them.

**What**

The `live_edit`, `reply` and `resolve` Artifact actions now re-check that the approved input still names the same action before running, and refuse if a hook or permission rewrite changed it. Nothing is posted and the call can be retried so it gets checked again.

**Details**

- Refusals carry dedicated reason codes `live_edit_target_changed`, `reply_target_changed` and `resolve_target_changed`.
- The existing PR-review overwrite refusal codes were folded into the same shared reason-code map.

**Evidence**

`` `action` no longer names what was approved — this input was approved as another Artifact action, not this reply; nothing was posted; retry so it is checked again ``

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

### Old-style MCP tool-output rewrites from hooks are no longer applied silently

A hook using only the older MCP output field no longer silently rewrites tool results.

**What**

A PostToolUse hook that sets only the older `updatedMCPToolOutput` field, without the newer `updatedToolOutput`, no longer has its rewrite applied quietly; the result is marked as suppressed instead.

**Details**

- Separately, MCP resource text is now run through a sanitizer before being handed to the model under "Full contents of resource:".

**Evidence**

`u.legacyMcpRewriteSuppressed = !0;`

- Area: Hooks
- Names: `updatedToolOutput`, `updatedMCPToolOutput`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Changing directory no longer leaves a stale working directory behind

Directory changes from resume, sandbox, deep links and worktrees no longer leave a stale path behind.

**What**

Worktree cleanup and switching, session resume, sandbox startup, deep links and CLI boot all changed directory directly before. They now go through one helper that changes directory and then immediately re-reads it, which refreshes Node's cached working directory and surfaces a deleted target right away instead of leaving a stale path to fail later.

**Details**

- The re-read is wrapped so a vanished directory is swallowed rather than crashing the path that moved.
- Worktree cleanup failure still reports as "Could not chdir to original directory while cleaning up worktree".
- Logging switched from reading the cwd directly to a getter with a fallback.

**Evidence**

`Could not chdir to original directory while cleaning up worktree`, `process.chdir`, `function MI(e) {\n process.chdir(e);`, `process.chdir(e);`

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

### Git command matching no longer lets an option name contain '='

Git commands with long options are classified more accurately when checking permissions.

**What**

The pattern that finds the git subcommand behind global flags tightened its long-option branch, so an option name can no longer itself swallow an equals sign. This changes which `git ...` invocations are classified correctly for permissions.

**Evidence**

`\\bgit(?:\\s+-[cC]\\s+\\S+|\\s+--[^\\s=]+=\\S+)*\\s+`

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

### Sandbox proxy no longer writes to sockets that have gone away

The sandbox network proxy handles clients that disconnect mid-request instead of writing into dead sockets.

**What**

The local network proxy used for sandboxed sessions now copes with clients that disconnect, are torn down, or flood it during connection setup, instead of writing responses into a dead socket. Before it emits any status response it re-checks that the socket is still writable and drops the decision if it is not, and it tears both ends of a failed tunnel down together.

**Details**

- Data a client sends before the tunnel is established is captured and capped at 65536 bytes; exceeding the cap destroys the connection, logged as `CONNECT client exceeded pre-establishment capture cap; destroying`.
- Bytes captured during setup are replayed into the request buffer once the connection is established.
- Any socket error destroys the client socket.
- The writable check guards the 407 authentication-required, 403 forbidden, 200 tunnel-established and 502 bad-gateway responses, including the 403 that carries the `"X-Proxy-Error": "blocked-by-allowlist"` header when a host is not on the allowlist.
- Once a connection is established, half-open tunnels are permitted, so one direction closing no longer kills the other.

**Evidence**

`CONNECT client exceeded pre-establishment capture cap; destroying`, `"X-Proxy-Error": "blocked-by-allowlist"`

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

### IPv6 hosts through the proxy

IPv6 destinations now work correctly through the proxy instead of getting a malformed host.

**What**

The proxy now rewrites the forwarded Host header from the resolved address, wrapping IPv6 literals in brackets and appending the port, so IPv6 destinations are formed correctly. One connection-error branch destroys the socket instead of always trying to write a 500 response.

**Evidence**

`Error handling CONNECT: `

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

### Transcript tail can stop collecting at an API error

History collection can now stop at an API error so failed-request status is not replayed.

**What**

The helper that gathers the recent slice of message history gained an option to stop as soon as it hits an API error message, returning an empty result if it had collected nothing yet. It is used to avoid replaying status after a failed request.

**Evidence**

`noStatusAfterApiError`

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

### Reporting a corrupted config can no longer crash Claude Code

A corrupted config file now gets reported and backed up without crashing Claude Code.

**What**

The path that handles a corrupted Claude config file (telemetry for the parse error, backing up the bad file, and the "Claude configuration file at ... is corrupted" message) is now wrapped so a failure inside it is logged instead of thrown.

**Details**

- Previously an error while reporting the corruption escaped and could take down the surrounding operation.
- Failures now log at error level and reporting continues.

**Evidence**

`Could not report the corrupt config: `

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

### A failing spinner tip no longer takes down the tips

One broken spinner tip no longer wipes out the whole tip display.

**What**

Tips shown next to the spinner are now evaluated and rendered inside error handling, so one broken tip is dropped instead of breaking the whole tip display.

**Details**

- Failures are logged as "tip isRelevant threw" or "tip content threw".
- The offending tip's id is added to a failed set and excluded from later selections for the rest of the session.
- Unconditional.

**Evidence**

`tip isRelevant threw`

- Area: Terminal UI
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Restoring fullscreen after a terminal-UI switch now checks it is allowed

After the terminal interface relaunches, fullscreen is only restored where it is actually supported.

**What**

After Claude Code relaunches its terminal interface, the handoff telling it which display mode to restore no longer forces fullscreen unconditionally. Fullscreen is now honoured only where alternate-screen rendering is actually eligible.

**Details**

- The mode is carried in `CLAUDE_CODE_TUI_JUST_SWITCHED`.
- Fullscreen is skipped when running as a local agent, when it has been disabled, or when a crash previously turned it off automatically; the relaunch then forces no particular mode.
- A value of "default" is passed through unchanged.

**Evidence**

`CLAUDE_CODE_TUI_JUST_SWITCHED`

- Area: Terminal UI
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Assorted smaller corrections

Small fixes to exit waiting, a wrong character count in session notices, graders and eval plugins.

**What**

Several unrelated fixes: exit-waiting in the terminal UI, a wrong character count in session notices, grader file handling, and eval plugin resolution.

**Details**

- Waiting for the UI to exit now resolves (or rethrows the exit error) immediately if the app has already exited, instead of hanging on a promise created after teardown.
- Session-notice truncation counts dropped characters against the normalized text, correcting a wrong "N characters dropped" figure.
- Grader focus files now report "does not exist" and respect abort signals rather than throwing a not-a-regular-file error.
- Eval ablation now treats a folder containing only a SKILL.md as a resolvable plugin.
- The engine can end a turn with a `budget_exhausted` reason and report `stop_reason: "tool_deferred"`.
- Session registry records advertise a new peer feature set.
- The mascot's block-glyph art was redrawn in all four poses.
- Auto-mode outcome codes in the compact transcript are behind the `CLAUDE_CODE_AUTO_MODE_OUTCOME_CODES` environment variable; everything else here is unconditional.

**Evidence**

`CLAUDE_CODE_AUTO_MODE_OUTCOME_CODES`

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

## In Development

### Resuming auto-replies picks up comments left during an interrupt

Comments left while you interrupted Claude now get answered when auto-replies resume, with clearer status wording.

**What**

When automatic replies to comments resume, Claude Code now distinguishes how the previous session ended: after you pressed Ctrl+C or Stop, recent comments addressed to Claude are picked up on reconnect, while a killed or unwatched session leaves them as unanswered history. The resume and watch result text was rewritten to say which case applies, and no longer claims auto-replies are on before the status confirms it.

**Details**

- Requires the comment auto-reply opt-in, which is off by default.

**Evidence**

`pickUpRecentSummons`

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

### Visible notice when approaching the 5-hour usage limit

You could be told when you are near the 5-hour usage limit instead of Claude just wrapping up.

**What**

A user-facing message, "Approaching your 5-hour usage limit — Claude will wrap up the current step.", is shown before the existing wrap-up instruction is injected, so the behaviour is explained rather than silent. It is behind the `tengu_vellum_anchor` flag, which falls back to false, so it is off in this build unless remote config enables it.

**Evidence**

`Approaching your 5-hour usage limit — Claude will wrap up the current step.`

- Flag `tengu_vellum_anchor`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Usage & Limits
- Tier: Not switched on
- Useful: 3/5
- Signal: 3/5
- Present in the build but not switched on

### Goal check-ins repeat on an idle timer with widening gaps

Goals with waiting tasks re-check themselves when you are idle, with the gap doubling each time.

**What**

A goal with tasks that are waiting can schedule a check-in that fires when the session is idle, then re-arms itself with the delay doubled each time up to a ceiling. It re-arms after failures too, and a failed fire is reported as a `goal_checkin` fire failure.

**Details**

- The timer only exists when a check-in interval is configured; an interval of 0 turns it off entirely. Nothing in this build sets that value, so where it comes from is decided elsewhere.

**Evidence**

`goal_checkin`

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

### Artifact watches can answer comments posted just before the watch started

A comment sent moments before a watch starts could get a reply, but the flag is off.

**What**

When Claude starts watching an artifact for comments, comments addressed to it within a short recency window are no longer treated as already-seen history, so a summons sent moments before the watch existed gets a reply instead of being silently ignored. It is decided by `tengu_sorrel_trellis_lintel`, which falls back to off, so with no server value the old behaviour of baselining everything stands.

**Details**

- Only applies on an explicit publish-time arm of the watch, not on every start.
- Also forces a refresh of the fast-acknowledgement flag that would otherwise be skipped.
- The surrounding comment-autoreply feature is itself behind `tengu_sorrel_trellis` and `CLAUDE_CODE_ARTIFACT_COMMENTS_AUTOREACT`.
- Neither the gate nor this code path exists in 2.1.235.

**Evidence**

`tengu_sorrel_trellis_lintel`

- Flag `tengu_sorrel_trellis`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.236; this account: on, anonymous baseline: on, compiled default: on)
- Area: Artifacts
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Live-watch tokens can be renewed in place, once the server allows it

Live artifact watches could refresh their connection token in place, but the server never says they can.

**What**

A new call renews the token for an artifact's live update channel instead of tearing down and re-establishing the whole subscription. The client only takes that path when the subscribe response marks the subscription renewable, and nothing in the client sets that field, so the code ships inactive until the artifact service starts returning it.

**Details**

- Renewal posts to `/api/frame/watch-token/<slug>`; a failure is reported as a renewal miss and the old re-subscribe path is used.

**Evidence**

`watchTokenRenewEnabled`

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

### Live artifact watches can reuse a subscription instead of re-subscribing

Re-armed artifact watches could reuse an existing connection instead of starting over, but the flag is off.

**What**

When a live artifact watch is automatically re-armed, it can reuse a cached subscription token or renew it rather than starting a fresh subscription each time. This sits behind the gate `tengu_slate_lantern_ember`, which falls back to false in this build.

**Details**

- The cached token's lifetime is clamped between 30 and 60 minutes.
- Reconnect backoff switches to a spread window when this mode is active.
- Reuse and renewal are reported in the subscribe telemetry as `token_reused`, `lease_remaining_min` and `token_renewed`; failed renewals appear as `renew_miss_*`.

**Evidence**

`restored_consent_not_editor`

- Flag `tengu_slate_lantern_ember`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Artifacts
- Tier: Not switched on
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### Plugin cache writes can route through a new storage backend

Plugin cache file operations can go through a new storage layer, off unless a remote setting enables it.

**What**

Publishing, adopting, probing and clearing plugin cache folders can go through a storage interface instead of direct file calls, falling back to ordinary file operations on invalid-argument, not-a-directory and cross-device errors. It is off unless a remote setting turns it on, and the decision is fixed once per process.

**Details**

- Publishing is refused when the cache path is reached through a symlink.
- Stray orphan markers and set-aside replaced versions are cleaned up.
- Session transcript appends, saved tool results and stored shell output use the same path.

**Evidence**

`refused to publish a staged plugin through a symlinked cache folder (v5 backend)`

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

### Artifact comments can be read over the control plane before startup

Artifact comments could be read over the control plane before startup, but the flag falls back to off.

**What**

The artifact comment reader gains an option to try the control plane first and fall back to the normal token-based read, scoring the attempt either way. The flag involved, `tengu_onyx_sluice`, falls back to off in this build.

**Details**

- When the sandbox blocks outbound traffic to the content host, the read can now recover using an already-successful control-plane result instead of failing.

**Evidence**

`tengu_onyx_sluice`

- Flag `tengu_onyx_sluice`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.236; this account: on, anonymous baseline: on, compiled default: on)
- Area: Artifacts
- Tier: Not switched on
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### New flag tengu_slate_lantern_ember for lease-based artifact auto-replies

One new switch controls both early artifact comment reads and a lease-based auto-reply mode, off by default.

**What**

A new remote flag, falling back to off, is read in exactly two places: it turns on the early control-plane read for artifact auto-reply scans and confirmations, and it selects a lease-based mode for deciding when auto-replies are armed. With the fallback off, both keep current behaviour.

**Details**

- The similarly named, older `tengu_slate_lantern`, also off by default, still controls whether the live-channel token is minted at all.

**Evidence**

`tengu_slate_lantern_ember`

- Flag `tengu_slate_lantern_ember`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Flag `tengu_slate_lantern`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.236; this account: on, anonymous baseline: on, compiled default: on)
- Area: Artifacts
- Tier: Not switched on
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### Early artifact comment read is behind two flags, both off

The early artifact comment read needs two separate switches, both of which default off.

**What**

The pre-startup control-plane comment read has one caller, the artifact comment auto-reply scan, and it passes the option only when `tengu_slate_lantern_ember` is on. Even then the read itself needs `tengu_onyx_sluice`. Both fall back to off, so neither half runs unless remote config enables both.

**Details**

- The option defaults to false at the reader itself, and no other code in the build passes it.
- The attempt carries a callback so it is scored whether it succeeds or falls back to the normal read.
- The same change lets a session whose outbound network is blocked reuse an already-successful control-plane result rather than reporting a denial.

**Evidence**

`controlPlaneReadBeforeBoot`

- Flag `tengu_slate_lantern_ember`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Flag `tengu_onyx_sluice`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.236; this account: on, anonymous baseline: on, compiled default: on)
- Area: Artifacts
- Tier: Not switched on
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### An origin-normalising helper with nothing calling it

A strict URL-to-origin helper exists next to the claude.ai origin code, with nothing calling it.

**What**

A new function beside the claude.ai origin resolver turns a URL into a bare scheme-and-host origin. It upgrades `wss:` to `https:`, allows `ws:` and `http:` only for loopback hosts (localhost, 127.0.0.1, ::1 or any 127.x.x.x address), and returns nothing if the URL carries credentials, a path, a query or a fragment.

**Details**

- Its symbol appears only at its own definition in the bundle; nothing calls it.
- The neighbouring origin-override hook that the resolver consults has a body of a bare `return;`, so it always yields nothing and the resolver always falls back to the configured `CLAUDE_AI_ORIGIN`.
- Together these look like groundwork for a configurable or localhost artifact origin that nothing selects yet.

**Evidence**

`"wss:"`

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

### Artifact list rows now carry audience, pinned state and last-viewed time

Artifact list entries now carry sharing audience, pinned state, last-viewed time and an editable title.

**What**

Each row returned by the artifact gallery now includes who it is shared with, whether it is pinned, when you last viewed it, and an editable title, and the fetch result passes through the server's `starsEnabled` switch.

**Details**

- The tab cycler steps through all, mine, shared and pinned, adding the fourth entry only when the server enables pinning and otherwise mapping a pinned selection back to all.

**Evidence**

`return { err: null, frames: n, starsEnabled: r.data.starsEnabled === !0 };`

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

### One-shot idle subscriptions deliver exactly one notice, or an expiry

An idle subscription to another session delivers one notice or an expiry, and older peers are refused.

**What**

A subscription to another session's idle state results in a single `[Cross-session idle notice]` message, or a notice that the subscription expired. Peers running an older version are reported as unsupported and nothing is subscribed.

**Details**

- The local socket channel gained request and notice frame types, correlated back to the original message id.
- Self-targets and out-of-reach namespaces are vetted and refused.
- Analytics under `cross_session_notify_idle` cover own_inbox_unbound, unvettable_reply_target, self_target_frame, malformed_frame and malformed_notice.
- Session records advertise their supported peer features so version mismatches are detected before subscribing.
- The parameter is only present in the cross-session-messaging build of the tool.

**Evidence**

`notify_when_idle: that session runs a version without idle notices \u2014 nothing was subscribed.`

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

### Idle subscriptions cannot ride a structured message

Asking to be pinged when another session idles only works with plain text or no message.

**What**

An idle subscription can accompany a plain-text message or stand alone with an empty message, but not a structured one. Bridge targets, did-scheme targets and messaging yourself are each refused with their own message.

**Details**

- Outcomes are recorded under a new telemetry name.
- Unconditional inside the tool; which refusal you get depends on the target's address scheme.

**Evidence**

`notify_when_idle cannot ride a structured message \u2014 send plain text, or omit the message for a pure subscription`

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

### The compression decision is rebuilt from a one-liner into a full decision path

The gzip decision now records exactly why it skipped, though compression stays off without a server value.

**What**

In 2.1.235 this was a single expression checking one environment variable and one flag. It is now a function that returns a reason for every skip: `not_bun_runtime`, `below_min_size` for bodies under 4096 characters, `non_string_body`, and the transport reasons `unix_socket`, `proxy`, `mtls`, `custom_ca`. Both gates default to off, so absent a server value nothing is compressed.

**Details**

- `tengu_atomic_ocean` and `tengu_gzip_request_bodies` are OR'd; `CLAUDE_CODE_GZIP_REQUEST_BODIES` overrides either way, and forcing it on still respects the in-process kill switch.
- New this release: a 400, 403 or 415 rejection of a compressed body from an Anthropic response causes an uncompressed resend and turns compression off, optionally saved to the global config key `gzipRequestBodiesLatchedOff` with a timestamp and status, honoured for 7 days and then cleared with `tengu_gzip_request_body_latch_cleared`.
- Request telemetry now carries `requestBodyEncoding`, `gzipSkipReason` and `gzipFallbackStatus`.

**Evidence**

`not_bun_runtime`

- Flag `tengu_gzip_request_bodies`: Not enough to say (read for one account on one subscription tier against v2.1.236; this account: no value returned, anonymous baseline: no value returned, compiled default: not a boolean we can read)
- Area: Internals
- Tier: Not switched on
- Useful: 1/5
- Signal: 2/5
- Present in the build but not switched on

## Internal Changes

### Injected reminders can now be marked ephemeral

Injected reminders can be marked one-turn only, so hook denial notices no longer linger.

**What**

Reminder messages can be flagged so they do not persist in the transcript, and the notice shown when a hook denies a permission is now marked as belonging to the current turn only. This affects how long injected notices stick around, not their wording.

**Evidence**

`silent_turn_reminder`

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

### Remote settings caching watches a second file and skips redundant priming

Cached remote settings now come with a companion attestation file, and both must load cleanly.

**What**

The component that reads cached remote settings from disk now tracks two files rather than one, the cache itself and a companion file whose contents become an attestation string, reading the companion only after the cache and standing the whole view down if either read fails or is oversized.

**Details**

- Priming is now skipped when a cache was already loaded this session, joining the existing skip reasons of ineligibility and the `CLAUDE_CODE_REMOTE_SETTINGS_PATH` override.
- The prime log line now reports whether the companion attestation was present.
- Applies only on the newer storage path; no new flag. Visible only in debug logs.

**Evidence**

`helper consent sidecar`

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

### Agent, MCP and UI caches scoped per session

Agent, MCP and interface caches now die with the session instead of lingering in the process.

**What**

Agent definition loading, MCP connection, tool, resource and prompt caches, task-list watching, teammate mode snapshots, telemetry header caching, output path bindings, fleet roster and editor state, artifact stop latches and voice level smoothing all moved out of module globals. These caches are now discarded with their session rather than surviving for the life of the process.

**Evidence**

`agentDefinitions`

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

### Global config writes now pass the storage handle explicitly

Settings writes now name their storage, so config can follow whichever backend a session uses.

**What**

Many settings writes now name the storage they are writing to instead of assuming the local config file. This is what allows configuration to follow whichever storage backend a session uses.

**Details**

- Converted call sites include onboarding completion, install method, OAuth role storage, clearing the primary API key, iTerm2 and tmux setup flags, overage consent, the remote dialog seen flag, Apple Terminal setup, the first-token date, expanded todo display and the fullscreen upsell counter.
- Which storage the handle actually points at is decided by the `tengu_hover_rest` flag.

**Evidence**

`await sn((d) => ({ ...d, installMethod: "global" }), t)`

- Flag `tengu_hover_rest`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Config Storage
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### The command queue is now passed around instead of being a global

The pending command queue is per session now instead of shared across the process.

**What**

Prompt input, queue-edit hotkeys, background loops and agent spawning read the queue of pending commands from an object handed to them rather than from module-level variables, making it per session instead of process-wide. The queue also travels through tool context, and task notifications are enqueued through it.

**Evidence**

`t.messageQueue.enqueuePendingNotification({`

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

### Idle notices queue behind earlier peer messages

Idle notices from other sessions now wait their turn behind earlier peer messages.

**What**

Idle notifications used to take the fast out-of-band path used by all control messages between sessions; they now go through the ordered processing chain instead, so an idle notice cannot overtake peer messages queued before it.

**Details**

- Only the idle-notice action is excluded from the fast path; other control messages still use it.
- Reachable when cross-session peer messaging is in use.

**Evidence**

`(e.type === "control" && e.action !== "notify_when_idle")`

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

### Some hook events take a separate dispatch path

Six hook events now wait on a readiness step before running; the rest are unchanged.

**What**

Six hook events, PreToolUse, PermissionRequest, UserPromptSubmit, UserPromptExpansion, TaskCompleted and TeammateIdle, are handled by a new branch that waits on a readiness step before running hooks. Every other event returns early from that branch and goes through the previous path.

**Details**

- The split is unconditional; there is no flag selecting between the two paths.
- What the readiness step waits for is not determined by anything in this branch.

**Evidence**

`if (!DIw.has(e.hookInput.hook_event_name)) {`

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

### Comment monitors are part of session state everywhere it is built

Artifact comment monitors are now part of saved session state everywhere it is built.

**What**

Per-session monitors for comments on published Artifacts are now constructed and serialized alongside the existing worktree, isolation and file-history state, and a helper that produced a standalone "watching was stopped earlier in this session" note was removed, so that bookkeeping now comes from the shared state.

**Evidence**

`artifactCommentMonitors`

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

### Format checks for message ids and socket paths

Messages between Claude Code processes must now have well-formed ids and socket addresses.

**What**

New validation for messages passed between Claude Code processes: an id must be `cc-msg-` followed by 32 hexadecimal characters, and a socket path must start with a `uds:`, `bridge:` or `did:` scheme. A separate check for loopback addresses was added nearby.

**Evidence**

`^(?:uds|bridge|did):`

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

### Team inbox poller rewritten with an explicit session identity

Team inbox polling now picks a mailbox based on whether you are a teammate or plain session.

**What**

Inbox polling moved from a hook to a class the component starts and stops, with its collaborators injected. A new helper classifies the current process as in-process, a named teammate, or a plain session, and that identity picks which mailbox is polled.

**Details**

- Polling is now a self-rescheduling timer keyed by a generation counter, so results from a stale poll are discarded.
- Message routing rules are unchanged, including still dropping inbound permission-rule updates outright.

**Evidence**

`[InboxPoller] Dropping team_permission_update message: permission rules are never accepted from the inbox`

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

### Command queue proxy removed and remaining globals moved behind lookups

Resuming a session no longer installs a process-wide command queue; several globals are gone.

**What**

The inbound peer-hold state, the local socket messaging state, the GitHub auth logger, computer-use call state and the activity tracker all lost their process-global form; the tracker's shared instance field became a looked-up reference. The command queue proxy and its socket dispatcher were deleted and the queue is passed as a plain value, so resuming a session no longer installs a process-wide queue.

**Evidence**

`resetInstance`

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

### Local socket messaging checks which process it is talking to

Local socket messages now verify which process is on the other end.

**What**

The helpers that send messages and control commands over the local socket now carry an expected peer process id, verified against the connecting process along with a child-token check, and the inbox teardown and key-file state move behind an accessor. Failing to publish the inbox auth key still refuses to start an inbox.

**Evidence**

`[uds-messaging] Failed to publish the inbox auth key (refusing to run an inbox no peer can authenticate to): `

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

### Storage handle threaded through MCP and skills code paths

MCP and skills code now receives its storage explicitly instead of reading a process-wide location.

**What**

Functions that used to read configuration from a process-wide location now receive it as an argument, including MCP server connection and tool fetching, skills syncing and classification, grove config caching, subscription and auth lookups, and tips and nudge handling. Auth lookups are now cached under a single "oauth-tokens" key.

**Evidence**

`storageV5`

- Area: Config Storage
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Request telemetry records whether you are on the default model and effort

Each response now reports whether you were on the default model and effort, plus compression outcome.

**What**

Each assistant response now reports `is_default_model`, `default_model`, `is_default_effort` and `default_effort_level`, letting Anthropic separate traffic on the shipped defaults from explicitly chosen models. A helper resolves the model actually in use and compares it against the automatic model set and the configured default; when the comparison is ambiguous the fields are omitted rather than guessed. The same event also records `requestBodyCompression`, the gzip outcome for that request. No gate, and nothing visible in the app.

**Evidence**

`...(D !== void 0 && { is_default_model: D }),`, `defaultEffortLevel`, `default_effort_level`

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

### MCP code is loaded on demand and connections live in one registry

MCP code loads only when first needed, so startup is cheaper.

**What**

The MCP client, authentication, prompt-handling, task-watching, directory and error-classification code is no longer loaded when Claude Code starts. It is fetched through small accessors the first time an MCP path is used, which cuts startup cost. Behaviour is unchanged.

**Details**

- The cache of per-server connections moved from a memoised function's own cache onto a single shared registry used by both MCP implementations, so connection state now has one home.
- Tool and resource discovery calls take the storage handle as an explicit argument instead of reaching for it.

**Evidence**

`mcpIsListAuthErrorModule`, `mcpSdkErrorClassificationModule`

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

### Sandbox network prompts now de-duplicate by host

Two blocked connections to the same host now raise one sandbox prompt, not two.

**What**

Two connections to the same blocked host during the same ask now share one prompt instead of stacking two. The prompt itself looks the same as before.

**Details**

- The inline pending-request and session-allowed-host bookkeeping was replaced by a small class keyed on host.
- When an editor or bridge is attached the request is forwarded there, and its answer races the local dialog; the result is allow, persist to settings, persist a row, or cancelled.

**Evidence**

`sandbox_network_access`

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

### Batch tool calls survive a history replay intact

Batched tool calls now stay batched when a conversation is replayed.

**What**

The transcript now records the id and name of the original batch tool call whose contents were split into individual calls, so replaying a conversation reassembles the batch instead of sending the pieces separately. The field is hidden from display and never sent to the model.

**Evidence**

`batch_tool_uses`

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

### Startup records whether `ANTHROPIC_DEFAULT_MODEL` actually took effect

Startup now records whether your default-model environment variable actually won or was overridden.

**What**

Model resolution now reports whether the environment variable had an effect: inert when nothing resolves from it, outranked when an organisation default wins, or success otherwise.

**Evidence**

`outranked_by_org_default`

- Area: Elsewhere
- Names: `ANTHROPIC_DEFAULT_MODEL`
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### OpenTelemetry raw body setting re-reads when it changes

Changing the raw-API-body telemetry variable now takes effect instead of being frozen at startup.

**What**

The parsed value of `OTEL_LOG_RAW_API_BODIES` is cached per host and recomputed whenever the environment variable's text differs from what was cached, instead of being parsed on every use or frozen for the process lifetime.

**Evidence**

`t = process.env.OTEL_LOG_RAW_API_BODIES;`

- Area: Telemetry
- Names: `OTEL_LOG_RAW_API_BODIES`
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### REPL streaming state moved into a single object

Interactive session state was consolidated into one object; an out-of-order safety check was dropped.

**What**

About a dozen separate pieces of interactive-session state, including pending query parameters, streaming text and tool-use updates, and swarm timing and budget info, become fields on one object, with callbacks passed as named methods. No behaviour change, though an internal consistency check that used to throw on out-of-order sends is gone.

**Evidence**

`deferredSlashEchoUuid`

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

### Turn and streaming handling moved onto one controller

Cancel and interrupt, including from the remote bridge, now run through one streaming controller.

**What**

Five separate callbacks passed into the query component are replaced by a single streaming object with named methods, and cancel and interrupt now go through the same controller, including the remote bridge's interrupt path.

**Evidence**

`[bridge:repl] Remote interrupt → turn.cancel()`

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

### Cheaper storage listings and bounded settings watchers

Listing your sessions is cheaper now that per-key statistics are skipped.

**What**

Session enumeration now asks the storage layer to skip per-key statistics when listing sessions, making those listings cheaper.

**Details**

- Config backup listing returns stamp and size pairs and takes the backup kind as a parameter.
- Watchers on user settings now subscribe with an upper bound on observation lag.
- A scope-size check prefers stored byte counts over the generic size field.
- A v5 listing log renamed its truncation state from "first-cap" to "first-truncation".
- All of this sits on the v5 storage paths, which are themselves behind the pinned `tengu_hover_rest` flag.

**Evidence**

`skipKeyStats`

- Flag `tengu_hover_rest`: Off in both readings (read for one account on one subscription tier against v2.1.236; this account: off, anonymous baseline: off, compiled default: on)
- Area: Sessions
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### More singletons moved behind per-session lookups

More shared caches, including MCP connections and the git repo cache, became per-session.

**What**

The same conversion applied to another group of caches: the MCP server connection cache, the internal message bus, the git repository cache, the announcement store, the computer-use state, the plugin catalogue cache and the profiler. Two separate MCP caches collapsed into a single connections map.

**Evidence**

`[cu-esc] registered`

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

### Another batch of globals converted to per-session accessors

Another batch of shared caches, including rate-limit backoff and sandbox state, became per session.

**What**

MCP connection caches, agent background-signal resolvers, GitHub rate-limit backoff, the tool memory directory, the correspondent and rename registry, deduplication sets for telemetry, terminal setup state and sandbox and proxy caches all became containers created on demand per session.

**Evidence**

`return adw.of(Pr().host);`

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

### More config writes, logout and cache clearing take an explicit storage handle

More config writes, logout and cache clearing name their storage explicitly.

**What**

The default nudge, organisation default model tracking, daemon install prompt dismissal, GitHub repository path tracking, the feedback survey timestamp and Chrome onboarding all write through a named storage handle now. Logout takes credentials directly, session cache clearing and post-compaction cleanup take the handle, and transcript path validation only runs when the newer storage backend is active.

**Evidence**

`if (fr() && t !== void 0 && o === void 0)`

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

### Log paths are computed fresh on each call

Log paths are recomputed each time and the messages log folder is gone.

**What**

Every log path is now derived from one cache-directory helper that falls back to a safe working directory if the session's own directory cannot be read. The `messages` log directory was dropped, leaving base logs, errors and MCP logs.

**Evidence**

`baseLogs`

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

### A recorded stop is shown as a notice, not a warning

A second recorded stop state now shows as an informational notice rather than a warning.

**What**

The severity mapping that already downgraded one stop state to an informational notice now covers a second, so a recorded stop is presented as a notice rather than a warning.

**Evidence**

`recorded_stop`

- Area: Terminal UI
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Plugin cache version folders nest under marketplace and plugin

Cached plugin versions now nest under marketplace and plugin folders.

**What**

Cached plugin versions now live at `cache/<marketplace>/<plugin>/<version>` because the directory and the sanitized version segment are computed separately. The per-stage error reasons for the path containment check moved from a switch into a lookup table of the same strings.

**Evidence**

`plugin cache version parent not contained before relink`

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

### Version 2.1.236

The build version is now 2.1.236.

**What**

The embedded version, build time and commit stamp move to 2.1.236. The version string is inlined in roughly a hundred places, including telemetry, update checks and the user-agent header.

**Evidence**

`BUILD_TIME: "2026-08-19T16:25:31Z"`

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

### Fleet view and the prompt input rebuilt on explicit stores

The fleet screen and text input were rebuilt on state stores; nothing you see changed.

**What**

The fleet screen, the text input and the reply composer were rewritten from dozens of loose React refs onto store objects with named operations. Behaviour is broadly unchanged, and the visible copy such as the delete failure toast is the same.

**Details**

- Fleet operations are now named calls: `updateJobs`, `navigateTo`, `beginPromote`/`endPromote`, `beginNewSession`, `toggleCollapsed`, `openGroupEdit`, `startRename`, plus the reload, remote poll and archive guards.
- The editor store exposes `setQueryAndCursor`, `pastes`, `saveReplyDraft` and `registerLivePeekQuery`; the old prop-drilled refs are gone.
- REPL streaming state moved onto a single object with a `dispose()`.
- Escape handling picked up a debug-panel case.
- Two dialogs were dropped from the REPL's list: IDE onboarding and the auto-default nudge.

**Evidence**

`sweepReplyDrafts`, `TurnController: used before its screen bound a host`, `setQueryAndCursor`

- Area: Background Tasks
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Truncation telemetry label renamed

A truncation telemetry label was renamed, so old dashboards keyed on it stop matching.

**What**

The first capped result in a run is reported as `first-truncation` instead of `first-cap`. Anything keyed on the old label stops matching.

**Evidence**

`t.capped === 1 ? "first-truncation" : void 0`

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

### Queued commands passed explicitly instead of read from a global

Pending commands are handed to the input directly, and the status line reads queue depth properly.

**What**

The queue of commands waiting to run is now handed to the input handlers and prompt components directly rather than fetched from a global, and the task and status display reads its depth from a dedicated queue-length call. One fewer piece of ambient state.

**Evidence**

`getMainThreadQueueLength`

- Area: Session State
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Voice input rebuilt as a controller object

Voice input was rebuilt as a controller object with no behaviour change.

**What**

Voice recording (hold and tap modes, focus-mode auto-record, silence and maximum-duration timers, the streaming connection, the silent-drop replay path, and the circuit breaker that backs off after early failures) moved from a React hook full of refs into a class with its dependencies passed in. Same log lines, same telemetry, no behaviour change identified.

**Details**

- The circuit breaker's state was module-level globals shared process-wide; it is now held per instance.
- The hook remains as a thin wrapper that builds the controller and subscribes to its snapshot.

**Evidence**

`[voice] Re-arming focus recording after silence timeout`

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

### Query handling moved out of the REPL component

Query setup moved out of the main session view into an engine object.

**What**

Several hundred lines of query and tool-context setup in the REPL were replaced by a single binding step that hands the session, registries and callbacks to an engine object, and the fleet view's state became a store with methods. No behaviour change was identified, but almost every fleet and REPL key binding now goes through the store.

**Details**

- Fleet state is read through a snapshot getter and changed through named calls such as `setGroupPickIdx`, `abandonRespawnAttempt`, `nextRespawnAttempt` and `closeResumePicker`.

**Evidence**

`abandonRespawnAttempt`

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

### Fleet view focus and ordering moved into a store

Fleet view focus, pinning and reordering now go through one store.

**What**

Focus, pinning, reordering and reload in the fleet view now go through store methods instead of ad-hoc references, and a debug path for re-pinning during polling was dropped. Reporting such as the group-delete failure reason is unchanged.

**Evidence**

`"fleet_view_group_delete"`

- Area: Background Tasks
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### Fleet view state rebuilt around a job store

Fleet view job statuses and tabs moved into a store; your messages are unchanged.

**What**

Six ad-hoc state slots in the fleet view, including per-job statuses, PR statuses and the active tab, are replaced by a store with roster, selection, attach and editor slots updated through methods. Messages you see, such as the rename failure, are unchanged.

**Evidence**

`Couldn't rename — that session isn't responding`

- Area: Background Tasks
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### REPL and fleet view read from a shared snapshot instead of scattered state

Streaming state and the fleet view now read one shared snapshot instead of scattered props.

**What**

Streaming state, including the user's input while a turn is processing, the streaming text peek, the interruptible-tool flag, a pending preserved insert and the API metrics reset, moves onto one stream object. The message queue is now passed explicitly to spawn and notification paths rather than reached through a module-level function, and the fleet view drops eight initial-state props in favour of snapshot reads for focused index, rename state, follow origin and mode. No behaviour change is claimed, though most REPL callback signatures changed.

**Evidence**

`hasInterruptibleToolInProgress`

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

## Removed

### Managed-settings review prompt dropped from the main UI

The organisation settings security prompt no longer appears from the main screen; a reworked consent flow handles it.

**What**

The branch that drew the organisation settings security prompt, with accept and reject resolving to approved or rejected, was removed from the main render path. That review is now handled by the reworked consent store.

**Evidence**

`managed-settings-security`

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

### Sandbox-host and managed-settings prompts removed from the waiting-state machine

The status line and spinner no longer track sandbox-host or managed-settings prompts, leaving only worker sandbox and elicitation.

**What**

The status line and spinner no longer recognise the sandbox host prompt or the managed settings security prompt, and the hook that registered the settings prompt handler was deleted. Only the worker sandbox prompt and the elicitation prompt remain.

**Evidence**

`if (e.workerSandboxPrompt) return "sandbox request";`

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