# Claude Code v2.1.229

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

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

Thirty-nine entries are present but not switched on. A large `/design` skill drafts multi-artboard canvases published as an editable Artifact, but it needs the `tengu_ethereal_nova` gate, which falls back to false; a slides artifact template with a thumbnail rail, comment threads and a Present mode is registered as a command whose enable check returns false outright. A built-in `/commit` command and a new `pr` skill that opens pull requests through the gh CLI both wait on the `tengu_cinder_relay` gate, which the build compiles as false and the server decides. Rate-limit handling can queue an automatic continue at reset and nudge Claude to wrap up when five-hour usage runs low, both behind `tengu_vellum_anchor`, also off. A `ReadNotifications` tool and the queued notifications that feed it need `CLAUDE_CODE_REMOTE` set and a non-interactive session, and `CLAUDE_CODE_ARTIFACT_VIEWER_BASE_URL` is recognised but read by a resolver with an empty body.

Ninety-four entries are usable now. `CLAUDE_CODE_TURN_UPDATES` turns on a narration style where Claude says what it is about to do, posts progress and writes a standalone final message. `/teleport` gained "Continue this session in the cloud", which requires Remote Control, a claude.ai login and a clean git tree with nothing unpushed. Plugins can now be installed from a `command` source, a shell command that prints a plugin directory, with a `timeout` capped at 600 seconds and a `mode` of copy or link; administrators can block it with the managed setting `disableCommandPluginSources`, and a repository can set `disableAllHooks: true`. The `[1m]` long-context variants are no longer hidden from subscription accounts pointed at a custom `ANTHROPIC_BASE_URL`, and self-hosted runners on Windows now demand `--base-dir` instead of falling back to a POSIX path.

MCP sign-in now redirects to `http://127.0.0.1` rather than `localhost`, fixing machines where localhost resolves to an unlistened IPv6 address, and a stale saved OAuth client is repaired in place instead of forcing a fresh registration. A failed sandbox initialization clears its state rather than caching the rejected promise, so a later attempt can succeed. Messages injected into a session programmatically are no longer counted as verified human turns, and tool inputs are filtered to a fixed set of fields before being recorded in history. The bundled `code-walkthrough` and `pr-explainer` skills are gone, and `PowerShell(git checkout -b *)` is no longer pre-allowed.

The billing attribution header builder now takes an options argument, and one first-party, non-socket path passes `ignoreEnvOptOut` so the header is sent even when `CLAUDE_CODE_ATTRIBUTION_HEADER` opts out. The auto-mode classifier, the two-stage classifier and the critique query do the same; the block carries version, entrypoint and workload metadata rather than conversation content. Plugins installed from a command re-resolve in the background once per session, subject to non-essential network traffic being allowed and a remote flag that defaults on.

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

### "Turn updates" narration style for Claude's replies

Claude can narrate what it's about to do and post progress updates; set CLAUDE_CODE_TURN_UPDATES.

**What**

Claude can be told to say what it is about to do before starting, post brief progress updates while working, and write a final message that stands alone. Set `CLAUDE_CODE_TURN_UPDATES` to turn it on or off yourself; otherwise it is decided by a per-model capability check, then by a capability map the server sends with your account data. It replaces the usual communication guidance in the system prompt rather than adding to it.

**Details**

- Resolution order: the environment variable wins if set (it accepts true/false/unset), then the model capability check, then the server-supplied map.
- The env var is new in this build.
- When the value comes from the server-supplied map, a `tengu_model_capability_from_client_data` telemetry event is emitted.

**Usage**

`CLAUDE_CODE_TURN_UPDATES=1 claude` **Evidence** `Before you start, say in a line what you're about to do; brief updates while you work help the user follow along. Your final message should stand on its own for a reader catching up fresh.`

- Area: Output Style
- Names: `CLAUDE_CODE_TURN_UPDATES`
- Tier: Use it now
- Useful: 5/5
- Signal: 5/5

### A slides artifact template exists but its command is switched off

A presentation-deck artifact with slide editing, comments and Present mode is built but unreachable.

**What**

A third artifact family joins doc and sheet: a presentation deck, with a full skill body and a published-deck template offering a thumbnail rail, in-place slide editing, comment threads kept in a hidden storage block, and a Present mode. It is registered as a user command described as "Publish a presentation deck Artifact", but the registration's isEnabled function returns false in this build, so the command cannot be reached.

**Details**

- Present mode: arrow keys step, N toggles speaker notes, B blanks the screen, T runs a timer, digits jump to a slide, Esc exits.
- Nothing in the build makes isEnabled return anything other than false.

**Evidence**

`Publish a presentation deck Artifact`

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

### New /design skill drafts multi-artboard canvases as an Artifact, gated off

A /design skill drafting multi-artboard canvases as editable Artifacts is bundled but switched off.

**What**

A large new bundled skill drafts designs as `.dc.html` artboards laid out on a single pan-and-zoom canvas and publishes them as an Artifact, with an editor built into the published page. Saving republishes for everyone only where the artifact grants the self-write capability; otherwise the page is view plus PNG and PDF export. It registers a `design` slash command. Off in this build: it needs `tengu_ethereal_nova`, which falls back to false, and an Artifact tool that advertises capabilities.

**Details**

- ships as a skill document plus a canvas seeding script and a precompiled editor page payload

**Evidence**

`Draft a design on a canvas Artifact — editable where saving is enabled (Claude Design preview)`

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

### Remote sessions can receive queued notifications and read them with a new tool

Remote sessions can be handed webhook, schedule and cross-session events mid-turn and read them with a new tool.

**What**

Sessions running in remote mode can now be handed out-of-band events while a turn is in flight (GitHub webhook activity, a scheduled trigger firing, a message from another Claude session). They are buffered and Claude is nudged to call a new tool, ReadNotifications, until it reports 0 remaining. This needs remote mode, set through `CLAUDE_CODE_REMOTE` plus an internal remote check; payloads arriving otherwise are dropped.

**Details**

- Drained notification bodies are wrapped in an explicit warning that they are relayed external content and may imitate the surrounding delimiters.
- Oversized content is refused without acknowledgement, and the buffer has a fixed size cap.
- Telemetry lands under `ccr_queued_notifications` with reasons `malformed_payload`, `not_remote`, `content_too_large`, `buffer_full` and `nudge_ignored`.

**Evidence**

`queued_notification outside remote mode`

- Area: Remote Control
- Names: `CLAUDE_CODE_REMOTE`
- Tier: Not switched on
- Useful: 3/5
- Signal: 5/5
- Present in the build but not switched on

### /teleport can send the current session to the cloud

/teleport can hand your current session to the cloud if your git tree is clean and pushed.

**What**

The teleport menu gained "Continue this session in the cloud", which hands the current session to a cloud session and then either reconnects Remote Control to it or disconnects. It refuses unless Remote Control is active, you are logged in to claude.ai, and you are in a git repo with a clean tree and nothing unpushed. Available where /teleport already is, which needs the `allow_remote_sessions` remote setting.

**Details**

- calls a new `move-to-cloud` endpoint on the session after the precondition checks pass
- the command description changed to cover both directions of travel
- emits `tengu_teleport_to_cloud` and `teleport_to_cloud` telemetry

**Evidence**

`Send this session to the cloud, or resume one from claude.ai`

- Area: Remote Control
- Names: `/teleport`, `allow_remote_sessions`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### ReadNotifications tool, reachable only in remote non-interactive runs

A new tool lets Claude pull queued notifications, but only in remote non-interactive runs.

**What**

A new read-only tool called ReadNotifications lets the model pull notifications the server has queued for a session (GitHub activity, scheduled triggers and messages from other agents), oldest first, marking them delivered and reporting how many are left. It is only offered when the `CLAUDE_CODE_REMOTE` environment variable is set and the session is non-interactive, so it does not appear in an ordinary terminal session.

**Details**

- Each notification carries an id, an origin, a queue timestamp and its content, plus a count of those still waiting.
- Subagents are refused: "ReadNotifications can only be called from the main conversation — subagents must not drain the session notification buffer."
- This replaces injecting webhook and trigger activity into the conversation as if you had typed it.

**Evidence**

`ReadNotifications can only be called from the main conversation — subagents must not drain the session notification buffer.`, `Read the notifications queued for this session`

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

### Windows now has its own gate for Harbor cross-session messaging

Setting CLAUDE_CODE_HARBOR_KITE forces cross-session messaging on, and Windows can now be allowed through.

**What**

The check for Harbor cross-session messaging was rewritten. Setting `CLAUDE_CODE_HARBOR_KITE` now forces it on regardless of platform. Windows, which previously always returned false, is allowed through when the separate `tengu_harbor_kite_win` gate is on. The general gate is consulted last. Both gates fall back to off in this build, so unless the environment variable is set, whether it runs is decided by remote config.

**Evidence**

`tengu_harbor_kite_win`

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

### Artifact watches can wake on comments as well as republishes

Artifact watches can now wake you on comments addressed to Claude, not just republishes.

**What**

Subscribing to an artifact now returns which events it covers, and the status line says whether you will be woken on republishes only or on republishes plus comments addressed to Claude.

**Details**

- A path that arms these wake subscriptions automatically is present but sits behind a build-time constant that is `false` in this build, so it never runs.

**Evidence**

`watching for republishes and to-Claude comments`

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

### A built-in /commit command, off unless a server gate turns it on

A built-in commit command with a strict git workflow exists but is off unless a server enables it.

**What**

A command named "commit" is registered that gathers git status, the diff against HEAD, the branch and the last 10 commits, then follows a fixed workflow: never amend after a failed pre-commit hook, never use `--no-verify`, prefer naming files over `git add -A`, and never push unless asked. Its tool access is limited to git add, status and commit. It appears only when the `tengu_cinder_relay` gate resolves true; the value compiled into the build is false, so whether it shows up is decided by server config.

**Details**

- It is separate from the existing commit-push-PR flow.

**Evidence**

`Create a git commit. Use whenever you are about to create a commit`, `tengu_cinder_relay`

- Area: Slash Commands
- Names: `/commit`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### New pr skill opens pull requests, replacing pr-explainer

A pr command that opens GitHub pull requests is bundled but gated off; pr-explainer is gone.

**What**

A new `pr` command gathers git context and opens a GitHub pull request through a fixed gh CLI workflow, with a fixed allowed-tools list covering `gh pr create --title * --body *` and `git push -u origin *` and a heredoc body format. Off in this build: it needs `tengu_cinder_relay`, which falls back to false, behind the commit-skill rollout gate. The old `pr-explainer` command that generated a shareable PR walkthrough artifact is gone, and PR review skills no longer point at it.

**Evidence**

`Create a GitHub pull request. Use whenever you are about to open a PR`

- Area: Slash Commands
- Names: `/pr`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Nudge to wrap up when your 5-hour usage is nearly spent

Claude could be told to wrap up and list remaining work when your usage is nearly spent.

**What**

When five-hour usage crosses a threshold, and you are not using or allowed extra overage, Claude Code arms a one-shot hint that tells the model to finish the current step, list at most 3 bullets of the most impactful remaining work, and not start subagents or long-running work. Off in this build: `tengu_vellum_anchor` falls back to false. Reports as `usage_limit_near_wrapup`.

**Details**

- fires at most once per session, injected as a meta message rather than shown as UI text

**Evidence**

`[Usage limit approaching. Checkpoint now: finish the current step, then list up to 3 short bullets of the most impactful remaining work. Don't start subagents or long-running work.]`

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

### Rate-limit banner can queue an automatic continue at reset

Hitting a five-hour limit could queue your turn to resume automatically at reset, currently off.

**What**

When you hit a five-hour limit and the reset time is known, Claude Code can queue your turn to resume after the reset and show a hint line ending in " to continue after reset", or prefill `continue` into the prompt. Choosing "adjust" or "upgrade" from the rate-limit or spend-limit menu cancels the queued continue; cancelling or choosing "wait" prefills it instead. The whole path is behind the `tengu_vellum_anchor` flag, which falls back to off, so nothing in this build turns it on locally and whether it appears is decided by server config.

**Details**

- Events: `tengu_rl_checkpoint_copy_shown` when the hint is shown, `tengu_rl_checkpoint_auto_continue_queued` when the continue is armed, and a third when the upsell path cancels it
- Hint text is assembled from a key chord plus the fixed trailing phrase

**Evidence**

`tengu_rl_checkpoint_auto_continue_queued`, `Press `

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

### Wrap-up hint when you are close to the usage limit

Past 95% of five-hour usage Claude could be told to checkpoint and list remaining work, off here.

**What**

When five-hour usage crosses 95% and the account is not in overage, Claude gets a message telling it to checkpoint: finish the current step, list up to three bullets of the most impactful remaining work, and start no subagents or long-running work. A `usage_limit_near_wrapup` event is recorded. The whole path is behind the `tengu_vellum_anchor` gate, which falls back to off, so with no remote config value nothing is injected.

**Details**

- The exact injected text is "[Usage limit approaching. Checkpoint now: finish the current step, then list up to 3 short bullets of the most impactful remaining work. Don't start subagents or long-running work.]".
- None of this machinery existed in 2.1.228.

**Evidence**

`tengu_vellum_anchor`

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

### Attribution header can override the environment opt-out

One first-party request path sends the attribution header even if you opted out via env var.

**What**

The builder for the attribution header now takes an options argument, and one specific first-party, non-unix-socket path passes `ignoreEnvOptOut`, which makes the header be sent even when `CLAUDE_CODE_ATTRIBUTION_HEADER` is set to opt out.

**Evidence**

`ignoreEnvOptOut`

- Area: Telemetry
- Names: `CLAUDE_CODE_ATTRIBUTION_HEADER`
- Tier: Under the hood
- Useful: 3/5
- Signal: 4/5

### Auto-mode side queries always send the billing attribution header

Auto-mode's side queries send billing attribution metadata even if you set the opt-out variable.

**What**

The auto-mode classifier, the two-stage classifier and the critique query now force the billing attribution system block to be sent even when the opt-out environment variable is set. It carries version, entrypoint and workload metadata, not conversation content, and is only sent on the first-party API and never over a unix socket.

**Evidence**

`forceAttributionHeader`

- Area: Telemetry
- Names: `CLAUDE_CODE_ATTRIBUTION_HEADER`
- Tier: Under the hood
- Useful: 3/5
- Signal: 4/5

### /prototype now picks a fidelity and can build on an app you already have

/prototype picks a fidelity itself, can build on your existing app, and hands off a scoped brief.

**What**

The prototype skill names three fidelities, sketch, clickable and wired, defaults to clickable, and states its pick instead of asking you. If you explicitly ask, it can prototype a feature on top of an existing app, either as a screenshot overlay or a shell built from your repo source. A new hand-off step converts the finished prototype into an approved brief split into must-have, nice-to-have and cut.

**Evidence**

`Every prototype is built at one of three fidelities, and naming it sets`

- Area: Skills
- Names: `/prototype`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### 1M-context models no longer suppressed on custom endpoints

Subscription users on a custom base URL now see the 1M-context model variants again.

**What**

The `[1m]` long-context model variants were hidden from subscription accounts whose credit state disallowed extra usage. That credit check now applies only when you are talking to Anthropic's own endpoints or a local socket, so subscription users pointed at a custom `ANTHROPIC_BASE_URL` see the 1M variants offered. Setting `CLAUDE_CODE_DISABLE_1M_CONTEXT` still hides them everywhere.

**Details**

- The first-party test is satisfied by `ANTHROPIC_UNIX_SOCKET` being set, or by the base URL being an Anthropic one.
- On first-party endpoints behaviour is unchanged: cached extra-usage state still decides.

**Evidence**

`CLAUDE_CODE_DISABLE_1M_CONTEXT`

- Area: Models
- Names: `ANTHROPIC_BASE_URL`, `CLAUDE_CODE_DISABLE_1M_CONTEXT`, `[1m]`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### Directory sync can be turned off with an environment variable

You can turn off background directory syncing by setting CLAUDE_CODE_DISABLE_DIR_SYNC.

**What**

The background worker that syncs directory contents now starts only when a decision helper allows it, and `CLAUDE_CODE_DISABLE_DIR_SYNC` feeds that decision alongside the entrypoint being used. When it is skipped for that reason, a telemetry line records it.

**Evidence**

`dir_sync_worker_disabled`

- Area: File Sync
- Names: `CLAUDE_CODE_DISABLE_DIR_SYNC`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### Suggested permission rules for commit and PR commands narrowed

Approving commit and PR permissions now allows only specific flags, not any git or gh arguments.

**What**

The built-in allow-rule suggestions are now argument-specific: `git commit -m *`, `gh pr create --title * --body *` and `gh pr edit --title * --body *`. Approving one no longer blanket-allows arbitrary commit or pull-request flags.

**Evidence**

`gh pr create --title * --body *`

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

### OpenTelemetry can be sent through Anthropic's first-party gateway

Telemetry can route through Anthropic's own gateway, but only under a narrow set of conditions.

**What**

Telemetry export now decides per signal (metrics, logs, traces) whether to send OTLP data to a gateway endpoint derived from `ANTHROPIC_BASE_URL`, authenticated with a `Bearer` header from `ANTHROPIC_AUTH_TOKEN`. It only does this when `CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST` is set, the auth mode is first-party/gateway, the token is scoped to the gateway audience, you have set no OTLP headers of your own, and your OTLP endpoint already matches the derived URL. Any other combination sends nothing.

**Details**

- The check runs separately for each signal, so metrics can qualify while traces do not.
- User-set `OTEL_EXPORTER_OTLP_*` headers disqualify the signal outright, so your own collector configuration is never overridden.
- Falls back to returning "do not send" rather than erroring.

**Evidence**

`claude-gateway`

- Area: Telemetry
- Names: `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST`
- Tier: Use it now
- Useful: 2/5
- Signal: 4/5

### Artifact comment auto-replies report why they did not reply

Automatic replies to artifact comments now record why they skipped a thread.

**What**

The pipeline that reacts to comments on published artifacts now records a status for each comment thread rather than just succeeding or vanishing: declined with a reason such as blocked, auto_reply_off, plan_mode, hourly_cap, loop_guard or denied_by_rule, or failed with compose_failed or post_failed. Threads seen for the first time can be deferred instead of dropped. Nothing in the build resolves whether the surrounding auto-reply feature is on for a given account.

**Evidence**

`"deferred_first_sight"`

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

### Artifact database gains a private per-viewer area

Artifact databases can give each viewer a private area, documented only when an internal flag is on.

**What**

Artifact database guidance documents an exception to shared rows: each viewer's subtree under the `data/users/` prefix is private to that viewer, and the literal path segment `me` stands for the current viewer's id, the same one the page gets from `claude.user.id()`. It requires the published version to declare the `user` capability alongside `db`. The paragraph is only included in the tool description when its internal flag is set.

**Evidence**

`data/users/me`

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

### Artifact publish can demand a session context, switched off here

Publishing an artifact could carry session context so a later session resumes it, but it's disabled.

**What**

Publishing an artifact gained an optional `session_context` parameter, an error when it is missing on a first publish, and capture of the prompt that triggered the publish (truncated with a marker) plus the model into the published metadata. It all sits behind a latch that is initialized to false in this build, so the parameter is never offered and the requirement never fires.

**Details**

- Error case is `session_context_required` on the first publish only
- Latch has no code path in this build that sets it true

**Evidence**

`session_context_required`

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

### Artifact watches can wake on comments, and say why they did not

Watching an artifact could wake on new comments, not just republishes, but it's server-gated off.

**What**

In remote sessions, watching a published artifact can now register a wake that fires on new comments as well as republishes, and the tool text was rewritten to match. The whole durable wake path is behind a remote-config flag whose built-in fallback is false, so it is off unless the server turns it on, and it also requires the remote-session mode.

**Details**

- Watch results can carry a skip reason, shown as "durable wake subscriptions are not enabled for this session" or "no wake-webhook minter is available (the session's own MCP mount was not found)".
- The gate is `tengu_scalable_quiche`, alongside `CLAUDE_CODE_REMOTE`.

**Evidence**

`This remote session's durable wake rail was skipped as well`

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

### Custom artifact viewer host, not yet resolvable

An env var for a custom artifact viewer host is recognised but currently ignored.

**What**

`CLAUDE_CODE_ARTIFACT_VIEWER_BASE_URL` is a new recognised environment variable, and artifact URL parsing and building now ask a resolver for the viewer host instead of assuming claude.ai or the staging host. In this build that resolver has an empty body and returns nothing, so the variable is never read and URLs still resolve to the built-in hosts.

**Evidence**

`CLAUDE_CODE_ARTIFACT_VIEWER_BASE_URL`

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

### PR writing guidance and PR templates are present but switched off

Plain-language PR writing rules and repo PR template fetching exist but reach no prompt.

**What**

Helpers were added that would inject plain-language PR writing rules ("Short beats complete"), fetch the repo's PR template out of git, and warn that the template is untrusted content. All of it sits behind a helper that unconditionally returns false in this build, so none of it reaches any prompt.

**Details**

- The template fetch uses `git show HEAD:<template>` rather than reading the working tree.
- A `tengu_pr_writing_guidance_rendered` event is wired up for when the guidance renders.

**Evidence**

`tengu_pr_writing_guidance_rendered`

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

### Publish tool has a session-context field that cannot be reached

The publish tool has a field for saving session context so a later session resumes, but it's unreachable.

**What**

The artifact publish tool can offer a `session_context` field holding context saved with the published version so a later session can pick up where you left off. The condition that adds it reads an internal flag that starts false and is never set anywhere in this build, so the field is never offered.

**Evidence**

`Required the first time an artifact is published`

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

### Queued notifications for remote sessions

Remote sessions accept and validate a new queued notification message instead of ignoring it.

**What**

Remote sessions can now receive a `queued_notification` message, which previously fell through as an unknown message type and was logged and ignored. Payloads are validated and size-capped, and an oversized one is refused without acknowledgement. The feature is only advertised when the `CLAUDE_CODE_REMOTE` environment variable is set and an SDK transport is active.

**Details**

- App state gained a buffer holding pending notifications, the IDs of ones already drained, and a nudge slot.
- A registry of queued notifications is passed through to tool execution.
- Telemetry is recorded under `ccr_queued_notifications`.
- Outside remote mode the message is rejected with a logged reason.

**Evidence**

`[queued-notifications] queued_notification outside remote mode`, `Ignoring unknown message type`

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

### `queued_notification` added to the stdout message protocol

Claude Code can now buffer notifications and redeliver undrained ones when you resume a session.

**What**

The message types Claude Code emits on stdout gained a `queued_notification` entry carrying an id, origin, content, a priority of "now", "next" or "later", and a queue timestamp. It is the wire format behind the ReadNotifications tool: the CLI buffers the event, nudges the model to drain it, and only acknowledges it once drained, so an undrained event is redelivered when the session resumes.

**Evidence**

`The CLI buffers it, nudges the model to call the ReadNotifications tool, and acks the event processed only when drained — an undrained event redelivers on resume.`

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

### Artifact comments can be anchored to a file

Artifact comments now record the file and content hash they refer to, and when that anchor drifts.

**What**

Comments on an artifact can now carry the file they refer to and a hash of that file's contents, alongside the existing path and quoted span, so a comment stays tied to what it was written about. The schema also records when the anchor has degraded.

**Evidence**

`anchor_file_sha: Rt.anchorFileSha`

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

### Session, host and storage handles passed in explicitly

A broad refactor passes session, project root and storage explicitly, preparing for several sessions in one process.

**What**

A large refactor across config writing, hooks, the status line, file suggestions, notifications and MCP elicitation: each now receives the session, its project root and cwd, or a storage handle as an argument instead of reading ambient globals, and the remaining shared state sits behind accessor objects. Groundwork for running more than one session correctly in a process.

**Details**

- Covers the skills registry, teleport info, cached CLAUDE.md, sticky betas and cron tasks.
- A root session now carries a host object built by a factory.

**Evidence**

`backgroundHousekeeping`

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

### The published code change event no longer assumes a pull request

The event for work sent out for review no longer assumes GitHub pull requests.

**What**

The SDK event that fires when work from a session goes out for review is now described in provider-neutral terms, with the provider field documented as carrying other values for other review systems and the URL as the published change rather than a PR link.

**Evidence**

`other values for other review systems in internal builds`

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

### Artifact links can resolve against a configured host

Artifact links can point at a custom host you configure instead of the default origin.

**What**

Building and parsing artifact URLs now checks a configured origin override first and falls back to `CLAUDE_AI_ORIGIN`, so artifact links point at a custom host when one is set.

**Details**

- Applies to both URL construction and pulling the artifact slug back out of a URL

**Evidence**

`/code/artifact/`

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

### Marketplaces can ship a plugin built by running a local command

Marketplace plugins can be built by a local command, with a timeout and optional link mode.

**What**

A plugin marketplace entry can now use `source: "command"`, a shell command that prints the absolute path of a plugin directory on stdout and exits 0. Claude Code copies that directory into its plugin cache, or links it in place with `mode: "link"`. The command has a `timeout` in seconds, default 60 and capped at 600. Administrators can block it entirely with the managed settings `disableCommandPluginSources` or `allowManagedHooksOnly`.

**Details**

- The command must print exactly one line, the absolute path of the plugin directory, and exit 0.
- The command string is validated as printable ASCII before it runs.
- `mode: "link"` is refused on Windows; the copy behaviour remains available there.
- Every install and update through this source goes through the confirmation prompt described below.

**Evidence**

`Shell command that prints the absolute path of the plugin directory on stdout (exactly one line) and exits 0.`

- Area: Plugins
- Names: `timeout`, `disableCommandPluginSources`, `allowManagedHooksOnly`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Plugins can be installed from a command that prints a directory

Marketplaces can install a plugin by running a command that prints a directory path, with your consent.

**What**

Plugin sources now include `command`: a shell command that prints the absolute path of a plugin directory. It is re-resolved on install, on update and once per session in the background, takes an optional `timeout`, and a `mode` of `copy` or `link` decides whether the directory is copied into the plugin cache or used in place. Installing one asks for consent first.

**Details**

- The command string must be printable ASCII and short enough for the consent prompt to display in full, so it can be read before you approve it.
- Declining the consent prompt is recorded as `command_source_declined`.
- `link` mode serves the plugin from the producer's directory rather than a cache copy.

**Evidence**

`command must be printable ASCII (letters, digits, punctuation, single spaces) with no runs of 4 or more spaces`

- Area: Plugins
- Names: `command`, `timeout`, `mode`, `link`, `copy`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Plugins can be installed from a command that produces the plugin directory

Marketplaces can declare command plugin sources, and admins can block them with one setting.

**What**

A marketplace can now declare a `command` plugin source, where Claude Code runs that command on your machine to produce the plugin directory. Installs and updates prompt before running the command. A new managed setting `disableCommandPluginSources` turns it off; when it is unset, the existing `allowManagedHooksOnly` managed setting decides, and an invalid value for either key is treated as disabled.

**Details**

- `command` is now listed alongside github, git-subdir, npm, url and archive in marketplace validation messages.
- Each installation records the command that produced it, the directory it produced, and the directories previous runs produced, so the sandbox keeps refusing writes to superseded producer directories.
- Both settings keys are read from managed settings only, so a user or project settings file cannot re-enable this.

**Evidence**

`disableCommandPluginSources`

- Area: Plugins
- Names: `disableCommandPluginSources`, `allowManagedHooksOnly`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Repositories can declare that all hooks are off

A repo settings file can set disableAllHooks to turn every hook off.

**What**

A settings file inside the repository root that sets `disableAllHooks: true` is now recorded during settings traversal, and the self-hosted runner path honors it.

**Evidence**

`repoDisablesAllHooks`

- Area: Hooks
- Names: `disableAllHooks`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Self-hosted runner requires `--base-dir` on Windows

Self-hosted runners on Windows now require you to set a base directory instead of failing oddly.

**What**

Starting a self-hosted runner on Windows without a base directory now fails with an explanatory error instead of silently falling back to a POSIX container path that does not exist there. Set `--base-dir` or the `SELF_HOSTED_RUNNER_BASE_DIR` environment variable to the directory repositories should be checked out under. The runner also now passes `CLAUDE_RUNNER_CLIENT_PLATFORM` into spawned runner environments, parsed as `client_platform`.

**Details**

- Passing `--base-dir` records where the value came from.

**Usage**

`claude ... --base-dir C:\runner\repos` **Evidence** `--base-dir (or SELF_HOSTED_RUNNER_BASE_DIR) is required on Windows: the built-in default is a POSIX container path that does not apply there. Pass the directory that repositories should be checked out under.`

- Area: Self-Hosted Runner
- Names: `--base-dir`, `SELF_HOSTED_RUNNER_BASE_DIR`, `CLAUDE_RUNNER_CLIENT_PLATFORM`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Sibling workflow agents stagger their first request to warm the cache

Parallel workflow agents stagger their first call to share cache; tune with an env var.

**What**

When several workflow agents start with an identical prompt prefix, the followers now wait briefly for the leader's first response so their request hits the prompt cache instead of paying full price for the same prefix. The wait is capped by `CLAUDE_CODE_WORKFLOW_PREFIX_STAGGER_MS`, which has a built-in default when unset and is forced to 0 when `DISABLE_PROMPT_CACHING` is set.

**Details**

- Agents sharing a prefix register in a warm-up map; the first one through is the leader
- Followers wait up to the cap, then proceed regardless
- With prompt caching disabled there is no wait at all

**Evidence**

`CLAUDE_CODE_WORKFLOW_PREFIX_STAGGER_MS`

- Area: Workflows
- Names: `CLAUDE_CODE_WORKFLOW_PREFIX_STAGGER_MS`, `DISABLE_PROMPT_CACHING`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### `claude plugin install` and `update` gained `-y`

Plugin install and update take -y to skip the command confirmation, but not inside a session.

**What**

Both subcommands accept `-y`/`--yes`, which accepts a plugin whose install method is a command declared by its marketplace without showing the interactive confirmation. It is required when stdin or stdout is not a terminal, and it is refused with an explicit message if you run it from inside a Claude Code session.

**Usage**

`claude plugin install my-plugin@my-marketplace -y` **Evidence** `For a plugin installed by running a marketplace-declared command: accept the displayed command without the confirmation prompt (required when stdin or stdout is not a TTY)`

- Area: Plugins
- Names: `-y`, `--yes`, `claude plugin install`, `claude plugin update`
- Tier: Use it now
- Useful: 4/5
- Signal: 2/5

### /login warns if an OAuth token is set in your environment

After logging in, you're warned if an OAuth token in your environment will override it later.

**What**

After a successful login, if `CLAUDE_CODE_OAUTH_TOKEN` was set when `/login` started and the gateway is not in use, the success message now notes that new sessions will keep using the old token until you remove it from your shell profile or settings file.

**Details**

- Message begins `Note: CLAUDE_CODE_OAUTH_TOKEN was set in your environment when /login started.`
- Helpers were added alongside to re-run the bypass-permissions and auto-mode checks after login.

**Evidence**

`Note: CLAUDE_CODE_OAUTH_TOKEN was set in your environment when /login started.`

- Area: Auth
- Names: `CLAUDE_CODE_OAUTH_TOKEN`, `/login`
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### A failed sandbox startup no longer sticks

If the sandbox fails to start, a later attempt can now succeed instead of failing forever.

**What**

When sandbox initialization fails, it now clears its recorded state and returns instead of caching a rejected promise, so a later attempt can succeed. A successful init clears the previous failure reason.

**Details**

- The reset covers both whether the sandbox was started with TLS termination and a new flag recording whether the installed network filter actually enforces the allowlist.
- The settings-change subscription now also re-subscribes to the stream of denied plugin producer directories.
- A new helper answers whether the sandbox is currently usable without re-running the dependency probe.

**Evidence**

`Sandbox configuration updated from settings change`

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

### Clearer message when a request is rejected as too large

If a request is too big, you're told whether dropping attachments or compacting would help.

**What**

On an HTTP 413 from the API, Claude Code now measures the request body and how much of it is images and documents, then tells you whether removing attachments or compacting could make it fit or whether nothing in the request is strippable.

**Details**

- The size threshold used for the explanation comes from a remote config value named `tengu_wobbly_fern`, falling back to a built-in limit when no value is supplied.
- Details of the oversized request are logged as a `request_body_over_limit` line.

**Evidence**

`so removing attachments or compacting cannot make it fit.`

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

### Git commands no longer hang on credential-manager popups

Git commands no longer hang forever on a Windows credential manager popup.

**What**

Git subprocesses now run with `GCM_INTERACTIVE` set to "never", alongside the existing `GIT_TERMINAL_PROMPT: "0"` and `GIT_ASKPASS`, so a Windows Git Credential Manager dialog cannot block a git command indefinitely.

**Evidence**

`GCM_INTERACTIVE: hHt.GCM_INTERACTIVE`

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

### MCP sign-in callback now uses 127.0.0.1

MCP browser sign-in now redirects to 127.0.0.1, fixing failures on IPv6-only localhost setups.

**What**

When an MCP server signs you in through your browser, the local address it redirects back to is now `http://127.0.0.1` rather than `http://localhost`. This fixes sign-in on machines where localhost resolves to the IPv6 address ::1 and nothing is listening there.

**Details**

- Servers you have already signed into keep working: the check that reuses a previously registered redirect address now accepts either origin, where it used to demand `http://localhost` exactly.
- The port is still chosen at runtime and filled into the address.

**Evidence**

`t === "http://127.0.0.1" || t === "http://localhost"`

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

### Plugin auto-updates can now take effect without a restart

Plugin auto-updates can now apply mid-session instead of always asking you to reload.

**What**

After a background auto-update, Claude Code tries to re-resolve plugin commands in the running session. The notification now says "reloaded for this session", or "reloaded with errors — see /plugin", and only falls back to the old "Run /reload-plugins to apply" when the reload was skipped or failed.

**Details**

- Reload is skipped when it would invalidate caches; failures fall back rather than leaving the session in a half-updated state.
- Plugins held at a fixed version report as blocked by the pin instead of updating.
- Outcomes counted under `plugin_command_reresolve_reload` with reasons `cache_impact`, `load_errors` and `refresh_failed`.

**Evidence**

`plugin-autoupdate-reloaded`, `reloaded with errors — see /plugin`

- Area: Plugins
- Names: `/reload-plugins`, `/plugin`
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### The built-in commit and PR command now blocks risky flags

The built-in commit-and-PR command can no longer use flags that rewrite history.

**What**

The built-in "Commit, push, and open a PR" command ships an explicit deny list, so Claude cannot reach for the flags that would rewrite history or redirect a PR while running it.

**Details**

- Covers git commit message and `--amend` and `--no-verify` flags, force pushes and branch deletions, `git add --force` and `--chmod`, `git checkout -f`, and `gh pr create`/`gh pr edit` flags that change the repo, base, head or body.
- Every pattern is registered for both `Bash(...)` and `PowerShell(...)`.

**Evidence**

`gh pr edit *--body-file*`

- Area: Slash Commands
- Names: `/commit-push-pr`
- Tier: You'll notice
- Useful: 4/5
- Signal: 2/5

### Subagent prompt for analysing one artifact comment thread

There's now a prompt for a helper agent that analyses a single artifact comment thread.

**What**

A system prompt was added for a dispatched subagent that reads exactly one comment thread on one published artifact and writes a plain-text brief whose first line must be exactly "ANALYSIS BRIEF". It replaced an unrelated read-before-edit reminder in that slot.

**Details**

- It may only read that thread plus page data, and any write-shaped tool call is denied.
- Comment text is treated as reader feedback, never as instructions to follow.

**Evidence**

`You are an artifact comment-thread analyst for Claude Code.`

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

### Unused network host allowlist

A fixed list of allowed hosts covering GitHub, npm, PyPI and Anthropic domains exists but nothing uses it.

**What**

A new module defines a frozen list of hosts, covering GitHub and its api/codeload/raw/objects variants, pypi.org, files.pythonhosted.org, registry.npmjs.org, anthropic.com, claude.ai, claude.com and ant.dev, along with a size cap and a host-character pattern. Nothing in this build references any of it, so it has no effect today and reads as groundwork.

**Evidence**

`"files.pythonhosted.org"`

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

### Command-sourced plugins re-resolve in the background behind a flag

Plugins installed via a command quietly re-resolve once per session, subject to a remote flag that defaults on.

**What**

Once per session, a plugin installed from a command can be re-resolved in the background. This needs non-essential network traffic to be allowed, the resolved path not to be the current project, and a remote-config flag whose built-in fallback is on.

**Details**

- The gate is `tengu_plugin_command_source_refresh`.
- Versions for these plugins come from a hash of their contents, and read as "unknown" until the command has run once.

**Evidence**

`tengu_plugin_command_source_refresh`

- Flag `tengu_plugin_command_source_refresh`: Not enough to say (read for one account on one subscription tier against v2.1.229; this account: no value returned, anonymous baseline: no value returned, compiled default: off) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Plugins
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### IPv6 addresses in sandbox network rules must be bracketed

Write IPv6 addresses in sandbox rules in bracketed, canonical form or enforcement gets conservative.

**What**

Sandbox `domain` and `injectHosts` entries are now normalized and checked for bracketed IPv6 spellings, ports and canonical form. Entries with unreliable spellings still work, but enforcement becomes conservative: it denies at least what any readable interpretation denies, and allow rules never widen.

**Details**

- Problem entries are collected and surfaced as doctor issues rather than failing startup.

**Evidence**

`Sandbox network domain entries have unreliable spellings`

- Area: Sandbox
- Names: `domain`, `injectHosts`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Managed setting to block command-sourced plugins

A managed key blocks command-sourced plugins, falling back to your existing hook restriction.

**What**

A new managed settings key, `disableCommandPluginSources`, stops plugins that come from a command source from being installed, updated or re-resolved. If it is unset it follows `allowManagedHooksOnly`, so an organization that already restricts hooks gets this too. Either key blocking produces a dedicated message naming both keys. Both fall back to blocking when the configured value fails validation. Only read from managed settings, so absent those, command sources stay allowed.

**Details**

- Gates: `policySettings.disableCommandPluginSources` and `policySettings.allowManagedHooksOnly`.
- When blocked, the plugin is not installed or updated and its command is not run.

**Evidence**

`Command-sourced plugins are disabled by your organization's managed settings (disableCommandPluginSources / allowManagedHooksOnly). The plugin was not installed or updated and its command was not run.`

- Area: Managed Settings
- Names: `disableCommandPluginSources`, `allowManagedHooksOnly`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Managed settings can force commit attribution off

Admins can force off the co-author trailers and session URL Claude adds to commits.

**What**

Settings pushed by an administrator can now switch off the co-authorship trailers Claude Code adds to commits. When the effective attribution setting resolves to disabled, commit trailers are forced off and the session URL line is suppressed too.

**Details**

- Applies to the projection of managed and policy settings, so a local setting cannot re-enable it.
- New helpers detect the case where a settings file supplied by flag or policy was rejected but did contain attribution keys.

**Evidence**

`commitTrailers`

- Area: Managed Settings
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Plugins installed by running a command gain a link mode and clearer consent errors

Command-installed plugins can be symlinked instead of copied, and consent errors say what changed.

**What**

A marketplace entry that installs a plugin by running a command on your machine can now use `mode: "link"`, where the directory the command produces is symlinked in place instead of copied. Link mode is refused on Windows. If the marketplace's command changed since you accepted it, the refusal now says specifically what changed, and the /plugin details pane shows the command and its mode.

**Details**

- the install record keeps the path the command produced and the paths previous runs produced, so a changed producer path is detectable
- consent is tied to the exact command text you approved

**Evidence**

`Installed by running a command on this machine:`

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

### Two new sandbox diagnostics for IPv6 misconfiguration

`claude doctor` now flags badly written IPv6 entries in your sandbox network rules.

**What**

`claude doctor` now flags unbracketed or non-canonical IPv6 addresses in allow and deny network domain entries, and credential `injectHosts` entries that can never match their destination because they are bracketed, carry a zone id, or are not canonically compressed. Each says to rewrite the entry as the bare, canonically-compressed address.

**Evidence**

`Sandbox credential injectHosts entries can never match their destination`

- Area: Sandbox
- Names: `claude doctor`, `injectHosts`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### remote-control --continue is documented as reattaching, not resuming

Remote control's `--continue` is documented as reattaching to a session recorded in the last four hours.

**What**

The help text for remote control now says `-c`/`--continue` reattaches to the session last recorded for the current directory or one of its git worktrees, and errors if nothing was recorded in roughly the last 4 hours. `--session-id` is likewise described as reattaching.

**Details**

- Wording change only; the 4 hour window is now stated where before it was not.

**Evidence**

`Reattach to a specific session by ID (cannot be`

- Area: Remote Control
- Names: `--continue`, `-c`, `--session-id`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Self-hosted runner requires `--base-dir` on Windows and validates launcher hooks

On Windows the self-hosted runner now requires --base-dir, and launcher hook config is validated.

**What**

On Windows the self-hosted runner has no default working directory, so `--base-dir` (or the `SELF_HOSTED_RUNNER_BASE_DIR` environment variable) must now be supplied; the help text says so. A new `launcher_hooks` config section is also checked at load: every entry must name a known hook event and point at a `.py` or `.sh` filename matching a restricted pattern, and duplicate entries are rejected regardless of letter case.

**Details**

- Config now records whether the base directory came from the built-in default or from `SELF_HOSTED_RUNNER_BASE_DIR`.
- Other platforms keep their existing default base directory.

**Evidence**

`required on Windows, which has no default`

- Area: Self-Hosted Runner
- Names: `--base-dir`, `SELF_HOSTED_RUNNER_BASE_DIR`, `launcher_hooks`
- Tier: Use it now
- Useful: 2/5
- Signal: 2/5

### Auto-mode setup rewritten around a single confirmation step

`/auto-mode-setup` opens straight on one confirmation step with reworded copy and key hints.

**What**

`/auto-mode-setup` now opens on a confirmation step instead of a three-question flow. The "Question N of 3" counter is gone, replaced by a hint showing the left and right keys, the intro no longer claims it "takes a few minutes", the error for a bad argument was reworded, and context placeholders now say gathered "at setup" rather than "at Q3".

**Evidence**

`action: "change usage"`

- Area: Auto Mode
- Names: `/auto-mode-setup`
- Tier: Use it now
- Useful: 3/5
- Signal: 1/5

## New Features

### Enterprise managed-mcp.json takes exclusive control of MCP servers

In remote mode an enterprise MCP config drops all other MCP servers, explaining each one.

**What**

When a session runs in remote mode and an enterprise-managed MCP config file (managed-mcp.json) is present, MCP servers from every other source are dropped, each with a per-server explanation. Servers passed with `--mcp-config` get a matching warning instead of silently disappearing.

**Details**

- Requires the `CLAUDE_CODE_REMOTE` environment variable plus the presence of an enterprise managed-mcp.json.
- Joins the existing hermetic-mode drop path; the shared drop helper gained a branch reporting the reason as "enterprise MCP config".

**Evidence**

`Ignored: an enterprise MCP config (managed-mcp.json) is present and has exclusive control over MCP servers`

- Area: MCP
- Names: `--mcp-config`, `managed-mcp.json`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Self-hosted runner can install launcher hooks sent by the server

The self-hosted runner writes server-supplied hook scripts for a session and deletes them afterwards.

**What**

When the server supplies hook scripts with a session, the runner validates and writes them under `hooks/.ccr-launcher` alongside a `launcher-settings.json`, then deletes them when the session ends. Scripts must have known event names, match `^[a-zA-Z0-9][a-zA-Z0-9_-]{0,62}\.(py|sh)$`, and stay under a per-script size cap. A `hooks` path that is not a directory is refused.

**Details**

- outcomes report through a new `self_hosted_launcher_hooks` telemetry family, including a distinct case where a repo settings file sets `disableAllHooks:true`
- runner path only; nothing happens unless the server sends the hooks with the session

**Evidence**

`self_hosted_launcher_hooks_validation_failed`

- Area: Self-Hosted Runner
- Names: `launcher-settings.json`
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Remote clients are told which slash commands need the terminal

Phone and remote interfaces are now told which slash commands only make sense in a terminal.

**What**

The session init payload sent over the control protocol now includes an optional list of slash commands whose behaviour only makes sense in a local terminal, such as `/color`, so phone and remote interfaces can hide them. The list is sent only when it is non-empty.

**Evidence**

`Subset of slash_commands whose UX is bound to the local terminal (e.g. exit, statusline).`

- Area: Remote Control
- Names: `/color`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Slash commands can mark themselves terminal-only, and SDK clients get the list

Terminal-only commands like /doctor and /exit are flagged so non-terminal hosts can hide them.

**What**

Commands that only make sense in the CLI, such as `/doctor`, `/exit`, `/statusline` and `/reload-plugins`, are now flagged as terminal-oriented. The startup `system`/`init` message sent to SDK clients carries a new `terminal_slash_commands` array naming those commands, so a non-terminal host can hide or reject them instead of offering commands it cannot run. The array is left out entirely when nothing is flagged.

**Details**

- Command definitions gained a `terminalOriented` field; only user-invocable commands appear in the array.

**Evidence**

`terminal_slash_commands`

- Area: Slash Commands
- Names: `/doctor`, `/exit`, `/statusline`, `/reload-plugins`, `terminal_slash_commands`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Self-hosted runner accepts a settings file, and rejects hooks and orchestrator mode on Windows

The runner can pass a settings file to sessions; its Windows hooks guard never actually fires.

**What**

The self-hosted runner launcher now takes a path to a settings file and passes it to the session it spawns as `--settings`, and `settings` is added to the argument names the server is not allowed to override. A separate guard is meant to fail with an explanation when `--hooks-dir`, `SELF_HOSTED_RUNNER_HOOKS_DIR` or orchestrator mode is used on Windows, but its platform argument defaults to the literal `"linux"` and both callers rely on that default, so it never fires in this build.

**Details**

- The reserved-argument list stops a server-provided config from replacing your settings path.
- The Windows error text explains that orchestrator mode depends on the spawn-runner lifecycle hook.

**Evidence**

`orchestrator mode is not supported on Windows: it is driven by the spawn-runner lifecycle hook`

- Area: Self-Hosted Runner
- Names: `--settings`, `--hooks-dir`, `SELF_HOSTED_RUNNER_HOOKS_DIR`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Plugins that install by running a command now need explicit terminal acceptance

Plugins that install by running a command now print it and ask you to confirm.

**What**

If a plugin marketplace entry installs itself by running a command on your machine, install and update now print that command and ask "Run this command now? [y/N]". Acceptance is remembered per exact command string, so a changed command forces a fresh confirmation with a warning.

**Details**

- `-y`/`--yes` is refused when the CLI is itself running inside another Claude Code session.
- In a non-interactive terminal the command is only printed, never accepted.
- Link mode for these plugins is refused on Windows.

**Usage**

`claude plugin install <name>@<marketplace>` **Evidence** `Not an interactive terminal, so the command was only displayed, not accepted.`

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

### Bulk plugin updates skip plugins that install by running a command

Bulk and background plugin updates skip command-installed plugins so nothing runs unattended.

**What**

Updating all plugins now skips any plugin whose install method is a marketplace-declared command and tells you why, pointing you at its details view to review the command first. Automatic background updates pass the same skip, so an unattended update never runs a command on your machine.

**Evidence**

`installed by running a command on this machine — open its details (Enter) to review the command and install it from there`

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

### Proxy keepalive pings during silent gaps

Long thinking pauses no longer trip idle timeouts, because the proxy sends periodic pings.

**What**

The local Anthropic-shaped proxy now sends a periodic SSE `ping` event whenever the upstream has gone quiet, so long thinking pauses no longer trip client or proxy idle timeouts. The stream wrapper takes a keepalive interval and enqueues a pre-encoded ping frame.

**Details**

- The bundled Bedrock proxy guidance document was updated to state the same requirement for third-party proxies.

**Evidence**

`emit your own \`event: ping\` during silent gaps`

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

### Command-sourced plugin installs ask before running anything

You must confirm before Claude runs a marketplace plugin command, and -y works only outside sessions.

**What**

Before running a marketplace-declared plugin command, Claude Code prints the command and the marketplace it came from and asks you to confirm. Declining aborts with "Aborted — the command was not run." `-y/--yes` is accepted only on a TTY outside a Claude Code session; inside a session it is refused with a message telling you to run it in your own terminal.

**Details**

- Consent is recorded against the exact command string, so a marketplace that changes its command prompts again.
- A workspace-local acceptance is re-confirmed rather than reused silently.

**Evidence**

`-y/--yes is ignored inside a Claude Code session: run this in your own terminal to accept the command shown above.`

- Area: Plugins
- Names: `-y`, `--yes`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### SDK clients are told the CLI accepts queued notifications

SDK clients now learn during handshake that the CLI can accept queued notifications.

**What**

The init handshake a client gets over the SDK control protocol now advertises a third capability value, `queued_notifications`, meaning the CLI accepts inbound queued notification stream messages and drains them on request.

**Details**

- Documented as read by the backend from the persisted init event to decide whether it may send such messages.

**Evidence**

`'queued_notifications' = the CLI accepts inbound queued_notification stream messages and drains them via ReadNotifications`

- Area: SDK
- Names: `queued_notifications`
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5

### Pinned memory files are listed as their own source

Memory file listings now label pinned files separately from auto-recalled and files you read.

**What**

When Claude Code tells you where the files in a session's memory came from, pinned files now appear as "pinned, loaded automatically", separate from files recalled automatically and files you opened with the Read tool. If the same file arrives from more than one source, the pinned label wins.

**Details**

- Third attribution source alongside automatic recall and Read tool opens
- Pinned entries take precedence over the same path reported by another source

**Evidence**

`pinned, loaded automatically`

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

### Backgrounded shell commands note how they ended

Background shell output now ends with a line saying it was killed or its exit code.

**What**

When a shell command you sent to the background finishes, a bracketed line is appended to its captured output saying either that it was killed or what exit code it returned, so the transcript records the outcome rather than just stopping.

**Details**

- The marker is written before the existing success/failure telemetry for the background shell.

**Evidence**

`exited with code `

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

### Killed background shells show a `[killed]` marker

Stopping a background shell appends a [killed] marker so truncated logs are obvious.

**What**

Stopping a background shell now appends an explicit `[killed]` line to its output, so a truncated log is distinguishable from one that ended on its own. The "stopped" event also now includes the path of the output file.

**Evidence**

`[killed]`

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

### Pull request links are recognised in `/cr/` review URLs

Pull request URLs using a /cr/ path segment are now recognised.

**What**

Claude Code now spots pull request URLs that use a `cr` path segment, in addition to `pull`, `pull-requests` and `merge_requests`.

**Details**

- The host and repository portion of the pattern was consolidated onto one shared fragment shared by all the variants.

**Evidence**

`pull|pull-requests|cr`

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

### Sandbox doctor warns about unbracketed IPv6 hosts

Sandbox diagnostics warn when an IPv6 host in a rule is missing square brackets.

**What**

Sandbox diagnostics now flag permission rules that name an IPv6 address without square brackets, both for domain rules and for injected hosts, so a rule that silently fails to match gets called out.

**Details**

- Two new checks, one for domains and one for inject hosts; the existing Linux glob-pattern warning summary now shares a common formatter.

**Evidence**

`getUnbracketedIpv6DomainWarnings`

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

## Improvements

### Command-sourced plugins are not rebuilt behind your back

Plugins that build via a command won't silently run it; you get an error telling you to update.

**What**

When a plugin installed from a command has no cached copy, Claude Code will not run its command if the plugin is disabled or if background command execution is turned off. Instead it shows a load error telling you to run `claude plugin update` in a terminal.

**Details**

- The refusal is reported as `command_source_inactive` rather than a generic operation failure.

**Usage**

`claude plugin update` **Evidence** `command_source_inactive`

- Area: Plugins
- Names: `claude plugin update`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Command-sourced plugins refresh on their own schedule

Command-built plugins refresh on their own background schedule, and block if the command changed.

**What**

Plugins installed by running a command are now left out of the normal plugin autoupdate pass and refreshed by a separate background job that re-runs the command you accepted. If the marketplace's command no longer matches the one you consented to, the refresh blocks with an explanation instead of running it. On by default via `tengu_plugin_command_source_refresh`; managed settings can also disable it, in which case those plugins are simply skipped.

**Details**

- the autoupdate telemetry event now carries `command_plugins_refreshed` and `command_plugin_refresh_failed` counts

**Evidence**

`Plugin autoupdate: command-source refresh disabled by tengu_plugin_command_source_refresh (still excluding command-sourced plugins from the regular pass)`, `Plugin autoupdate: skipped (auto-updater disabled)`

- Flag `tengu_plugin_command_source_refresh`: Not enough to say (read for one account on one subscription tier against v2.1.229; this account: no value returned, anonymous baseline: no value returned, compiled default: off) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Plugins
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Artifact watch prompt now spells out remote wake-ups and comments

The artifact watch prompt now explains remote wake-ups and comment forwarding before you allow it.

**What**

The confirmation shown the first time Claude watches an artifact was rewritten. It now says a remote session is woken with a new turn when the artifact is republished and, where you grant it, when a comment on the artifact is sent to Claude, and states that notifications carry no content. The Artifact tool description gained a matching sentence about durable wake subscriptions, emitted only when an internal flag is on.

**Evidence**

`in a remote session, also when a comment on it is sent to Claude`

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

### Per-session tool state stops caches leaking between sessions

Caches now live per session, so parallel sessions in one process stop contaminating each other.

**What**

Caches that were previously process-wide now live in a per-session store: the WebFetch URL and domain-check caches, the send-file duplicate set, Bash working-directory tracking, the alias-probe fired set, plus per-host containers holding plugin sync state, the keybinding writer, the diagnostics manager and the binary-check cache. In a process running more than one session at once (SDK, runner, cowork) these no longer bleed across sessions.

**Details**

- The store is created with the session context and carried through context construction, forking and agent spawn.
- Disposing a session installs a fresh store rather than clearing shared maps.

**Evidence**

`toolState: this.toolState`, `toolState`

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

### Plugins installed by running a command refresh once per session

Plugins built by running a command re-run once per session in the background.

**What**

A plugin whose source is a command to run, rather than a marketplace download, is now re-run once per session in the background so its output is picked up. The background marketplace updater deliberately skips these, and the auto-update notification splits them into their own group alongside updated and blocked plugins.

**Details**

- Subject to the same managed-settings gate as the rest of plugin auto-updating.
- No marketplace network refresh happens on this path.

**Evidence**

`is installed by running a command, which the background marketplace update never runs; it is re-resolved separately once per session.`

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

### Device bridge can list MCP servers in a remote workspace

MCP servers in a remote workspace now list over the device bridge, including pending ones.

**What**

MCP server settings normally read back as empty in a remote workspace. Requests made on the device-bridge path now bypass that and read the underlying settings directly, and also use the permissive approval lookup so project servers that are still pending or were rejected show up.

**Evidence**

`deviceBridge`

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

### Durable wake skips now explain themselves

When a durable wake subscription is skipped, the result now explains why.

**What**

When a durable wake subscription is skipped, the tool result now carries an optional `durable_skip_reason` with a prose explanation, covering cases such as durable wake subscriptions not being enabled or no wake-webhook minter being available. Telemetry also separately records a subscription downgraded because comment events are unsupported.

**Evidence**

`durable_skip_reason`

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

### Remote sessions can accept much longer heartbeat intervals from the server

Idle remote sessions can now check in far less often when the server says so.

**What**

An idle remote session can now beat far less often when the server asks it to. The upper limit on a server-advised heartbeat interval was a flat 900000 ms; it is now effectively unbounded, still capped by the session token's lifetime.

**Details**

- Only applies when the caller advertises heartbeat probe support, which defaults to off; without it the ceiling stays 300000 ms.
- The new ceiling is computed as the maximum 32-bit integer divided by one plus the jitter fraction.

**Evidence**

`cli_heartbeat_interval_updated`

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

### Teleporting a session to the cloud now stops the local remote bridge from reviving it

After you send a session to the cloud, your terminal stops trying to reconnect to it.

**What**

Once a session has been sent to the cloud, the local remote-control bridge no longer tries to reattach to it, re-mint its credentials or re-poll for recovery. Each of those paths now bails out early reporting "Session teleported to cloud" rather than reconnecting to a session that has moved.

**Details**

- Teleported session ids are held in a bounded set, so very old ids eventually age out.
- Reattach is either suppressed or fails terminally depending on the path.
- New counters: `bridge_repl_v2_revive_reattach_teleported` and `bridge_repl_v2_remint_loop_teleported`.

**Evidence**

`[remote-bridge] Reattach suppressed for teleported session `

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

### Sandbox proxy explains itself to unauthenticated clients

The sandbox proxy now tells refused connections why, including SSH ones, instead of dropping them.

**What**

When the sandbox proxy requires an auth token, a client that offers no authentication method now gets an explanation rather than a silently dropped connection. On port 22 the proxy writes an SSH-shaped banner and disconnect message saying why the connection was refused, including the policy reason when the destination is on the deny list.

**Evidence**

`This proxy requires authentication, and this client did not offer an authentication method, so the connection was refused.`

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

### Enterprise MCP config now overrides `--mcp-config` with a named warning

When enterprise MCP config wins over your `--mcp-config`, the log names the file and dropped servers.

**What**

Where an enterprise MCP configuration is present, servers passed on the command line with `--mcp-config` are dropped, and the log now names `managed-mcp.json` as the reason and lists the ignored server names instead of a generic note.

**Details**

- Applies to remote, non-strict configurations.

**Evidence**

`is present and has exclusive control over MCP servers; ignoring `

- Area: MCP
- Names: `--mcp-config`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Auto-mode model selection retries when its classifier model is unavailable

Auto mode retries instead of failing when the model that routes your turn is briefly unavailable.

**What**

The model that picks which model handles your turn in auto mode is now retried with exponential backoff and jitter when it returns a retryable unavailability error, for a configured number of attempts, instead of failing on the first one.

**Details**

- Each retry is logged and counted under `tengu_auto_mode_outer_retry`.

**Evidence**

`tengu_auto_mode_outer_retry`

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

### Listing artifacts no longer asks for permission

Listing artifacts no longer asks you for permission first.

**What**

The Artifact tool's list action used to require a one-time confirmation per session, with separate prompts for the shared and all scopes. That branch is gone and listing is permitted outright as a read-only action in every scope.

**Evidence**

`Listing artifacts published by or shared with the user is a read-only action`

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

### Artifact viewer blocks downloads the page starts by itself

Artifact pages can't start downloads themselves, so Claude is told not to offer plain download links.

**What**

The Artifact tool description now warns that the viewer sandbox makes any page-initiated download inert, including `<a download>` links pointing at data: or blob: URLs and script-driven saves, so a file must never be offered through a plain link. Handing the viewer a file to save is now listed as a runtime capability to look up in the artifact-capabilities skill.

**Evidence**

`blocks any download the page starts itself`

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

### Directories produced by plugin commands are protected from edits

Folders created by command-built plugins are now treated as sensitive and protected from edits.

**What**

Directories created by command-sourced plugins are now treated like `.claude` config paths, so editing a file inside one is refused with "Claude requested permissions to edit ... which is a sensitive file."

**Details**

- The denied set is built by scanning the `installed_plugins.json` records across plugin roots, and is also updated directly when a plugin is installed or removed.
- The scan result is cached for 5 seconds.

**Evidence**

`installed_plugins.json`

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

### Generated review workflow leaves inline comments

The review workflow Claude scaffolds now posts inline comments on the diff.

**What**

The GitHub Actions review workflow that Claude Code scaffolds now passes `--comment` to `/code-review` and grants the inline-comment MCP tool, so reviews land as inline comments on the diff.

**Evidence**

`mcp__github_inline_comment__create_inline_comment`

- Area: GitHub Actions
- Names: `/code-review`, `--comment`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Gateway streaming keeps connections alive with pings

Long streaming requests through the local gateway send keep-alive pings so proxies don't drop them.

**What**

Streaming responses through the local gateway now emit a periodic `event: ping` frame when the upstream has gone quiet, so idle proxies and load balancers are less likely to drop a long request. The interval defaults to 15000 ms.

**Details**

- The frame carries the payload line `data: {"type": "ping"}`.
- No such frame existed in the previous build.

**Evidence**

`data: {"type": "ping"}`

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

### `/commit-push-pr` restricts its own tools

`/commit-push-pr` now carries an explicit list of tools it may not call.

**What**

The "Commit, push, and open a PR" command now carries an explicit list of tools it may not call, narrowing what it can do beyond the tools it already allows.

**Evidence**

`description: "Commit, push, and open a PR"`

- Area: Slash Commands
- Names: `/commit-push-pr`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Assistant messages built from streamed text now report a stop reason

Stream-json output now includes the stop reason field tools expect on assistant messages.

**What**

When Claude Code assembles an assistant message from accumulated text on the stream-json path, it now sets `stop_reason: "end_turn"` and `stop_sequence: null`. Tools consuming stream-json output see a complete, correctly shaped message instead of one missing those fields.

**Evidence**

`stop_reason: "end_turn"`

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

### Render errors are now reported instead of silently blanking

A crash in the interface now reports itself instead of leaving a blank screen.

**What**

The component that catches crashes in the UI previously just recorded that an error happened and rendered nothing. It now reports the error and calls an optional `onError` handler, so a failed render surfaces rather than disappearing.

**Evidence**

`componentDidCatch`

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

### In-place plugins are refused when their directory contains your working directory

A linked plugin won't load if its folder contains your working directory.

**What**

A plugin served in place from its source directory (link mode, no cache copy) is not loaded if that directory contains the current working directory, because its files could not be protected from sandboxed commands running there. The reason appears as a plugin load error.

**Evidence**

`which contains the working directory, so its content could not be protected from sandboxed commands here.`

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

### Plugin cache copies abort if the source changes underneath them

Copying a plugin into cache aborts if files are swapped out mid-copy.

**What**

Copying a plugin into the cache now opens files without following symlinks where the platform allows it, and re-checks during the walk that directories are still directories and files still regular files. If something is swapped mid-copy the copy aborts with an explicit error instead of following the new path.

**Evidence**

`plugin copy source swapped for a non-regular file mid-copy`

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

### Plugin installs replace symlinks with real files

Installed plugins get real files instead of symlinks, with unsafe links removed.

**What**

When a plugin is copied into the versioned cache, a new pass walks it and turns symlinks into real copies. Links that dangle, escape the plugin root, form an ancestor cycle, point at something other than a regular file, or use a reserved top-level name are removed instead. If the plugin directory is read-only the pass logs and leaves the links alone rather than failing the install.

**Details**

- node_modules links are re-checked after conversion.
- The outcome is written to a `.links_materialized` marker file holding the CLI version and the materialized, removed and failed counts.
- Only some install paths invoke the pass; the conditions that select them sit outside the copy routine.

**Evidence**

`materializeLinks: ${e}: cannot write (${T}), leaving links in place`

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

### Plugin failures caused by an un-run command are reported as policy errors

Certain plugin failures are now labelled policy problems, changing how they're shown to you.

**What**

Messages such as a plugin's command not having been run, command-sourced plugins being disabled, and the feature not being supported on Windows yet are now classified as policy failures rather than unknown ones, which changes how they are surfaced.

**Evidence**

`Command-sourced plugins are disabled`

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

### Command-sourced plugins record where their files came from

Command-built plugins record their command and output directory so moves can be detected.

**What**

Installs of plugins that build themselves by running a command now store the command, the directory it produced and any previous producer directories, so a later change of location can be spotted. Reinstalling from cache now fails with a distinct "not cached" code instead of a generic "not found".

**Details**

- Installs pass a consent record of `shown`, `recorded` or `none`.
- Plugin state tracks which producer directories have been scanned and which were denied.

**Evidence**

`failureCode: r ? "not_cached" : "not_found"`

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

### Permission-prompt-tool errors reportable without leaking tool names

Permission-prompt-tool errors sent as telemetry no longer include your tool names.

**What**

Failures validating `--permission-prompt-tool` now have fixed redacted variants for telemetry, so the reported error omits the tool name and the list of available MCP tools.

**Evidence**

`Error: tool <redacted> (passed via --permission-prompt-tool) must be an MCP tool`

- Area: Permissions
- Names: `--permission-prompt-tool`
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Partially converted plugin trees are no longer cached

A plugin that only half-converts during install fails instead of caching a broken copy.

**What**

Copying a plugin's files into a symlink-free form now fails loudly when some entries could not be converted or the destination is read-only, instead of caching a half-converted tree that would be reused later.

**Evidence**

`plugin tree could not be made symlink-free`

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

### Automatic compaction errors say what went wrong

When auto-compaction fails you now see the underlying reason, not just a generic error.

**What**

When automatic compaction fails, the message now appends the underlying failure detail, truncated, instead of only the generic compaction error text.

**Evidence**

`automatic compaction failed: `

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

### Auto-mode setup wizard reduced to one screen

Auto-mode setup is one screen now instead of three separate questions.

**What**

The separate posture, scope and depth screens are gone; one confirm screen collects everything, and how deep the setup scan goes (both, shell only, repos only, or just here) is derived from whether you tick shell history and other repos. Scope is fixed to the project.

**Details**

- The same setup wizard answers telemetry event is still recorded.

**Evidence**

`tengu_auto_mode_setup_wizard_answers`

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

### Session load failures now explain themselves

When a session transcript won't load, you're told exactly why in plain words.

**What**

When a session transcript cannot be read, the error says why in plain words: the file is a symbolic link, something other than a regular file is in its place, it exceeds the size limits, or it changed while being read, in which case you are told to run the command again.

**Details**

- Replaces raw filesystem error codes in the session load path.
- Symlinked transcripts are reported as unsupported rather than silently followed.

**Evidence**

`this session's transcript is a symbolic link, which session storage does not read through.`

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

### Prompt history search shows what it is doing

Prompt history search shows scan progress and stops results reshuffling under you.

**What**

Searching past prompts now scans history incrementally and tracks whether the scan is still running, finished, cut short, or failed, with a distinct empty-state message for each. Fuzzy matches are held back until the scan finishes, so results no longer shuffle underneath you.

**Details**

- A failed read shows `Couldn't read prompt history`.
- Entries whose stored display text is not a string are skipped rather than rendered.
- The per-entry age labels were removed.

**Evidence**

`Couldn't read prompt history`

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

### `attribution` and `includeCoAuthoredBy` no longer raise settings-conflict warnings

Setting commit attribution in more than one place no longer triggers a conflict warning.

**What**

These two settings are now excluded from the check that warns when a managed or inline setting conflicts with your own, so setting commit attribution in more than one place stops producing a warning.

**Details**

- The attribution settings object now accepts extra keys rather than rejecting them.
- New helpers derive the commit trailer state from that object as explicitly enabled, implicitly enabled, or disabled.

**Evidence**

`e !== "attribution" && e !== "includeCoAuthoredBy"`

- Area: Settings
- Names: `attribution`, `includeCoAuthoredBy`
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Node warnings are suppressed for the rest of the process

Node deprecation and experimental warnings stay out of your terminal for the whole run.

**What**

Previously only warning listeners already registered were removed, so warnings emitted later still printed. The CLI now sets `process.noProcessWarnings`, keeping Node deprecation and experimental-feature warnings out of your terminal for the whole run.

**Evidence**

`process.noProcessWarnings`

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

### Publish warnings no longer flood the tool result

Long publish warning lists get truncated so they don't crowd out the actual result.

**What**

Warnings returned when publishing an artifact are joined and cut off at half the result budget, with a marker where the text stops, so a long warning list cannot crowd out the rest of the result.

**Details**

- Truncated output ends with `(warnings truncated)`
- A separate helper sanitizes and caps each individual server warning before joining

**Evidence**

`(warnings truncated)`

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

### Unknown-model context warning no longer suggests an impossible fix

The unknown-model warning only suggests the `[1m]` suffix when that would actually help.

**What**

The warning shown for a model Claude Code does not recognize only tells you to append `[1m]` to the model name when the assumed context window is smaller than 1M. Otherwise it says the model accepts less.

**Evidence**

`append [1m] to the model name for 1M`

- Area: Models
- Names: `[1m]`
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### Settings fix prompt notes that --settings is read once

Fixes to a file passed via `--settings` are noted as taking effect next session.

**What**

If a reported settings problem is in the file you passed with `--settings`, the generated fix-it prompt now says that file is read only at startup, so you are told the fix lands on your next session rather than immediately.

**Evidence**

`That file is read once at startup, so after fixing it, tell me the fix takes effect the next time I start a session`

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

### Plugin autoupdate stops early when the auto-updater is off

Plugin auto-update stops early when the updater is off, and notifications no longer overwrite each other.

**What**

The pass now returns immediately with a debug line, and still records a "skipped" outcome if a command-source refresh had already run. Pending update, blocked and re-resolved notifications are now merged by a single helper rather than overwriting each other, so one list no longer hides another.

**Evidence**

`Plugin autoupdate: skipped (auto-updater disabled)`

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

### Auto-mode setup confirmation is now a single keyboard form

Auto-mode setup confirmation is now one keyboard form with a picker and checkboxes.

**What**

The confirm step of the auto-mode setup wizard is a custom form: a left/right picker for how you use this project (Work, Open source, Hobby, Mixed), two checkboxes for optional scans, and a Continue row.

**Details**

- With screen-reader mode on, it renders as plain select lists instead, including one combined "Optional reads" list offering shell history, other repos, both or neither.

**Evidence**

`Optional reads (Claude already reads this project):`

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

### Updating an existing PR no longer passes a PR number

Updating an existing PR now relies on your branch instead of passing a PR number.

**What**

The commit and PR prompt now tells Claude to update an existing PR with `--title` and `--body` and no PR number or URL, since `gh` already resolves the current branch's PR.

**Evidence**

`(gh resolves the current branch's PR when no selector is given)`

- Area: Slash Commands
- Names: `--title`, `--body`
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### A whitespace-only prompt gets its own error

A prompt of only spaces to `--print` now gets its own clear error.

**What**

Passing a prompt that contains only spaces or newlines to `--print` now reports that specifically instead of the generic missing-input error.

**Details**

- Message: `Error: Input contained only whitespace. Provide a prompt with text through stdin or as a prompt argument when using --print`.

**Evidence**

`Error: Input contained only whitespace. Provide a prompt with text through stdin or as a prompt argument when using --print`

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

### Artifact guidance is clearer about file downloads

Artifact guidance now says static pages can't hand you a file to save.

**What**

The wording for what an artifact can and cannot do now lists handing the viewer a file to save among the things static HTML cannot provide, and the error you get when publishing an unservable supporting file explains what supporting files are for.

**Details**

- The rejection now spells out that neither a served file nor a `data:`/`blob:` link gives the viewer a file, because downloads started by the page are blocked.
- Applies to the runtime-capability description and the prototype/template guidance.

**Evidence**

`Supporting files are assets the page itself loads`

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

### `/teleport` hint mentions both directions

The `/teleport` tip now mentions you can move sessions both to and from the cloud.

**What**

The tip now says you can move a session between the terminal and the cloud, rather than only describing pulling a web session down.

**Evidence**

` to move a session between here and the cloud`

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

### TLS certificate mismatch errors are redacted before display

Certificate mismatch errors are trimmed so long hostname lists stay out of your transcript.

**What**

Error messages about a hostname or IP not matching the server certificate now have their trailing detail truncated, which keeps full certificate subject-alternative-name lists out of logs and transcripts.

**Details**

- Two patterns were added to the error scrubbing table, covering the different apostrophe encodings these messages use.

**Evidence**

`Cert does not contain a DNS name`

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

### Whitespace-only prompts are rejected for `--cloud` and `--environment`

Whitespace-only prompts on `--cloud` and `--environment` are rejected instead of sent empty.

**What**

A prompt consisting only of spaces or newlines is now treated as no prompt at all on both non-interactive paths, so you get the existing "requires a prompt" error instead of an empty request being sent.

**Evidence**

`Error: non-interactive --cloud <session_id> requires a prompt (positional or stdin).`

- Area: CLI
- Names: `--cloud`, `--environment`
- Tier: You'll notice
- Useful: 2/5
- Signal: 1/5

### OAuth token warning no longer tells you to unset the variable

The OAuth token login warning now correctly says your session switches to the new credentials.

**What**

When `CLAUDE_CODE_OAUTH_TOKEN` is set, the login warning now says the current session will switch to your new credentials after you log in, rather than claiming the environment variable overrides them at runtime.

**Evidence**

`This session will switch to your new credentials after logging in`

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

### TLS certificate details redacted from error text

Certificate hostnames and IPs are replaced with placeholders in logs and error reports.

**What**

Certificate errors no longer carry the certificate's alternative-name list, hostname or IP into logs and reports. The existing error scrubber now replaces altnames with a redacted marker and substitutes `<host>` and `<ip>` placeholders.

**Evidence**

`'s altnames: <redacted>`

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

### Trailing slashes stripped during path normalisation

Paths with a trailing slash are now treated the same as ones without.

**What**

On platforms other than Windows, a trailing `/` is trimmed before the rest of path resolution, with the root `/` preserved, so `foo/bar/` and `foo/bar` are treated as the same path.

**Evidence**

`e.endsWith("/") && e !== "/"`

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

### Faster new-diagnostic detection, and none at all without a baseline

Detecting new editor diagnostics after an edit is faster and skipped entirely without a baseline.

**What**

Comparing editor diagnostics before and after an edit now hashes each one (message, severity, source, code, range) into a set instead of comparing every diagnostic against every other, and when there is no earlier snapshot to compare against it returns immediately without even asking the IDE for diagnostics. Less CPU and one fewer round-trip on files that were never snapshotted.

**Evidence**

`getDiagnostics`

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

### One wording for an unavailable tool

Unavailable tools now always say the tool is disabled for this session.

**What**

The message shown when a tool cannot be used no longer branches into a softer variant; both cases now say the tool is disabled for this session, in subagents as well as here.

**Evidence**

`. ${e} is disabled for this session, in subagents as well as here.`

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

### Bind failure message rewritten

The remote device bind failure message now describes what the server actually rejected.

**What**

The explanation shown when a remote device bind is rejected now says the server refused an initial event or field sent with the bind, instead of reporting a missing device proof.

**Evidence**

`the server refused an initial event or field sent with the bind`

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

## Bug Fixes

### Injected stream-json user messages no longer count as human turns

Programmatically injected messages are no longer counted as coming from a real person.

**What**

Messages pushed into a session programmatically are now marked and skipped by both the content transform and the check that stamps a turn as coming from a verified human relay, so an injected line cannot be attributed to a person. The injected line is also no longer given a trailing newline before it is parsed.

**Evidence**

`verifiedSlackHumanTurn`

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

### Tool inputs are filtered before being recorded

Only a few named fields of tool input get recorded in history now, and NUL-containing strings are dropped.

**What**

History summaries and edited-file tracking no longer read raw tool input. A new helper picks out only `file_path`, `path`, `pattern`, `glob`, `command`, `query` and `content`, and drops any string containing a NUL byte.

**Evidence**

`return typeof e === "string" && !e.includes("\x00");`

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

### Teleported sessions no longer touch their remote connection

Sessions moved to another machine no longer archive or rebuild their old remote connection.

**What**

Three paths that archive, refresh or rebuild a session's remote connection now stop early for sessions that were teleported to another machine, logging that they were suppressed rather than acting. One of them returns the outcome `"suppressed_teleported"`.

**Evidence**

`[remote-bridge] Rebuild suppressed for teleported session`

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

### MCP OAuth repairs a stale saved client instead of re-registering

A stale saved OAuth client for an MCP server is repaired in place instead of re-registered.

**What**

When a saved OAuth client for an MCP server has an out-of-date redirect URI, Claude Code no longer always forces a fresh dynamic registration. If a client ID is configured, it updates the stored entry in place, adopting that client ID and clearing the secret, or just bringing the redirect URI into line, and uses it. Failures to write the repair are logged.

**Evidence**

`stale client_id repair`

- Area: MCP
- Names: `oauth.clientId`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Background roster and dispatch files handle symlinks and unreadable files explicitly

Background task files that are symlinks or unreadable are quarantined or skipped instead of trusted.

**What**

The background task roster file and the dispatch files it works with are now inspected before use. Symlinks and anything that is not a regular file are quarantined or removed instead of read, and a file that cannot be inspected produces a clear skip message rather than being treated as missing.

**Details**

- Message on skip: `bg roster.json update skipped: the file could not be inspected`
- Read errors that mean the filesystem refused (ENXIO, EFBIG, ELOOP, ENAMETOOLONG) take their own path rather than being lumped in with "not found"

**Evidence**

`bg roster.json update skipped: the file could not be inspected`

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

### MCP OAuth no longer re-saves a stale client secret or redirect URI

MCP sign-in no longer saves a stale client secret or redirect address back to disk.

**What**

Saving OAuth client details for an MCP server now drops the client secret when the issuer is known, the client id matches the one already stored with no secret against it, and it matches the configured `oauth.clientId`. A previously stored redirect URI is only reused when this client is not doing the redirect itself, instead of falling back to the current one.

**Details**

- Prevents a secret from a different registration being written back over a secretless entry.

**Evidence**

`mcpOAuth`

- Area: MCP
- Names: `oauth.clientId`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### GitHub rate-limit reminder backoff is now per session

One session hitting the GitHub rate limit no longer silences the warning in your other sessions.

**What**

The reminder telling Claude it has hit the shared GitHub API limit keeps its backoff timestamp in per-session tool state instead of a single process-wide variable, so one session's reminder no longer suppresses another's.

**Details**

- The reminder text still cites the 5,000 requests per hour limit shared across all tools and agents.

**Evidence**

`GitHub API rate limit exceeded (5,000/hr shared across all tools and agents)`

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

### Windows file watching retries a permission error before giving up

On Windows you get fewer spurious file-watch errors when a file is briefly locked.

**What**

File watchers now handle an EPERM error on Windows by trying to open the file for reading before reporting a failure, which stops spurious watch errors on files Windows briefly locks.

**Evidence**

`l.close.bind(l)`

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

### Diff sidebar failures no longer take down the view

An error drawing the diff sidebar no longer breaks your whole session view.

**What**

A rendering error in the diff sidebar is now caught and reported rather than escaping and breaking the session view.

**Details**

- Reported as `repl_diff_read` / `render_threw`, with the React component stack hashed when one is present.

**Evidence**

`render_threw`

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

### A failing tool summary no longer breaks the display

A tool with confusing input now falls back to showing its raw name instead of breaking the display.

**What**

Working out a tool's display name and its one-line summary is now wrapped in error handling, so a tool whose input confuses that code falls back to showing the raw tool name instead of taking down the renderer.

**Evidence**

`getToolUseSummary`

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

### Truncating a file to empty is no longer skipped

Truncating a file to empty now actually empties it instead of doing nothing.

**What**

The append helper previously routed to the storage backend only when there was content to write, so a write in truncating mode with an empty payload did nothing. It now routes on mode as well, and the file is emptied as asked.

**Evidence**

`r === "w" || t.length > 0`

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

### Whitespace-only prompts now tell you nothing was sent

A whitespace-only prompt now tells you plainly that nothing was sent to the model.

**What**

In non-interactive runs, a prompt that contains only spaces or newlines now returns a warning instead of failing silently. The result reads "Blank prompt — the message was only whitespace, so nothing was sent to the model."

**Details**

- The turn ends without querying the model at all, so no tokens are spent.
- Previously the empty submission was only recorded internally, with no message back to the caller.

**Evidence**

`the message was only whitespace, so nothing was sent to the model.`

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

### Runner refuses unsafe client platform labels

Odd platform labels from the runner are rejected or replaced with "unknown" instead of exported.

**What**

A shared check, `^[A-Za-z0-9_.-]{1,64}$` plus rejection of "unknown", a leading "-", "." and "..", now guards what gets exported as `CLAUDE_RUNNER_CLIENT_PLATFORM`. A bad value from a spawn hint is escaped and logged instead of exported, and a bad label arriving with a polled assignment falls back to "unknown" with a debug line rate-limited to once per 256 sessions.

**Evidence**

`spawn-hint client_platform rejected by the env-export gate (CLAUDE_RUNNER_CLIENT_PLATFORM left unset)`

- Area: Self-Hosted Runner
- Names: `CLAUDE_RUNNER_CLIENT_PLATFORM`
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Empty prompts in non-interactive runs no longer hit the API

A blank prompt in a scripted run stops right away with a warning instead of calling the API.

**What**

A prompt made up entirely of whitespace in a non-interactive session now stops immediately with a warning instead of sending an empty query.

**Details**

- Reported as `prompt_submit_empty`.

**Evidence**

`prompt_submit_empty`

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

### File and image links degrade to plain text when the path cannot be linked safely

File and image paths that cannot be linked safely, like UNC shares, now print as plain text.

**What**

Terminal hyperlinks now build the `file://` URL first and skip linking entirely for paths that resolve to a hostname, such as Windows UNC shares, or an otherwise unsafe path. Those file references and image references print as plain text rather than a broken clickable link.

**Evidence**

`(Original working directory)`

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

### Malformed file:// links are dropped instead of printed

Broken file:// links, such as those from Windows network paths, are no longer printed.

**What**

When turning a local path into a clickable terminal link produces a URL with a hostname (which happens with Windows UNC paths) or with no path at all, no link is emitted now instead of a broken one. Affects image references and file-path links.

**Evidence**

`pathToFileURL`

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

### Session marker files respect the process umask

Session marker files are written with permissions that respect your umask.

**What**

The in-use session marker file is now written with an explicit mode of 438 masked by the process umask rather than whatever default applied. The surrounding error handling was also flattened so a failed write no longer skips registering the follow-up path.

**Evidence**

`{ mode: 438 & ~process.umask() }`

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

### Bash commands that are not strings no longer reach the parser

A non-string Bash command can no longer reach the command parser.

**What**

The check deciding whether a Bash invocation is a read or search command bailed out only on falsy values, so a non-string command could be passed to the parser. It now bails out on anything that is not a string.

**Evidence**

`isSearchOrReadCommand`

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

## In Development

### Rate-limit checkpoint can pre-queue "continue"

Hitting a rate-limit checkpoint can prefill "continue" for you, cancelled if you upgrade instead.

**What**

The prompt can prefill and queue the word "continue" when you hit a rate-limit checkpoint, and cancels that prefill if you take the upgrade offer instead. The cancellation is reported to telemetry as `tengu_rl_checkpoint_auto_continue_cancelled_by_upsell`. Nothing in the build source turns this on, so whether it runs is decided elsewhere.

**Evidence**

`tengu_rl_checkpoint_auto_continue_cancelled_by_upsell`

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

### New "send to cloud" teleport menu, off unless a server turns it on

A menu to send your session to the cloud exists but stays hidden unless the server enables it.

**What**

The resume flow can show a new teleport menu for sending a session to the cloud, guarded by a flag named `tengu_teleport_send_to_cloud` whose built-in fallback is false. Nothing in this build sets it, so without a remote config value you get the previous teleport screen.

**Evidence**

`tengu_teleport_send_to_cloud`

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

### Transcript writes can be routed through a storage backend

Transcript writes can go through a storage backend instead of plain files, but files remain the default.

**What**

Transcript appends, hydration, forking, snapshot copies, job timelines and history-suppression records all accept an optional storage backend and key. When one is supplied, writes go through that backend; with none supplied they fall back to a plain file append, which is what happens by default. Backend failures are re-shaped into standard errno-style errors so they show up in telemetry the same way file errors do.

**Details**

- Failure log line: `transcript storage append failed`
- Backend path uses append, replaceRecords and stat operations

**Evidence**

`transcript storage append failed`

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

### Artifact comments can say which page of a multi-file artifact they sit on

Artifact comments can name which page of a multi-file artifact they're attached to.

**What**

Comment listings can carry a marker naming the file, that is the page, of a multi-file artifact a thread is anchored to, with a separate row when that anchor could not be read during a fetch. The preamble that introduces a comment block was extended to explain that only the marker line comes from the tool and everything after it is data from the viewer. This section of the Artifact description is emitted only when its internal flag is on.

**Evidence**

`anchor_file_degraded`

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

### Rate-limit banner can say "progress saved"

The out-of-credits message can add a "progress saved" note, off unless a server enables it.

**What**

The out-of-credits message can append a "progress saved" note. It is behind a gate named `tengu_vellum_anchor` whose built-in fallback is false, so nothing in this build shows it unless a remote value turns it on.

**Evidence**

` · progress saved`

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

### Rate-limit notice can offer to continue via auto-queue

The rate-limit notice can now be handed an "auto-continue" action, shown only under other conditions.

**What**

A new callback for continuing automatically when you hit a rate limit is passed from the main REPL down through message rendering into the rate-limit notice, next to the existing handler that opens rate-limit options. Whether the notice presents it depends on conditions set elsewhere.

**Evidence**

`onRateLimitAutoQueueContinue`

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

### Retry for auto-mode classifier calls that come back unavailable

Auto mode's model-picking call can retry when unavailable, but the retry count defaults to zero.

**What**

Auto mode's model-choosing request can now be retried when it fails as unavailable, with exponential backoff starting at 2 seconds, capped at 10 seconds, plus 25% jitter. The retry count comes from remote config, is clamped to 0 through 10, and defaults to 0, so with no server value set the loop never runs and behaviour is unchanged.

**Evidence**

`unavailableOuterRetries`

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

### Usage-limit messages can be told your progress was saved

Usage-limit messages can append "progress saved", but callers only pass it when a server flag is on.

**What**

The code that writes "You've hit your …" and "You're out of usage credits…" can now append " · progress saved" to those messages. It is off in this build: callers only pass the flag when the `tengu_vellum_anchor` remote setting is on, which falls back to off, so the messages read exactly as before.

**Details**

- The suffix is a new option on the message builder rather than a separate string, so both limit messages pick it up together.

**Evidence**

`progressSavedSuffix`

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

### Workflow tool can repair arguments sent as a JSON string

Workflow arguments sent as a JSON string are now parsed instead of failing.

**What**

When the Workflow tool receives its `args` as a JSON-encoded string starting with `{` or `[`, it now parses it back into a real object or array rather than failing. The repair is controlled by the `tengu_jade_compass` flag, which falls back to on, so absent a server value the repair applies. With the flag off the same case is only recorded, and the original malformed input is passed through unchanged.

**Details**

- The detected shape is classified as `args_json_string_to_object` or `args_json_string_to_array`, each with a `_named` variant.
- When the flag is off, a `_detect_only` suffix is added to that classification.

**Evidence**

`args_json_string_to_object`

- Flag `tengu_jade_compass`: Not enough to say (read for one account on one subscription tier against v2.1.229; this account: no value returned, anonymous baseline: no value returned, compiled default: off) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Workflows
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Sandbox records whether its network filter enforces an allowlist, and always records false

The sandbox tracks whether its network filter enforces the domain allowlist, and always records false.

**What**

The sandbox now tracks, for each network filter configuration it installs, whether that configuration enforces the domain allowlist, including installs that race with startup. Every install goes through one wrapper, and the sandbox state also carries a latch for "every install since startup enforces it". The value is initialised to false and never set true anywhere in the builder, so every reader sees false and nothing in this build makes a decision from it.

**Evidence**

`builtConfigEnforcesAllowlist`, `installedFilterRequestEnforcesAllowlist`

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

### Artifact summon status is reported back to the server

Claude Code reports artifact summon status back to the artifact host with a short timeout.

**What**

New code posts a status update, reason, gesture timestamp, a per-artifact session id and a sequence number to a summon-status endpoint on the artifact host, with a 5 second timeout.

**Details**

- The session id is derived by HMAC per artifact slug, and updates are sequenced.
- Emits `artifact_summon_status_emit` telemetry for success, skip and failure, with reasons route_absent, no_auth, http_failed and request_error.
- A 404 from the endpoint backs the whole reporter off for a cooldown window.

**Evidence**

`artifact_summon_status_emit`

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

### A persistent summon session key

A persistent summon session key is generated and saved to your config, though nothing visibly reads it.

**What**

A new helper reads a summon session key from your config file, checks it against an expected pattern, and otherwise generates a fresh 32-byte random hex value, caching it for the session and writing it back to config. What reads the key is not wired up visibly in this build.

**Evidence**

`summonSidKey`

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

### Wake notifications now report which events they were armed for

Artifact wake notifications can report which events they were armed for.

**What**

A durable wake result, and the reply saying a watch is already running, can now carry the list of observed events rather than just a trigger id and a timestamp. This sits inside the artifact watch subsystem, which has its own flag checks and skips work when they are off, so whether any of it runs depends on server config.

**Evidence**

`rail: "durable_wake"`

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

### Startup repair for subagent transcripts left as symlinks

On startup, subagent transcripts stored as symlinks are converted to hard links or copies.

**What**

A startup sweep converts symlinked subagent transcript and metadata files into hard links, or copies them if that fails. It only runs when the newer session storage backend is present, and does nothing at all if the session directory sits outside the transcript store.

**Details**

- Before relinking, it checks the target is inside the transcript store, is not this session's own file, sits beside its transcript, and actually names the agent within the first 64 KiB.
- Caps how much it does per pass and starts at a random offset, so repeated runs cover different files.
- Reports counts through `tengu_adopt_relink`; copy fallbacks also increment `task_local_agent/adopt_link_fallback_copy`.

**Evidence**

`no record naming this agent in the first 64 KiB`

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

### Windows guards for runner hooks that may not be reachable

The runner now has Windows errors for hooks and orchestrator mode, but they never fire.

**What**

The self-hosted runner gained errors saying `--hooks-dir`, the matching `SELF_HOSTED_RUNNER_HOOKS_DIR` variable, and orchestrator mode are unsupported on Windows, because lifecycle hooks are POSIX executables spawned directly. Whether the guard can fire is unresolved in the code: the platform argument defaults to the literal "linux" and neither call site passes it.

**Evidence**

`--hooks-dir is not supported on Windows: lifecycle hooks currently require a Linux or macOS host`

- Area: Self-Hosted Runner
- Names: `--hooks-dir`, `SELF_HOSTED_RUNNER_HOOKS_DIR`
- Tier: Not switched on
- Useful: 1/5
- Signal: 2/5
- Present in the build but not switched on

### Windows guard for self-hosted runner hooks is present but never fires

Runner hook errors saying hooks need Linux or macOS exist but can never trigger.

**What**

New errors were added saying that lifecycle hooks and orchestrator mode need a Linux or macOS host, because hooks are POSIX executables spawned directly. In this build the check cannot trigger: both call sites pass only the feature name, and the platform argument defaults to `linux`, so the guard always returns without throwing.

**Details**

- One message reads `--hooks-dir is not supported on Windows`.
- Whether it ever fires depends on a caller passing a real platform value, which nothing in this build does.

**Evidence**

`--hooks-dir is not supported on Windows`

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

### File sync reports a machine-readable stop reason

File sync now records a machine-readable reason when it stops and exposes its current state.

**What**

The component that uploads a directory's files for a session now records a reason code next to each human-readable stop message, and exposes a state accessor reporting off, stopped, seeding or armed.

**Details**

- Reason codes include `not_opted_in`, `internal_error` and `shut_down`.
- Existing messages, such as the one for a directory whose files could not be listed, are unchanged.

**Evidence**

`File sync stopped for this session: this directory's files could not be listed`

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

## Internal Changes

### Global mutable state replaced with per-host containers

Shared process-wide state moved into per-session containers, so parallel sessions stop stepping on each other.

**What**

Session cost and usage counters, telemetry providers, auth state, prompt-cache-break state, skill health and dump-prompt state moved out of one shared mutable object into classes fetched per host through a WeakMap-backed container with an explicit reset. Several ad-hoc caches, including tool-search descriptions, fetched web URLs and agent definitions, are now class instances cleared through that container.

**Evidence**

`ToolSearchTool: cache invalidated - deferred tools changed`

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

### Device tools moved into a de-duplicating registry

Tools from the device bridge now drop duplicate names and track calls still in flight.

**What**

Tools exposed by the device bridge are held in a registry keyed by group that drops duplicate names and logs which ones it dropped, hashes the served definition list so unserved changes are detected, and tracks in-flight calls behind an idle barrier.

**Evidence**

`[deviceBridge] duplicate device tool names not announced: `

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

### Server-only remote events cover queued notifications too

The remote event lanes now accept queued notifications as well as workflow launches.

**What**

The SSE and stdin lanes now accept both `workflow_launch` and `queued_notification` as server-only payload types instead of just the first, and the events reporting a dropped payload were renamed to include the type that was rejected.

**Evidence**

`cli_stdin_server_only_type_dropped`

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

### Sessions carry a host and share one project view across forks

Sessions now carry their own host, and forked sessions share one view of the project.

**What**

Session objects gained a host field, inherited from the root session when a session is forked, and the project accessors were hoisted into a single object shared by root and forked sessions. Several UI paths read the host off the session instead of a global.

**Evidence**

`originalCwd`

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

### Storage and session handles passed explicitly instead of read from globals

Storage and session handles are now passed around explicitly instead of read from process-wide state.

**What**

Config writes, transcript appends (custom title, agent name, PR link, last prompt), session-start and setup hook dispatch, background housekeeping, plugin marketplace operations and many dialogs now receive the storage handle and session object as arguments rather than reaching for process-wide state. Hook dispatch in particular changed shape to take the session plus a storage field.

**Evidence**

`storageV5: t.storageV5`

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

### Two small-model calls force an attribution header

Two internal helper model calls now attach attribution metadata to their requests.

**What**

Two internal completion call sites now request that attribution metadata be attached to the request, alongside the existing option that skips the system prompt prefix.

**Evidence**

`forceAttributionHeader: !0`

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

### Flag settings file content is pinned and re-read

Feature-flag settings files are re-read against a pinned expected content instead of trusting one visit.

**What**

The startup settings context now resolves the expected contents of the flag settings file from a pinned value with a fallback, and new accessors read and replace that pinned value. The settings loader re-reads the flag settings file even when its path was already visited, if expected content is present.

**Evidence**

`flagSettingsFilePinnedContent`

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

### Killed SDK sessions are no longer reported as execution errors

Deliberately killed SDK sessions now report as terminated instead of showing up as execution errors.

**What**

A result that ends because the session was terminated now carries the subtype "terminated" rather than "error_during_execution", so deliberate kills stop showing up as failures.

**Evidence**

`error_during_execution`

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

### Session context is built lazily and cached per project root

Git status, CLAUDE.md and other session context are now built on demand and cached per project.

**What**

Git status, Perforce notes, CLAUDE.md, user email, attached project and the current date are now assembled on demand and cached per project root, with explicit invalidation and a subscribe hook so callers hear about rebuilds. Timing is still reported through the existing context-completed log events.

**Evidence**

`system_context_completed`

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

### Bash tool prompt always carries the full git commit instructions

Claude always gets the full git commit instructions now instead of a shortened version.

**What**

The shortened git section that used to be emitted when the commit flag was set is gone, so the full committing-with-git section is always included. The PowerShell tool description now appends the same guidance as a trailing section instead of splicing it in mid-list.

**Evidence**

`# Committing changes with git`

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

### Device binding reports attestation and counts dropped events generically

Device binding now reports whether the session was attested and counts dropped events generically.

**What**

The bound-create path now returns whether the session was attested, and the `tengu_device_bind_prepared` event reports a single `dropped_events` count in place of the old settings-events-specific one. The debug line lists dropped items by name instead of building one string by hand.

**Evidence**

`dropped_events`

- Area: Device Bridge
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5

### Per-session state replaces one process-wide globals object

Process-wide state moved into per-session objects, groundwork for isolating multiple sessions in one process.

**What**

Session and process state that used to live in a single mutable object read and written directly from anywhere is now held in per-domain classes with private fields and named accessors, so state can be isolated per session instead of being shared across the process. Nothing changes for you.

**Details**

- Fields such as sticky betas, the current prompt id, capabilities, vim state and the last API request now go through methods like `stickyBetas()`, `unlatchStickyBetas()`, `replacePromptCache1hAllowlist`, `primeSessionMessagesCache`, `notePlanFileWritten` and `recordThinkingTypeOverride`, many created lazily.
- The session record gained a `host` sub-object alongside its existing project paths.
- Dozens of functions now take a session argument rather than reaching for the global.

**Evidence**

`unlatchStickyBetas`

- Area: Session State
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5

### Tool-result mismatch diagnostics no longer send raw ids

Error reports about mismatched tool calls now scrub odd identifiers instead of sending them raw.

**What**

The report that fires when a tool call has no matching result now type-checks content blocks, coerces roles to user, assistant, system or unknown, and reports ids that do not match the expected shape as "nonconforming" instead of sending them verbatim.

**Evidence**

`tengu_tool_use_tool_result_mismatch_error`

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

### Startup telemetry records MCP transport classes and mobile project markers

Startup reporting now notes which kinds of MCP servers you use and whether it's an Xcode or Android project.

**What**

The startup event now reports which kinds of MCP server are configured (stdio, managed, localhost, private-network, public, other) and whether the repository is an Xcode or Android project, alongside the existing Docker, Nix, Bazel and lockfile probes. The MCP fields are new in this release.

**Evidence**

`has_mcp_private_network`

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

### Hooks are handed a session object instead of reading globals

Hooks now get an explicit session with id, working directory and project root instead of guessing from globals.

**What**

Hook entry points now receive an explicit session built from the session id, original working directory and project root, rather than reading ambient state. Covers ConfigChange, FileChanged, CwdChanged, Notification, DirectoryAdded, SessionEnd, StopFailure, WorktreeCreate and Remove, PreCompact and PostCompact, and Elicitation.

**Evidence**

`hook_event_name: "CwdChanged"`

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

### Caches moved from module globals onto the session

Web fetch and user context caches are now per session, so forked sessions no longer share them.

**What**

User context, the cache-breaker, the WebFetch cache and prompt-cache-break state all moved from process-wide memoized storage onto per-session state, so forked and concurrent sessions no longer share them.

**Details**

- The settings-change subscriber now clears context through the session-aware path rather than emptying a global cache.
- WebFetch results live in a `WebFetchCache` entry in tool state instead of a module-level map.
- Prompt-cache-break state sits in a per-source map with a flag recording whether hydration was attempted.

**Evidence**

`WebFetchCache`

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

### Adopted-agent and file-sync failures carry codes

Agent adoption and file-sync failures now come with specific reason codes instead of just a message.

**What**

Validation failures when adopting an agent now come with codes such as `ADOPT_WRONG_LEAF`, `ADOPT_PARENT_NOT_REGULAR` and `ADOPT_SCOPING_UNUSABLE`, and file-sync status lines pair their message with a reason: `lane_full`, `too_many_files`, `store_unreadable` or `seed_incomplete`.

**Evidence**

`ADOPT_SCOPING_MISMATCH`

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

### Background session credentials are handed back to the credential store

Background sessions now return spent credential files to the credential store instead of deleting them.

**What**

Cleanup for background and daemon sessions now asks the credential store to discard a spent credential file rather than deleting it directly, falling back to a plain unlink only when no store is present.

**Details**

- Applies in worker teardown, dispatch cleanup, and the session-record removal path.

**Evidence**

`discardSpentCredentialFile`

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

### Directory sync shutdowns carry a reason code

When file sync for a remote directory stops, it now logs a specific reason code.

**What**

When file sync for a remote directory stops, the shutdown now records a machine-readable reason such as `store_removed`, `too_many_files`, `arm_failed` or `seed_incomplete` alongside the sentence you see.

**Details**

- The user-facing text is unchanged, for example `File sync is off for this session: recording this directory's starting state failed`.

**Evidence**

`File sync is off for this session: recording this directory's starting state failed`

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

### Verbosity guidance section renamed to "communication"

The response-length guidance section was renamed, which changes that part of the prompt cache.

**What**

The system prompt block covering response length is now keyed under `communication` instead of `anti_verbosity`, which also changes that section's prompt cache identity.

**Evidence**

`p1(`communication${s}${f ? ":send_user_msg" : ""}``

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

### Daemon config is written atomically

The daemon config file is written atomically, so an interrupted write can't corrupt it.

**What**

The daemon configuration file is now written with an atomic publish rather than the default, so an interrupted write cannot leave it half-updated. Other writers in the codebase use in-place or follow-atomic modes.

**Evidence**

`publishDiscipline: "atomic"`

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

### Overlong filenames are no longer counted as expected failures

Overly long file paths now count as real file-tracking failures instead of being quietly ignored.

**What**

The classifier deciding whether a file-tracking failure is routine dropped the overlong-filename error code. Only permission and environment errors, missing files and unexpectedly absent entries are now treated as expected, so path-length failures show up as real ones.

**Evidence**

`e.telemetryCode === "UnexpectedAbsent"`

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

### Startup telemetry lists configured MCP servers

Startup reporting now lists your configured MCP servers, unless a suppression check blocks it.

**What**

The startup event now reports the configured MCP server entries as well as the existing count of connected clients. The field is null when the suppression check that governs it is true.

**Evidence**

`mcpConfigs`

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

### Status object separates busy from a running query

The spinner status now distinguishes waiting on something from actually running a query.

**What**

The spinner status now carries a separate flag that is false while waiting on something and otherwise reflects whether a query is actually running, alongside the existing busy/idle state.

**Evidence**

`working: e.isQueryActive`

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

### Skill preloads are recorded as invocations

Skills loaded ahead of time for an agent are now logged like real skill uses.

**What**

Preloading a skill for an agent now emits the same skill invocation event as a real use, tagged with execution context "preload" and trigger "agent-preload", plus the parent agent id and the length of the skill content.

**Evidence**

`agent-preload`

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

### Goals record where they came from

Goals now record whether you set them explicitly or they came from somewhere else.

**What**

Goal proposal results gained an optional `explicit` flag, active goals now store an origin, and the goal telemetry event carries that origin.

**Evidence**

`tengu_stop_hook_added`

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

### Storage keys reject direct addressing of a job's timeline

Internal storage now refuses attempts to address a background job's timeline file directly.

**What**

The key and scope validators now refuse `job` paths that point straight at a job's timeline stream, and say to use the dedicated key builder instead.

**Evidence**

`is the job's timeline stream: address it as keys.jobTimeline(jobId)`

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

### Transcript writer tracks storage generation per entry

Transcript writes now track each entry's storage format version and flush when it changes.

**What**

Session file appends now carry each entry's storage-format version, flushing a batch early when that version changes partway through a buffer, and the append records where it came from.

**Evidence**

`materializeSessionFile`

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

### Session memory viewer reports how many entries are pinned

Opening the session memory viewer now reports how many entries you have pinned.

**What**

The telemetry event fired when the session memory viewer opens now includes a count of pinned entries alongside the existing recalled and read counts.

**Evidence**

`pinned_count`

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

### Queued goals record where they came from

Queued goals now remember where they came from, defaulting to you.

**What**

When a goal is queued, Claude Code now stores its origin alongside the condition and reads it back once, defaulting to "user". The loops dialog tags its own goals explicitly.

**Evidence**

`queuedGoalOrigin`

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

### Job timelines get their own storage address, and config copies are validated

Background job timelines get their own storage namespace and config backups must be stamped and named.

**What**

Background job timeline streams are now addressed through a dedicated namespace key rather than by file path, with an error that redirects anyone addressing the stream file directly. Recovery copies of the global config must name one of the recognised copy kinds and carry a stamp.

**Evidence**

`must be one of the global-config copy kinds (`

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

### Telemetry when the goal proposal tool is reachable

Claude Code now logs once per session whether its goal-proposal tool was available to it.

**What**

Claude Code now records once per session whether its goal-proposal tool is available, along with the setting value that decided it. The tool behaves exactly as before.

**Details**

- A latch ensures at most one event per session no matter how often availability is checked.

**Evidence**

`tengu_goal_proposal_available`

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

### Diff and history-search failures are reported separately

Diff and history-search problems are now reported as distinct failures rather than one generic error.

**What**

The diff hook now distinguishes a failed git diff, failed hunk parsing and a thrown error, and the history picker separates a scan it cut short from a real failure, returning a failed status instead of throwing.

**Details**

- Diff events: git_diff_failed, git_hunks_failed, git_diff_threw
- Partial scan markers: `picker_scan_interrupted` and text_limit_reached

**Evidence**

`picker_scan_interrupted`

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

### Diagnostics comparison rewritten to use derived keys

Working out which editor diagnostics are new got much faster on large files.

**What**

The tracker that decides which editor diagnostics are new now compares sets of derived keys instead of every field of every pair, turning the comparison from quadratic to linear. The old equality helper is gone.

**Evidence**

`areDiagnosticArraysEqual`

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

### Concurrency docs say available CPUs

Parallel agent limits are now described as capped by available CPUs, not total cores.

**What**

The explanation of how many agents run at once now states the cap as `min(16, available CPUs - 2)` rather than referring to cpu cores.

**Evidence**

`min(16, available CPUs - 2)`

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

### Skills path resolution deferred to first use

Skill directory lookups happen on first use, shaving filesystem work off startup.

**What**

The lookup that resolves skill and plugin directory prefixes, including the staging subdirectory, now runs on first use rather than at module load, moving its filesystem work out of startup.

**Evidence**

`syncOwnedPrefixes`

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

### Version bumped to 2.1.229

Version is now 2.1.229, which is what /status and update checks report.

**What**

Build metadata moved from 2.1.228 to 2.1.229 with a new timestamp and commit, which is what `/status`, update checks and telemetry report.

**Evidence**

`BUILD_TIME: "2026-08-12T17:40:38Z"`

- Area: Internals
- Names: `/status`
- Tier: Under the hood
- Useful: 2/5
- Signal: 1/5

### Oversized requests get their own log line

Requests rejected for being too large now get their own clearly labelled error log line.

**What**

Server rejections for a request that is too large are now logged at error level under their own branch, next to the existing content-filtered and credential-error cases, so they are distinguishable in logs.

**Evidence**

`API request_too_large: `

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

### Project-kind telemetry learns Android and CUDA

Project detection now recognises Android and CUDA repos and stops mislabelling gradle wrappers as Windows scripts.

**What**

Repository detection now reports `android` for projects with an AndroidManifest.xml or matching gradle build files, and `cuda` for .cu/.cuh sources or matching build files. Repos are no longer labelled `windows_scripts` just because they ship `gradlew.bat`, `gradlew.cmd`, `mvnw.bat` or `mvnw.cmd`.

**Evidence**

`windows_scripts`

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

### Goal-set events gain an origin, one-shot warnings share a registry

Goal events record their origin, and one-time warnings now go through a shared registry.

**What**

`goal_set` telemetry now records where the goal came from, such as "restored". Two module-level "log this once" booleans, for the provider route event and the max-retries clamp warning, were replaced by a shared registry keyed by name.

**Evidence**

`max_retries_clamp_warning`

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

### Startup telemetry detects Android projects and pinned toolchains

Startup reporting now notes Android projects and whether your toolchain is pinned.

**What**

The repo shape reported at startup now includes whether the project is an Android one and whether it pins a toolchain, which shapes what Anthropic sees about the kinds of codebases in use.

**Details**

- Android detection looks for `AndroidManifest.xml` or Gradle files declaring `com.android.*` plugins.
- Toolchain detection uses new patterns for Android Gradle plugins and CUDA declarations in CMake and conda environment files.

**Evidence**

`has_android_project`

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

### Repo telemetry notes Xcode and Android projects

Project reporting now flags Xcode and Android projects alongside registry and toolchain details.

**What**

The payload describing your project now flags whether it contains an Xcode or Android project, alongside the package registry and toolchain fields it already reported.

**Evidence**

`has_xcode_project`

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

### Terminal capability probes moved onto the session

What Claude Code learns about your terminal is now remembered per session, not process-wide.

**What**

Facts Claude Code learns about your terminal, such as whether iTerm2 commands and synchronized output are supported, are now stored per session instead of in one process-wide place. No visible change.

**Evidence**

`recordIt2Command`

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

### Byte-budget string truncation no longer allocates a Buffer

Trimming text to a byte limit got cheaper, with the same results.

**What**

The helper that trims a string to a byte budget now returns early when three bytes per character would still fit, and otherwise encodes and decodes with TextEncoder/TextDecoder, stripping any trailing replacement character left by a cut mid-sequence. Same results, less allocation.

**Evidence**

`new TextDecoder().decode(r.subarray(0, t)).replace(/\uFFFD+$/, "")`

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

### Two animals swapped in the session name word list

New sessions can now be named after a bird or a mollusc instead of two former animals.

**What**

The word list used to generate memorable session names replaced one mammal and one cetacean with a bird and a mollusc. Only affects names generated from now on.

**Evidence**

`nautilus`

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

## Removed

### Two bundled skills dropped

The bundled code-walkthrough and pr-explainer skills are gone; other built-in skills stay.

**What**

The built-in skills "code-walkthrough" and "pr-explainer" are gone and neither name appears anywhere in this build. Other bundled skills including code review, PR review and the cowork plugin are unchanged.

**Evidence**

`pr-explainer`

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

### `git checkout -b` is no longer pre-allowed under PowerShell

On Windows PowerShell, creating a branch during commit or PR flows now asks you for permission.

**What**

The built-in commit, push and pull-request prompt commands used to ship an allow rule for `PowerShell(git checkout -b *)` alongside the Bash one. It is now filtered out, so creating a branch that way prompts for permission on Windows PowerShell.

**Evidence**

`PowerShell(git checkout -b *)`

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