# Claude Code v2.1.233

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

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

Thirty-nine entries sit in this build without being switched on. Todo tools are now decided per model, and for opus 4/8, sonnet 5, fable 5 and mythos 5 they are off unless you set `CLAUDE_CODE_ENABLE_TODO_TOOLS=true` or a server turns on the matching remote flag. A whole `poll_event` delivery path for pushing external events into a running session is built and completely unreachable, since the check that would enable it is a function that always returns false; requests are refused with "poll-event delivery is not enabled for this session." Plugin ratings can now be posted to an organization store, but the remote setting that permits it defaults to disabled, so nothing is sent. The skill-proposal tool needs `CLAUDE_CODE_SKILL_PROPOSALS`, and a `bypassPermissionsBlockedByHost` input is now threaded through the permission-mode resolver with nothing yet reading it back out.

Seventy-eight entries are usable today. Permission prompts left unanswered for six seconds now fire your Notification hook with `notificationType: "permission_prompt"` and a message naming the tool, both in the terminal and behind an SDK or `stream-json` host; set `CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS` to stop it. On Linux and WSL, shells from the Bash and PowerShell tools can be capped by setting `CLAUDE_CODE_TOOL_MEMORY_LIMIT` to a size such as `2g`, or `none` to disable. The remaining-token countdown, which prints `<total_tokens>N tokens left</total_tokens>` in the system prompt and after tool results, is now on by default. `CLAUDE_CODE_WEBFETCH_CACHE_TTL_MS` sets how long fetched pages stay cached, and screen-reader mode now defers redraws by 50ms, adjustable with `CLAUDE_AX_PREPARK_MS`.

The Windows Cygwin-symlink pass over Bash permissions has been removed entirely, so previously approved shell commands and redirecting commands should stop producing unexplained approval prompts under Windows and Git Bash. The Bash permission path also no longer inspects `<` input redirection targets, and `sort` output-file options are no longer pulled out as paths. MCP servers now track whether a re-authentication is already in flight, so several tool calls failing together share one OAuth re-auth instead of starting one each. Streamed server-sent events are no longer discarded when the connection is closing, and Windows device-namespace paths such as `\??\...` now count as network paths everywhere the check is made, blocking them for @-mention attachments and Chrome uploads.

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

### Todo tools switched off for several models

Todo tools are now off for several models unless you set CLAUDE_CODE_ENABLE_TODO_TOOLS=true.

**What**

A new check decides per model whether the todo tools are offered. For opus 4/8, sonnet 5, fable 5 and mythos 5 they are off unless `CLAUDE_CODE_ENABLE_TODO_TOOLS=true` or the `tengu_rosy_wren` gate is on; the gate falls back to off when the server has no value. Any other or unknown model keeps them on.

**Details**

- The same check now also guards the todo reminder attachment and the deferred todo tool, so those follow the tools.

**Evidence**

`tengu_rosy_wren`

- Area: Tools
- Names: `CLAUDE_CODE_ENABLE_TODO_TOOLS`
- Tier: Use it now
- Useful: 4/5
- Signal: 5/5

### New hidden flag --watch-artifact

A hidden flag lets a session watch an artifact for new versions and comments, with an option to disarm auto-reply.

**What**

A session can now watch a Claude artifact by id or claude.ai URL and be notified when new versions and comments arrive. `--watch-artifact-no-autoreact` starts the same watch with auto-reply disarmed. Both are registered hidden, so they do not show up in `--help`. Startup refuses them in non-interactive, print, `--sdk-url`, `--init-only` and redirected sessions, and in sessions attached to a remote environment, each with its own error text.

**Details**

- A malformed value produces an artifact-id/URL error; a URL belonging to a different environment is rejected against the one you are signed in to.
- Neither flag string appears in the previous build.

**Usage**

`claude --watch-artifact https://claude.ai/...` **Evidence** `Error: --watch-artifact is not available in sessions attached to a remote environment \u2014 run it in a plain local session`

- Area: Artifacts
- Names: `--watch-artifact`, `--watch-artifact-no-autoreact`
- Tier: Use it now
- Useful: 3/5
- Signal: 5/5

### Plugin eval tells the model the env var that turns it on

Setting CLAUDE_CODE_WALNUT_SPIRE=1 turns on the early-access plugin eval command.

**What**

When the early-access `claude plugin eval` command is enabled, Claude is now told the enabling variable is `CLAUDE_CODE_WALNUT_SPIRE=1`, settable in your shell or in the `env` block of user or managed settings, for machines outside the per-organization rollout. When it is not enabled, Claude is told to call it early access and never guess a variable name.

**Details**

- The gate itself is unchanged: the remote flag `tengu_walnut_spire`, which defaults to off, or the environment variable.

**Evidence**

`CLAUDE_CODE_WALNUT_SPIRE=1`

- Flag `tengu_walnut_spire`: Off in both readings (read for one account on one subscription tier against v2.1.233; 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: Plugin Eval
- Names: `CLAUDE_CODE_WALNUT_SPIRE`, `claude plugin eval`
- Tier: Use it now
- Useful: 3/5
- Signal: 5/5

### `--watch-artifact` flag to follow a claude.ai artifact from a session

A new --watch-artifact flag follows a claude.ai artifact for new versions and comments, but needs account access.

**What**

A new startup flag takes a claude.ai artifact id or URL and watches it for new versions and comments during the session, telling Claude which artifact is the current one of interest. It needs the `tengu_slate_lantern` gate (fallback off); without it you get "Artifact watching is not available for this account yet."

**Details**

- A hidden `--watch-artifact-no-autoreact` variant arms the watch without automatic comment replies.
- Both flags are hidden from `claude --help`, though they parse on every build, and both were added to the lists of flags that take a value and get forwarded.
- Rejected in non-interactive and print sessions, and in sessions attached to a remote environment; the id is validated against the signed-in environment.
- Arming prints a feedback line built around "Watching artifact".

**Usage**

`claude --watch-artifact https://claude.ai/public/artifacts/<id>` **Evidence** `Watch a Claude artifact (id or URL) in this session and hear about new versions and comments`

- Flag `tengu_slate_lantern`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.233; 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: `--watch-artifact`, `--watch-artifact-no-autoreact`
- Tier: Not switched on
- Useful: 3/5
- Signal: 5/5
- Present in the build but not switched on

### Bash and PowerShell tools can run under a memory limit on Linux

On Linux you can cap memory for shell commands so a runaway process cannot eat your machine.

**What**

On Linux and WSL, shells spawned by the Bash and PowerShell tools can now be placed in a `claude-code-bash` memory cgroup so a runaway command hits a cap instead of your machine. Set `CLAUDE_CODE_TOOL_MEMORY_LIMIT` to a size such as `2g` to pick the cap, or to `none` to switch it off. With no explicit size it only engages when the remote flag `tengu_tool_memory_cgroup` is on, which is off by default in this build.

**Details**

- Writes `memory.max` on cgroup v2 and `memory.limit_in_bytes` on v1.
- With no override the limit is derived from total system RAM.
- Other platforms return early and spawn normally.
- Reports the outcome as `tengu_tool_cgroup` with status enabled or disabled, and logs a `tool cgroup: disabled (` line with the reason.

**Usage**

`CLAUDE_CODE_TOOL_MEMORY_LIMIT=2g claude` **Evidence** `tengu_tool_cgroup`, `tool cgroup: disabled (`

- Flag `tengu_tool_memory_cgroup`: Off in both readings (read for one account on one subscription tier against v2.1.233; 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: Bash Tool
- Names: `CLAUDE_CODE_TOOL_MEMORY_LIMIT`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Task tools now follow the todo-tools flag rather than an org denylist

Task tools now turn on via the tasks and todo-tools env vars instead of an organization denylist.

**What**

TaskCreate, TaskUpdate and TaskList used to be available unless your organization sat on a denylist. They now require both that tasks are not disabled by `CLAUDE_CODE_ENABLE_TASKS=false` and that todo tools are on, which happens for certain session kinds, when `CLAUDE_CODE_ENABLE_TODO_TOOLS` is true, or when the `tengu_rosy_wren` flag is on. That flag is off by default in this build.

**Evidence**

`CLAUDE_CODE_ENABLE_TODO_TOOLS`

- Area: Background Tasks
- Names: `CLAUDE_CODE_ENABLE_TASKS`, `CLAUDE_CODE_ENABLE_TODO_TOOLS`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### File sync to cloud sessions now holds back credential-looking files

Files that look like secrets, or that your Read rules deny, are held back from cloud sandboxes with a warning.

**What**

When files are carried to a remote sandbox, a new per-file check withholds anything that looks like a secret or that your own Read permission rules deny. You get a warning naming what was skipped and why.

**Details**

- Withheld by directory name: `.ssh`, `.aws`, `.azure`, `.gnupg`, `.kube`, `.docker`, and pairs such as `.config/gh` and `.claude/settings.local.json`.
- Withheld by filename or extension: `.git-credentials`, `.npmrc`, `.pgpass`, `.zsh_history`, `secrets.yaml`, `_ed25519`, and `pem`, `p12`, `pfx`, `jks` keys and certs.
- Also withholds files covered by your Read deny or ask rules.
- Warnings end in "not carried by file sync" or "not uploaded with this directory", with reasons recorded as sensitive, read_denied or rules_unreadable.
- If a settings file fails to parse, the message says your Read rules are unknown and points at `/status`.
- Telemetry gains `skipped_sensitive` and `skipped_read_denied` counts.
- Only reached on the cloud and directory-sync path, so local sessions are unaffected.

**Evidence**

`a settings file has errors, so your Read rules are unknown (see /status)`

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

### Resume-from-summary and session-cost dialogs join the shared dialog system

Long sessions can offer to resume from a summary, and the $5 cost notice got a proper dialog.

**What**

Two dialogs whose components already existed are now registered properly: a choice between resuming a long session from a summary or in full, and an acknowledgement of a $5 session cost. Both now render through the unified dialog system and mark the session busy while open. The resume dialog offers `Resume from summary (recommended)`, resuming as-is, or a persisted "Don't ask me again", and it only appears past session-age and estimated-token thresholds.

**Details**

- The resume choice is reported along with session age, message count and estimated token count.

**Evidence**

`choose: resume from summary or full session`

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

### Plugin ratings can be recorded against an organization store, off by default

Thumbs-up and thumbs-down ratings for plugins can be posted to your organization, off by default.

**What**

New code posts plugin-store appearances and thumbs ratings (`bad`, `fine`, `good`) to `/api/oauth/organizations/:orgUUID/plugin_ratings`, records dismissed and unsure outcomes, and withdraws a stored rating with a DELETE. It is gated on the remote setting `tengu_plugin_ratings_store_config`, whose built-in fallback is disabled, so nothing is sent unless a server turns it on.

**Details**

- Also refuses when `CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY` is set, when the organization is not pinned, when the marketplace is unknown, or for non-first-party authentication.
- Telemetry is emitted under `cli_plugin_rating_store`.

**Evidence**

`tengu_plugin_ratings_store_config`

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

### Queue for external events delivered to the model

Outside events can be queued and handed to Claude in batches, with size and count caps.

**What**

A new queued-command mode lets external events be queued and handed to the model as a `poll_events` attachment carrying the event bodies, their kinds and a remaining-wake count. Each event body is capped at 49152 bytes and the queue at 1000 events, with bodies validated against their declared kind and identical ones deduplicated. Events marked as waking the session are delivered next; others are deferred. A `Poll` tool name constant is new here, and nothing in this build shows what exposes that tool.

**Details**

- Over-sized events are refused with "poll event rejected: envelope too large".
- Each queued event carries promises that settle on delivery or on drop; clearing the command queue settles all pending ones as dropped.
- The filters that preserve queued commands in history now preserve poll-event attachments too.

**Evidence**

`poll event rejected: envelope too large`

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

### `CLAUDE_CODE_SKILL_PROPOSALS` turns on the skill-proposal tool

An env var turns on the tool that proposes new skills; otherwise it stays off in this build.

**What**

Setting `CLAUDE_CODE_SKILL_PROPOSALS` enables the tool that proposes new skills on its own. Otherwise it needs the `tengu_propose_skills` flag, which is off by default in this build, plus a remote environment. The variable is also passed down to child and plugin processes.

**Details**

- `CLAUDE_CODE_EVAL_INTERVIEW_SESSION` is now stripped from the environment given to spawned processes, alongside the other session markers.

**Evidence**

`CLAUDE_CODE_SKILL_PROPOSALS`

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

### Hidden `claude plugin sandbox install` and `status` commands

Hidden sandbox install and status commands set up the Windows sandbox user with one UAC prompt.

**What**

A hidden `sandbox` command group adds `install`, which sets up the Windows sandbox user and network filters and self-elevates through a single UAC prompt, and `status`, which prints availability and install state as JSON. Both are registered on every build but refuse to run outside native Windows, when the Windows sandbox is not enabled, or when policy excludes the platform.

**Details**

- `install` prints a JSON `{status, message}` result and exits 0 only when status is `"ok"`.
- `status` prints `{available, installed, policyLocked, reasons}`; `policyLocked` comes from a check for sandbox settings locked by administrator policy.

**Usage**

`claude plugin sandbox status claude plugin sandbox install` **Evidence** `Install the Windows sandbox user and network filters. Self-elevates (one UAC prompt). Prints a JSON {status, message} result and exits 0 only when status is "ok".`, `Print Windows sandbox availability and install state as JSON {available, installed, policyLocked, reasons}.`

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

### The built-in guide agent can answer questions about plugin eval and /skill-doctor offline

The built-in guide agent can now answer about plugin eval and /skill-doctor from bundled reference text.

**What**

The claude-code-guide agent now ships with a reference document about `claude plugin eval`, `plugin eval init` and the `/skill-doctor` report bundled into its prompt, so it answers from that instead of guessing a docs URL that does not exist yet. The reference states whether each feature is on in your session, and when they are off it tells the agent to say they are early access rather than nonexistent.

**Details**

- The system prompt ends with a block headed "# Plugin eval and /skill-doctor (embedded offline reference)", and the prompt body gained a fifth expertise domain covering both features.
- `/skill-doctor` reports are described as available only when `tengu_lantern_prism` or `CLAUDE_CODE_LANTERN_PRISM` is on; plugin eval only when `tengu_walnut_spire` or `CLAUDE_CODE_WALNUT_SPIRE` is. Both remote flags fall back to false.
- The bundle adds `references/plugin-eval-quickref.md` and `references/plugin-eval.md`, covering flags, the JSON payload, sandbox and CI use.
- The guidance explicitly tells the agent not to guess the name of an enablement variable.

**Evidence**

`# Plugin eval and /skill-doctor (embedded offline reference)`, `# Plugin eval and \`/skill-doctor\` — quick reference`, `"references/plugin-eval-quickref.md"`

- Flag `tengu_lantern_prism`: Off in both readings (read for one account on one subscription tier against v2.1.233; 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.
- Flag `tengu_walnut_spire`: Off in both readings (read for one account on one subscription tier against v2.1.233; 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: Agents
- Names: `/skill-doctor`, `claude plugin eval`
- Tier: You'll notice
- Useful: 3/5
- Signal: 4/5

### Programmatic message submission carries new per-message flags

SDK-submitted messages can carry four new per-message flags for attachments, hooks, polling and skill permissions.

**What**

Messages submitted through the SDK path now forward four additional per-message options alongside the existing origin and query settings: `skipAttachments`, `skipSubmissionHooks`, `pollEventDelivery` and `skipSkillPermissionReset`. Marking a turn as coming from a verified Slack human now requires the message to have a uuid.

**Evidence**

`skipSubmissionHooks`

- Area: SDK
- Names: `skipAttachments`, `skipSubmissionHooks`, `pollEventDelivery`, `skipSkillPermissionReset`
- Tier: Under the hood
- Useful: 3/5
- Signal: 4/5

### Notification hook fires when a permission prompt is left unanswered

Under SDK or stream-json hosts, a pending permission request triggers your Notification hook after six seconds.

**What**

When Claude Code runs behind an SDK or `stream-json` host and asks that host for tool permission, a 6-second timer now fires your Notification hook with `notificationType: "permission_prompt"` and the message `Claude needs your permission to use <tool>`. It only fires if the request is still pending at 6 seconds, so answering promptly produces no notification. Set `CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS` to turn it off.

**Details**

- Covers both regular tool-permission requests and the WebFetch domain-approval request.
- The timer is cancelled the moment the request resolves or rejects, in both the success and failure paths.
- Neither the helper nor the environment variable existed in the previous build.

**Evidence**

`` `Claude needs your permission to use ${r}` ``

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

### Permission prompts fire a notification hook if left unanswered

Your notification hook now fires when a permission prompt sits unanswered for six seconds; one env var disables it.

**What**

When Claude Code asks permission to use a tool, a 6-second timer starts; if the prompt is still waiting, a notification hook fires with notificationType `permission_prompt` and a message naming the tool. The timer is cancelled the moment you answer. Set `CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS` to turn it off; it is on by default.

**Details**

- The timer is unref'd, so it never keeps the process alive on its own.
- The variable is newly registered in the environment-variable schema as a boolean.

**Evidence**

`CLAUDE_CODE_DISABLE_PERMISSION_PROMPT_NOTIFY_HOOKS`

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

### Screen-reader mode pauses before redrawing

Screen-reader mode waits briefly before redrawing so bursts are announced once; tune the delay with an env var.

**What**

In screen-reader mode the terminal is no longer repainted the instant output changes. The cursor is parked and the redraw deferred by a short timer, so a burst of frames is announced once rather than many times. The delay is 50ms by default and settable with `CLAUDE_AX_PREPARK_MS`, capped at 5000ms.

**Details**

- While the timer is outstanding, further screen-reader renders are skipped and coalesced into the pending redraw.
- If the app is exiting the timer is cleared first so the final frame is written immediately; it is also cleared on unmount and whenever the render state is reset.
- This sits on top of the existing startup-quiet delay, set by `CLAUDE_AX_STARTUP_QUIET_MS`, which is likewise clamped to a built-in maximum.
- None of this runs outside screen-reader mode.

**Evidence**

`CLAUDE_AX_PREPARK_MS`, `srPreParkTimer`

- Area: Accessibility
- Names: `CLAUDE_AX_PREPARK_MS`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### Remaining-token countdown is now on by default

Claude now gets a running count of tokens left by default, re-anchored on each of your prompts.

**What**

Claude Code emits a `<total_tokens>N tokens left</total_tokens>` block in the system prompt and after each tool result, counting down from the configured budget. This used to be off unless enabled; the built-in fallback is now `padded-countdown`. The companion behaviour of re-anchoring the countdown on each regular user prompt also flipped to on by default. Both can still be set explicitly, and remote config can override either.

**Details**

- Resolution order: env var `CLAUDE_CODE_TOTAL_TOKENS_REMINDER`, then the `totalTokensReminder` setting, then the server-side value whose in-source fallback moved from "off" to "padded-countdown".
- The budget comes from `totalTokensReminderBudget`.
- Re-anchoring uses `CLAUDE_CODE_TOTAL_TOKENS_REMINDER_AFTER_USER_TURN`, then `totalTokensReminderAfterUserTurn`, then the server value whose fallback flipped from false to true.
- Suppressed entirely by `CLAUDE_CODE_DISABLE_ATTACHMENTS` or `CLAUDE_CODE_SIMPLE`.
- The source only fixes the fallback; a server-side value still wins.

**Evidence**

`is on \u2014 task-budget semantics). Defaults to padded-countdown. `, `"@internal Emit a <total_tokens>N tokens left</total_tokens> block in the system prompt, after each tool result`, `tengu_lapis_anchor_user_turn`

- Flag `tengu_lapis_anchor_user_turn`: Off by default, switched on for this account (read for one account on one subscription tier against v2.1.233; this account: on, anonymous baseline: on, compiled default: off) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Context Management
- Tier: You'll notice
- Useful: 4/5
- Signal: 3/5

### A "poll_event" delivery path exists but is switched off in this build

A way to push outside events into a live session is built but completely switched off.

**What**

A new way to push events into a running session, as a `poll-event` command-queue mode and a matching control-protocol request, is fully built and completely unreachable: the predicate that would enable it is a function whose entire body returns false, with no flag or environment variable behind it. Incoming requests are refused with "poll-event delivery is not enabled for this session."

**Details**

- Queue APIs are `enqueuePollEvent`, `drainPollEventChunk` and `countPendingWakePollEvents`, with a 1000-event queue cap and a 49152-byte cap per envelope.
- Each event is validated against its declared kind; a `poll_event_delivery` telemetry event records reasons including `envelope_too_large`, `validation_failed`, `kind_mismatch`, `queue_cap` and `discarded`.
- Delivered poll events skip attachments, skip submission hooks and skip the skill permission reset that a normal user turn performs.
- None of this exists in the previous build.

**Evidence**

`poll-event delivery is not enabled for this session`

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

### New inbound relay origin treated as a human turn

Relayed messages from a new inbound source count as a human turn by default.

**What**

Relay message classification gained an origin named "hearth_human", counted as a human turn only when a gate allows it. The gate reads the remote flag `tengu_cuddly_willow`, which falls back to true, so with no remote value set the origin is honoured.

**Details**

- Two telemetry events accompany it: "bridge_projects_human_origin" when the origin is accepted, and the same name logged as rejected with reason "disabled_by_flag" when the gate is off.
- Neither the origin nor the gate name existed in the previous build.

**Evidence**

`bridge_projects_human_origin`

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

### Artifact watch priming, failure text and resume stand-down

When artifact watching turns on, Claude is told to re-read the artifact before editing it.

**What**

When a watch is armed, the session injects a message telling the model it is watching that artifact and must re-read it before editing or republishing, and prints a notification. Each way arming can fail now has its own message.

**Details**

- Failure reasons with distinct text: artifact not found, no subscription token, boot failed, sandbox egress denied, watch cap reached, websocket open error, and remote sessions.
- Resuming a transcript that started with a watch injects an explicit notice that the watch does not carry into the new session.
- Startup outcomes are logged as `artifact_watch_startup` with reasons including `fetch_gate` and `arm_error`.

**Evidence**

`--watch-artifact is not available in remote sessions.`, `The watch started with claude --watch-artifact earlier in this transcript does not carry into this session`

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

### Automatic comment replies stand down if someone else answers first

Claude withholds an automatic artifact comment reply if someone else answered while it was writing.

**What**

Before posting an automatic reply to an artifact comment, Claude Code re-reads the thread and, if a reply it does not recognise as its own arrived while it was composing, withholds its own reply and reports that the comment was answered while the reply was being written. Off unless `CLAUDE_CODE_ARTIFACT_COMMENTS_AUTOREACT` is set; otherwise it follows the `tengu_sorrel_trellis` flag, which is off by default.

**Details**

- Produces a distinct outcome, `answered_post_time`, with its own telemetry values.
- The notice shown to Claude explains why the reply was held back.

**Evidence**

`answered_post_time`

- Flag `tengu_sorrel_trellis`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.233; 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: `CLAUDE_CODE_ARTIFACT_COMMENTS_AUTOREACT`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Groundwork for a host that can block bypass-permissions mode

Permission resolution can now be told the host forbids bypass mode, but nothing reads it.

**What**

The permission-mode resolver and its subprocess variant now accept a `bypassPermissionsBlockedByHost` input, carried alongside the existing skip-permissions and non-interactive flags. Nothing in this build reads the value back out, so it is plumbing ahead of the behaviour it is named for.

**Evidence**

`bypassPermissionsBlockedByHost`

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

### Turning off artifact auto-react now survives a restart

Turning off artifact auto-replies now sticks across a background session restart.

**What**

A hidden `--watch-artifact-no-autoreact <artifact>` option was added and is accepted by the argument sanitizers alongside `--watch-artifact`. When you disarm auto-react in a session, the launch arguments are rewritten to swap in the new flag with the same artifact id, and background-job respawn flags are updated, so a restarted background session stays disarmed.

**Details**

- The flag is hidden from `--help`.
- Auto-react itself needs `CLAUDE_CODE_ARTIFACT_COMMENTS_AUTOREACT` or the remote flag `tengu_sorrel_trellis`, which falls back to false.

**Evidence**

`--watch-artifact-no-autoreact`

- Flag `tengu_sorrel_trellis`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.233; 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: `--watch-artifact-no-autoreact`, `--watch-artifact`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Relay messages from `hearth_human` count as coming from a person

Messages arriving over a new relay origin now count as coming from a person rather than another Claude.

**What**

Messages arriving through the new `hearth_human` relay origin are now treated as sent by a human, joining `slack_human`, `teams_human` and `owner_relay`. That decides idle-activity notes, whether a message is recorded as from a human or a peer, and the verified-human turn marking in remote-worker and bridge sessions. The extra origin only counts when the `tengu_cuddly_willow` flag is on; its built-in fallback is on, so absent a server override this path is live.

**Details**

- The flag is threaded through the classification helpers as an extra argument, so each caller decides whether to accept the new origin.
- The previous build defined `hearth_human` but no check consulted it.

**Evidence**

`hearth_human`, `tengu_cuddly_willow`

- Flag `tengu_cuddly_willow`: Off by default, switched on for this account (read for one account on one subscription tier against v2.1.233; this account: on, anonymous baseline: on, compiled default: off) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Sessions
- Tier: Under the hood
- Useful: 2/5
- Signal: 4/5

### Two more variables recognised when building subprocess environments

Two more Claude-owned variables are handled when building environments for subprocesses.

**What**

`CLAUDE_CODE_SKILL_PROPOSALS` and `CLAUDE_CODE_EVAL_INTERVIEW_SESSION` joined the list of Claude-owned variables handled when Claude Code builds the environment for a subprocess, with the former also added to the shorter second list next to `CLAUDE_CODE_COWORK_FRAME_ARTIFACTS`.

**Evidence**

`CLAUDE_CODE_EVAL_INTERVIEW_SESSION`

- Area: Environment
- Names: `CLAUDE_CODE_SKILL_PROPOSALS`, `CLAUDE_CODE_EVAL_INTERVIEW_SESSION`, `CLAUDE_CODE_EVAL_`
- Tier: Under the hood
- Useful: 2/5
- Signal: 4/5

### Env var to drop permission prompts left unanswered when a session resumes

Set an env var so resumed print-mode sessions drop unanswered permission requests instead of cancelling and re-asking.

**What**

Set `CLAUDE_CODE_RETIRE_UNANSWERED_PARKED_PERMISSION` and a resumed print-mode session that finds no saved answer to a permission request it was waiting on will drop the request and move on, instead of cancelling and asking again.

**Details**

- Applies only in print mode, and only when the resumed transcript has a permission request parked with no recorded response.
- Without the variable, behaviour is unchanged: the request is cancelled and the interrupted turn is re-run so the prompt appears again.
- With it set, the interrupted turn is skipped entirely and telemetry records the outcome as "retired_unanswered".

**Usage**

`CLAUDE_CODE_RETIRE_UNANSWERED_PARKED_PERMISSION=1 claude -p --resume <session-id> "continue"` **Evidence** `CLAUDE_CODE_RETIRE_UNANSWERED_PARKED_PERMISSION`

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

### Gateway upstreams can forward the signed-in user's identity

Self-hosted gateway upstreams can forward your identity and email, and config refuses it for Anthropic hosts.

**What**

Anthropic upstreams in gateway config accept a new `forward_user_identity` option. With it set, requests carry the signed-in developer's identity-provider subject and email as `x-litellm-end-user-id`, `x-claude-gateway-user-id` and `x-claude-gateway-user-email`, for a proxy you run yourself. Config validation refuses the option when the upstream `base_url` host is anthropic.com, any subdomain of it, or the AWS external-Anthropic pattern, so user emails never reach Anthropic.

**Details**

- When an `oidc` block is present alongside it, a warning names exactly which fields get forwarded.
- Off unless you add the option to an upstream.

**Evidence**

`forward_user_identity is for identifying users to a proxy you run at base_url; it is refused for `

- Area: Gateway
- Names: `forward_user_identity`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### /doctor now checks skills for broken frontmatter

/doctor now spots skills whose frontmatter fails to parse and offers to repair it.

**What**

The setup-health check run by `/doctor` scans project and user `SKILL.md` files for a YAML frontmatter block that fails to parse. Such a skill still loads, but every field is dropped: the name falls back to the directory name, the description to the first line of the body, and `allowed-tools`, `model` and `disable-model-invocation` stop applying with no warning at all. The check offers the frontmatter repair.

**Details**

- Points at `claude plugin validate <path>` for checking many skills at once.
- The skill's own description was updated to advertise the new check.

**Evidence**

`**Malformed skill frontmatter.**`

- Area: Skills
- Names: `/doctor`
- Tier: Use it now
- Useful: 4/5
- Signal: 2/5

### `/plugin validate` works on directories without a plugin manifest

You can point /plugin validate at any folder and it checks the skills, agents and commands inside.

**What**

Validation no longer requires a plugin manifest. Point it at a path with no plugin.json or marketplace.json and it validates the skills, agents and commands underneath instead, printing a `Validating components in:` header rather than the manifest header.

**Details**

- A directory named skills, agents or commands validates its own contents; `.claude` validates the components inside it; any other directory validates those under `.claude`, plus top-level ones when the directory looks like a plugin checkout.
- Unexpected-error output is now truncated to 200 characters.

**Evidence**

`Validating components in: `

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

### WebFetch cache lifetime is now configurable

You can set how long WebFetch keeps pages cached, in milliseconds.

**What**

`CLAUDE_CODE_WEBFETCH_CACHE_TTL_MS` sets how long fetched pages stay cached, in milliseconds. Without it the built-in default applies. The value is read once on first use and also drives the cache duration shown in WebFetch output.

**Details**

- Validated as a digits-only integer with a minimum of 1.

**Usage**

`CLAUDE_CODE_WEBFETCH_CACHE_TTL_MS=900000 claude` **Evidence** `CLAUDE_CODE_WEBFETCH_CACHE_TTL_MS`

- Area: WebFetch
- Names: `CLAUDE_CODE_WEBFETCH_CACHE_TTL_MS`
- Tier: Use it now
- Useful: 4/5
- Signal: 2/5

### MCP servers no longer re-authenticate several times at once

Failing MCP calls now share one login retry instead of firing several at once.

**What**

The MCP client now tracks whether a re-authentication is already running for each server, and both the tool-call and connect retry paths check it before treating a closed connection as grounds for another attempt. Several calls failing together now share one OAuth re-auth.

**Evidence**

`reauthInFlight = new Map();`

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

### The Windows Cygwin-symlink permission pass is gone

Windows and Git Bash users should stop seeing unexplained approval prompts on redirects and approved commands.

**What**

The extra Windows and Git Bash pass over Bash permissions has been removed entirely, so redirecting commands and previously approved shell commands should stop producing unexplained approval prompts on Windows.

**Details**

- Gone with it: the byte-exact allow plumbing it used, the redirection-target patterns, and the heuristics for working directories in compound `cd` commands.
- The prompt it produced, including the Cygwin symlink message, was present in v2.1.232 and does not occur anywhere in this build.

**Evidence**

`Path traverses a Cygwin-emulated symlink (Git Bash follows it, Node does not) — manual approval required`

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

### Windows Cygwin-symlink re-check on approved Bash commands dropped

On Windows and Git Bash, commands you already approved won't be re-prompted by a symlink safety check.

**What**

The safety layer that could turn an already-allowed Bash command back into a prompt on Windows is gone. That means fewer manual approvals under Windows and Git Bash, and one less check over commands you had previously approved.

**Details**

- Removed asks include "This previously-approved command runs shell code from a string its security scan cannot see into; on Windows its writes cannot be checked for Cygwin symlink redirection and need human review.", the cd-compound note about Cygwin-emulated symlinks, and "Path traverses a Cygwin-emulated symlink (Git Bash follows it, Node does not)".
- The overlong-command ask, "Command exceeds the maximum analyzable length; its full text cannot be security-scanned and requires human review.", is gone too; only the sed-specific length check remains.

**Evidence**

`on Windows its writes cannot be checked for Cygwin symlink redirection and need human review.`

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

### Proactivity level wired into API request telemetry but never set

Request telemetry has a slot for a proactivity level, but it is always empty.

**What**

API request telemetry events now carry a `proactivityLevel` field alongside `permissionMode`. The helper that produces the value returns nothing in every case, so the field is absent on this build; the plumbing landed ahead of the value.

**Details**

- Affects the query-sent event and the success and error completion events.

**Evidence**

`tengu_api_query`

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

### Gateway can forward user identity headers, plus a new ccr-gateway host mode

Gateway upstreams can pass your sanitised identity headers along, and a new ccr-gateway host mode takes its origin from --sdk-url.

**What**

An upstream configured with `forwardUserIdentity` now has sanitised identity headers, including `x-claude-gateway-user-email`, added to its outbound requests. A new `ccr-gateway` host kind takes its origin from `--sdk-url`.

**Details**

- Config validation warns when `forward_user_identity` is set on a deployment where no session principal can exist, since the headers would then never be sent.
- `ccr-gateway` reports the same missing-URL and rejected-URL errors as the existing `ccr-session` host kind.
- Upstream 400 and 413 responses now get their own error-message mapping instead of a generic one.

**Evidence**

`x-claude-gateway-user-email`

- Area: Gateway
- Names: `forwardUserIdentity`, `--sdk-url`
- Tier: Use it now
- Useful: 2/5
- Signal: 3/5

### Resume checkpoint file now tells you how to undo the turn

The usage-limit checkpoint file now explains how to roll the work back with /rewind.

**What**

The "Claude Code — resume checkpoint" file written when you approach or hit a usage limit now ends with a section explaining how to roll the work back with `/rewind`.

**Details**

- Notes that changes made by bash commands are not covered by the rewind.
- Says the saved snapshot is complete and is kept until the next checkpoint or roughly two weeks.

**Evidence**

`Don't want these changes? Resume this session (above), then run`

- Area: Checkpoints
- Names: `/rewind`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Worktrees can be created from GitLab merge requests

You can create a worktree from a GitLab merge request number, and the fetch now times out instead of hanging.

**What**

Creating a worktree from a change number now works on GitLab as well as GitHub. The ref fetched depends on the remote host, and the fetch now times out rather than hanging.

**Details**

- GitLab remotes fetch `merge-requests/<n>/head`, GitHub remotes fetch `pull/<n>/head`, and a remote on any other host tries both in turn.
- The failure message was reworded to cover both kinds of change and to mention timeouts.

**Evidence**

`Failed to fetch PR/MR #`

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

### Tab-indented YAML frontmatter now parses

Skill and agent files indented with tabs now load their metadata correctly.

**What**

A `SKILL.md` or agent file indented with tabs used to load with empty metadata. Frontmatter parsing now retries after expanding each leading tab to two spaces, in both the runtime reader and the plugin, skill and agent validator.

**Details**

- The retry returns a structured success or error result, so a genuine syntax error still logs "Failed to parse YAML frontmatter".

**Evidence**

`let t = U1_(e).replace(/^\t+/gm, (r) => " ".repeat(r.length));`

- Area: Skills
- Names: `SKILL.md`
- Tier: You'll notice
- Useful: 4/5
- Signal: 1/5

### `claude plugin validate` works on directories without a plugin manifest

claude plugin validate now falls back to checking skills, agents and commands when there is no manifest.

**What**

Pointing `claude plugin validate` at a directory that has no `.claude-plugin/plugin.json` or `.claude-plugin/marketplace.json` no longer just fails. It now falls back to validating the `skills`, `agents` and `commands` subdirectories.

**Details**

- The manifest lookup returns an error tagged `NO_MANIFEST`, which is what triggers the fallback rather than aborting.
- A dedicated message covers the case where the target path or its enclosing `.claude` directory is a symlink.
- The command's help text was updated to describe both behaviours.

**Usage**

`claude plugin validate ./my-plugin-dir` **Evidence** `Validate a plugin or marketplace manifest, or the skills, agents, and commands in a directory`

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

### Checkpoint notice now tells you how to undo the turn

After a turn is checkpointed you now see a dimmed line pointing at /rewind to undo its edits.

**What**

The message shown after a turn is checkpointed gained a second, dimmed line pointing at `/rewind` to undo that turn's file edits. It appears only when the checkpoint actually committed.

**Evidence**

`or /rewind to undo this turn's file edits`

- Area: Checkpoints
- Names: `/rewind`
- Tier: Use it now
- Useful: 3/5
- Signal: 1/5

## New Features

### Duplicate-reply guard for artifact comment threads

Claude re-reads an artifact comment thread before posting so it does not repeat a reply it already gave.

**What**

Before posting to an artifact comment thread, Claude Code re-reads it; if a Claude reply already stands after every "sent to Claude" request, the reply is refused and the draft discarded rather than repeating itself. A new `acknowledge_duplicate: true` parameter forces the post through, and the prompts now tell Claude to set it when posting a promised follow-up that genuinely adds something.

**Details**

- The refusal returns `already_answered: true` plus an optional `standing_reply_id`, and logs `already_answered_guard`.
- `acknowledge_duplicate` is valid only for the `reply` action and errors on any other action.
- The auto-react path passes a `recheck` payload (its own reply ids plus the scanned row ids) into each post so the guard can re-verify at publish time; the payload is omitted when the list of own reply ids is known to be incomplete, and that path reports `answered_post_time` when it stops.

**Evidence**

`reply only: post even though a Claude reply already stands after every "sent to Claude" request on the thread. Without it such a reply is refused as a likely duplicate. Pass true only for a deliberate follow-up that adds something new — never to restate what the standing reply said.`, `already_answered_guard`, `acknowledge_duplicate`, `acknowledge_duplicate` applies only to action "reply"`

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

### Artifact skills work with the 0.2.x viewer runtime

Interactive artifact elements now arm reliably on both the newer and older viewer runtimes.

**What**

The bundled artifact templates (workshop skill, workshop-page, whiteboard, PR-review artifact and the artifact-components decisions block) now request capabilities through the newer viewer runtime when it offers one, and fall back to the older globals otherwise, so interactive elements arm reliably in both.

**Details**

- Asks for `artifact`, `mcp`, `permissions` and `downloads` capabilities via `claude.use()`, falling back to `window.claude.artifact`, `window.claude.self` and `window.claude.mcp`.
- Interactive rows and pills are armed once by whichever signal answers first, the capability promise or the existing 250ms poll, and no longer give up while the document is still parsing.
- The decisions script hash pinned in artifact-components changed to match.

**Evidence**

`c.use('artifact').then(function (got) {`

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

### Backgrounding warns when a comment watcher would keep replying for you

The backgrounding dialog now calls out separately when a comment watcher would keep replying for you.

**What**

If Claude Code is watching a shared artifact for new comments and auto-replying to them, the "Background this session?" dialog now calls that out separately instead of folding it into the count of running tasks, and the confirm button reads "Background anyway (other tasks will be stopped)".

**Details**

- Running comment monitors are excluded from the ordinary in-flight task count and get their own sentence in the dialog.
- Forking a background session while keeping the parent alive strips the watch-artifact arguments from the child, so the same artifact is not watched twice.

**Evidence**

`Artifact comment monitor keeps`

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

### Auto-reply stands down when the comment thread was already answered

Auto-reply stays quiet when someone has already answered the artifact comment thread.

**What**

When a reply is attempted on an artifact comment thread that someone already answered, Claude Code no longer posts, and reports "reply not posted (thread already answered)".

**Details**

- The reply result can carry an `already_answered` flag, producing a new outcome `answered_post_time` alongside the existing `answered_elsewhere` and `summon_foreign`.
- The suppression is recorded on the `artifact_comments_autoreact` metric as `edit_reply_stood_down_post_time` and `answered_post_time_notice_suppressed`.

**Evidence**

`reply not posted (thread already answered)`

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

### Warning when an unrecognized model is sent to the API

You get a one-time warning line if a query names a model Claude Code does not recognise.

**What**

If a query names a model Claude Code does not recognize, it now warns once per model, printing a line starting with `[claude-code:unrecognized_model]` followed by the model name and where the query came from.

**Details**

- In non-interactive sessions the line goes to stdout, except when `CLAUDE_CODE_SESSION_KIND` is `bg`, where it is logged at warn level instead.
- Also fires the telemetry event `tengu_api_unrecognized_model` with the model and query source.
- The warning fires at most once per distinct model per session.

**Evidence**

`[claude-code:unrecognized_model]`

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

### Truncated event batches are flagged, and pending events are drained on interrupt

When background event batches get cut short, Claude is told how many events are still waiting.

**What**

When background events are delivered to Claude in batches, a batch cut short by size or count limits now says so in a system note, and each attachment carries a count of how many events are still waiting, so the model knows nothing was silently dropped.

**Details**

- Batches that hit the size or count limit append a "delivery chunked" note rather than ending mid-list with no signal.
- Cancelling the queue settles every undelivered event as dropped with the reason "interrupt cleared the queue", instead of leaving them unresolved.
- These event attachments are also now kept out of one transcript-collapsing path, so they stay visible in history.

**Evidence**

`delivery chunked: `

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

### Merge requests render as MR and `!` instead of PR and `#`

GitLab merge request links now show as MR with an exclamation mark instead of PR with a hash.

**What**

Links to GitLab-style merge requests now display with an `!` prefix and an MR label, and the URL is used exactly as given rather than rewritten through the configured pull-request URL template.

**Details**

- A link whose path matches `/-/merge_requests/<number>` is classified as a merge request.

**Evidence**

`aQe ? "MR" : "PR"`

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

### macOS `/net/<host>` autofs paths treated as network paths

macOS /net autofs paths are now treated as network paths, so previews are skipped and attaching one is refused.

**What**

Paths under `/net/<host>/...`, the macOS autofs -hosts mounts, are now recognised everywhere UNC network paths already were. File-read, write and edit previews, notebook reads, file opening and sed target checks all skip content previews for them, and attaching such a file is refused with a message saying it is a `/net` autofs path.

**Evidence**

`is a /net autofs -hosts path, which is not supported.`

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

### GitLab merge requests show up in the fleet and job views

Fleet and job views now recognise GitLab merge request links and label them "mr".

**What**

Rows linking to a GitLab merge request are now recognised and labelled "mr" instead of "pr", and merge request numbers are parsed out of URLs and free text. Previously only GitHub `/pull/` links were matched anywhere in these views.

**Details**

- The per-number matcher accepts either `pull` or `-/merge_requests`, and the free-text parser falls back to `/\/-\/merge_requests\/(\d+)(?!\d)/`.
- The badge is passed a kind computed from the link target: "mr" when the href matches a merge-request path, "pr" otherwise.
- The URL-to-number parser gained a matching merge-request branch.

**Evidence**

`/\/-\/merge_requests\/\d/`, `return /\/-\/merge_requests\/\d/.test(e.href) ? "mr" : "pr";`

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

### GitLab merge request URLs are recognised like GitHub pull requests

Pasting a GitLab merge request URL now resolves to a number just like a GitHub pull request.

**What**

Pasting a GitLab merge request URL now resolves to a number the same way a GitHub pull request URL does, and the row is labelled per provider.

**Details**

- A second URL pattern matches `/-/merge_requests/<n>`, with optional trailing slash, query or fragment.
- The row renderer computes a kind from the link, returning "mr" for merge request links and "pr" for everything else.

**Evidence**

`/^https?:\/\/[^/]+\/[^?#]+\/-\/merge_requests\/(\d+)\/?(?:[?#].*)?$/i`

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

## Improvements

### Permission rules now apply to artifacts fetched without a prompt

Your deny and ask rules now apply to artifacts pulled automatically by a watching session.

**What**

When a session started with `--watch-artifact` pulls an artifact with no user prompt behind it, having the artifact tool enabled is no longer enough on its own: matching deny or ask permission rules for that tool now block the read. Previously this path returned allowed outright.

**Details**

- Only reached when the artifact read surface is enabled in the first place; prompt-driven reads are unchanged.

**Evidence**

`promptless`

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

### A model-side stop no longer discards your `--watch-artifact` flag

When Claude stops a monitor itself, your watch-artifact flags stay on the saved command line.

**What**

When Claude itself tears down a live frame or artifact monitor, the `--watch-artifact` and `--watch-artifact-no-autoreact` flags you passed stay on the stored command line. They are only stripped when the stop came from somewhere other than the model.

**Details**

- Kill paths now tag the stop with an origin flag, set when a live frame is torn down internally and when a monitor task is killed with reason "parent".

**Evidence**

`modelOrigin: n === "parent",`

- Area: Artifacts
- Names: `--watch-artifact`, `--watch-artifact-no-autoreact`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Prompt no longer tells Claude to resolve review threads

Claude will still reply to review threads but no longer marks them resolved on its own.

**What**

The instruction to post a brief reply and then resolve a review thread after acting on it was removed from the prompt. Claude will still reply, but will not mark threads resolved on its own.

**Evidence**

`resolve the thread (Artifact tool, action "resolve")`

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

### Document artifacts no longer advertise commenting

Document artifacts no longer describe themselves as commentable; sheets and slides still do.

**What**

Sheets and slides still describe themselves as commentable; documents no longer do. The doc description drops "and comment on" and now says the team can read and edit in place, and the separate text about a live editor with commenting wired up was removed.

**Evidence**

`published for the team to read and edit in place`

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

### Startup install check is async and skipped on remote workspaces

The startup installation check runs in the background and is skipped entirely on remote workspaces.

**What**

The check for problems with your Claude Code installation now runs asynchronously at startup and returns immediately on a remote workspace, where the local install layout means nothing. Failures go to the error reporter rather than the screen.

**Evidence**

`install check: `

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

### MCP event subscriptions back off instead of reconnecting in a tight loop

A flaky MCP server that keeps dropping event streams now gets a pause instead of endless instant reconnects.

**What**

When an MCP server repeatedly kills a held event-subscription stream, the client now counts reopens in a trailing window and, past a threshold, pauses re-listening for a jittered delay rather than reconnecting immediately. This is on by default (`tengu_mcp_listen_reopen_park`, fallback true).

**Details**

- The window size and pause length come from a separate tuning flag `tengu_mcp_listen_reopen_park_tuning` (fallback null), validated as `windowMax` 1-100 and `parkDelayMinutes` 1-1440; with no server value the built-in constants apply.
- Each pause logs a `tengu_mcp_listen_reopen` event with outcome "parked".

**Evidence**

`subscriptions/listen reopened ${i.length} times in the trailing window (the server keeps killing held streams); parking re-listen`

- Flag `tengu_mcp_listen_reopen_park`: Not enough to say (read for one account on one subscription tier against v2.1.233; 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.
- Flag `tengu_mcp_listen_reopen_park_tuning`: Not enough to say (read for one account on one subscription tier against v2.1.233; this account: no value returned, anonymous baseline: no value returned, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: MCP
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### `sort` and `uniq` no longer get a special write pre-check

Bash permission checks no longer special-case sort and uniq; they default to read access like other commands.

**What**

Path-restricted Bash permission checks stopped extracting `sort`'s `-o`/`--output=` target and stopped running a separate write-permission deny pass for `sort` and `uniq`. Both now use the generic positional-file handling and default to read access.

**Details**

- The read-only `sed` downgrade, where a sed command judged non-mutating is checked as a read rather than a write, is preserved and now inline.

**Evidence**

`sort contents of files from`

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

### Windows NT object-manager paths now require manual approval

Windows NT object-manager paths now require your manual approval like other suspicious path forms.

**What**

Paths beginning `\??\` (the NT object manager namespace, and spellings that normalise to it) are now treated as suspicious. They trigger the same manual-approval ask as other odd Windows path forms, are rejected by the network-path check, and are caught during general path normalization, which previously only recognised the `\\?\` device form.

**Details**

- The ask text is unchanged: "which contains a suspicious Windows path pattern that requires manual approval", recorded under the circuit breaker name "suspiciousWindowsPath".
- The file-upload guard used by Claude in Chrome inherits the new check and throws on a "suspicious path spelling".

**Evidence**

`/^[\\/]\?\?[\\/]/`

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

### No more frontmatter warnings for reference docs alongside agents

Reference docs sitting next to your agents no longer trigger missing-frontmatter warnings.

**What**

A markdown file in a project's `agent` directory no longer produces "No frontmatter block found" or "No description in frontmatter" warnings unless its frontmatter actually declares a `name`. Files without one are reference documents the runtime skips anyway.

**Details**

- The related error about a shell value now truncates the offending value to 64 characters before printing it.

**Evidence**

`No description in frontmatter. A description helps users and Claude understand when to use this `

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

### Typing `/` clears the auto-filled "continue" after a rate-limit checkpoint

Typing a slash after a rate-limit checkpoint clears the prefilled "continue" so your command runs cleanly.

**What**

When the usage-limit checkpoint flow prefills your input with `continue`, typing a slash now removes that prefill so the slash command runs as typed instead of being appended to it.

**Details**

- The cancellation is reported as `tengu_rl_checkpoint_auto_continue_cancelled_by_slash`, a new event in this build.

**Evidence**

`tengu_rl_checkpoint_auto_continue_cancelled_by_slash`

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

### Backgrounding a session warns when artifact comment monitors are running

The background-this-session prompt now warns you if artifact comment monitors are still auto-replying.

**What**

The "Background this session?" confirmation now mentions artifact comment monitors that keep auto-replying from this terminal, alongside running tasks. When only monitors are running and no tasks are, the confirm button text changes to match.

**Evidence**

`Background anyway (other tasks will be stopped)`

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

### Plugin and marketplace validation no longer follows symlinks

Plugin validation stops following symlinks and tells you what it skipped, so passing means less than before.

**What**

Validation now refuses to follow symlinks and reports what it skipped, so a passing validation no longer implies the real target was checked. Install behaviour is unchanged and still dereferences them.

**Details**

- A local marketplace source that is a symlink, or reached through one, produces a warning saying install still dereferences it and that the real path should be validated separately.
- Files that are symlinks, are not regular files, or exceed a size cap are reported as unvalidated rather than passing.
- A path component that exists but is not a directory now yields an ENOTDIR error; the shared checker classifies each component as ok, absent, not-a-directory or refused.
- Skill, agent and command discovery now count the symlinked entries they skipped.

**Evidence**

`validation never follows one. Install dereferences symlinks that stay inside the marketplace, so this entry still loads. Validate the real path separately.`

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

### Windows suspicious-path detection covers flags and NT device paths

On Windows, risky paths hidden inside --flag=value arguments or NT device paths are now caught.

**What**

On Windows, the check that spots risky paths in commands now unwraps `--flag=value` arguments and re-checks the value, and flags NT object paths containing a `/??/` segment.

**Details**

- A normalizer collapses repeated `\??\` prefixes before the check runs.
- The cygwin-style `/c/...` to `C:\` conversion is now done inline rather than through a helper that was removed.
- The sandbox path validator now uses the same shared UNC and device-path check as the rest of the detector.

**Evidence**

`DavWWWRoot`

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

### Bundled Claude Code config skill covers plugin CLI and eval

The built-in config skill now lists the plugin subcommands you have and whether plugin eval is on.

**What**

The built-in skill that explains Claude Code's own configuration now lists the `claude plugin` subcommands available in the session and whether plugin eval is enabled.

**Details**

- Its offline reference gained sections on a configurable eval directory, grading of images and binaries by a vision judge, and UTF-16 handling.
- Each of those three sections is marked as describing an upcoming release, not current behaviour.

**Evidence**

`run from a shell, not the prompt`

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

### Auth preflight before plugin eval runs

Plugin eval checks your credentials up front and fails fast instead of dying partway through a run.

**What**

The plugin eval CLI checks it can actually authenticate before running any cases, so a run fails fast with an explanation instead of partway through. It engages only for first-party auth in `env-quad` mode with none of `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN` or `ANTHROPIC_UNIX_SOCKET` set.

**Details**

- A failed setup or token exchange aborts the run with a message.
- Failures that look transient (HTTP 5xx, 429, 408, or DNS and socket errors) are downgraded to a warning and the run continues.
- 401 or 403 adds advice to check the federation rule and set `ANTHROPIC_WORKSPACE_ID`.

**Evidence**

`workload identity federation could not obtain an access token`

- Area: Plugins
- Names: `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_UNIX_SOCKET`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Background-exit prompt says artifact comment monitors keep running

Backgrounding no longer claims artifact comment monitors will stop; it says they keep auto-replying here.

**What**

Pressing left arrow to send work to the background no longer lists artifact comment monitors among the things that will be stopped. They are counted separately and shown as a line saying they keep auto-replying from this terminal.

**Details**

- Behind the `tengu_bg_leftarrow_inprocess` gate, which falls back to on when the server has no value.

**Evidence**

`tengu_bg_leftarrow_inprocess`

- Flag `tengu_bg_leftarrow_inprocess`: Not enough to say (read for one account on one subscription tier against v2.1.233; 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: Artifacts
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Plugin eval reports no longer pass through terminal escapes

Plugin eval output strips control characters, so a crafted case name cannot mess with your terminal.

**What**

Case names and per-case error lines printed by the plugin eval CLI now have control and format characters, plus line and paragraph separators, replaced with a space, so a crafted case name cannot inject escape sequences into your terminal. The "case files failed to load" tally is also suppressed when a JSON report was written.

**Evidence**

`return e.replace(/[\p{Cc}\p{Cf}\u2028\u2029]+/gu, " ");`

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

### Fleet view shares one cut/yank buffer across its inputs

In fleet view, text cut in one input can now be pasted into another.

**What**

Text you cut in one fleet view input can be yanked into another. The fleet view now creates its own kill ring and hands it to every input it renders, instead of each input falling back to the process-wide default.

**Details**

- The kill ring (the buffer holding cut text for later yanking) is created once by the fleet view host state and passed down through a provider.
- Inputs outside the fleet view are unaffected and still use the ambient default.

**Evidence**

`killRing: k7o(),`

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

### Subagents can no longer refresh the MCP tool list

Subagents can no longer refresh the MCP tool list and are told to finish with what they have.

**What**

The MCP tool-list refresh tool joined the set of tools withheld from subagents. A subagent that calls it gets the standard refusal telling it to finish with the tools it has.

**Details**

- The refusal text is the existing shared one, so no new message shape.
- Only subagents are affected; the main session can still refresh MCP tools.

**Evidence**

`is not available inside subagents. Complete the task with the tools provided and return findings to the orchestrator.`

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

### Plugin validation now reports symlinked and oversized files instead of reading them

Plugin validation now warns about symlinked or oversized files instead of reading them.

**What**

Validation of plugins and projects now applies the same safety rules the loader does. Component directories and `hooks.json` are read without following symlinks and are size-capped, and anything refused becomes a warning telling you to validate the real path separately.

**Details**

- Per directory, counts of skipped symlinks, non-regular files and oversized files are aggregated into one message rather than listed individually.
- Error paths are truncated at 200 characters and messages at 1000.

**Evidence**

`hooks.json is past the size cap and was not read`

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

### Outcome-branch creation prefers git switch and no longer hangs

Creating a session's result branch tries git switch first and times out instead of hanging.

**What**

When a session creates its result branch, it now tries `git switch -q -C` first and falls back to `checkout -B` only if that fails, logging a retry line. Both attempts run under a timeout and honour cancellation, killing the git process and continuing on the current HEAD rather than hanging.

**Details**

- Runs with `-c alias.switch=` so a user-defined `switch` alias cannot take over the command.
- The child process is killed with SIGKILL on timeout or abort.

**Evidence**

`as checkout -B`

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

### Teammate messaging prompt no longer points at TaskUpdate

Teammate agents report progress with whatever task tools they have, or in plain prose.

**What**

Agents in a teammate conversation are now told to report progress through their task tools if they have any, and otherwise in plain prose, instead of being told to use TaskUpdate for status.

**Evidence**

`report progress through your task tools if you have them, otherwise in plain prose`

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

### Security-related skip reasons always survive the sync report cap

Sync reports always keep security-related skips even when thousands of files are skipped.

**What**

When a directory sync skips more than 1000 files, entries skipped as sensitive, unreadable due to permissions, or with unreadable ignore rules are now kept in full instead of competing for space in the capped report.

**Details**

- Only the remaining reasons are truncated, with "filtered" and "dependency_dir" deprioritised first.
- The reported count of omitted entries is computed from that remainder alone.

**Evidence**

`skippedOmittedCount`

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

### Proxy guidance now says to relay Anthropic's 400 and 413 messages

Proxy guidance now says to pass through 400 and 413 error text so auto-compact can react.

**What**

The proxy-writing guidance bundled with the gateway docs still tells you to log upstream errors and return a generic message preserving `error.type`, but now makes an exception: if a 400 or 413 arrives in Anthropic's own error envelope, relay its `error.message`, because client-side recovery such as auto-compact keys on that text.

**Details**

- The guidance text appears twice in the gateway docs and both copies changed.

**Evidence**

`own error envelope (e.g. \`prompt is too long: \u2026\`): relay that`

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

### Disarm notice says what happens to a watch the session carried

Turning off auto-replies now explains what happens to a watch this session was carrying.

**What**

Stopping auto-replies for a session now tells you that a watch this session was carrying is ended or stays disarmed for anything continuing from it, not just that a new session re-arms on publish.

**Evidence**

`Auto-replies are disarmed for the rest of this session (a new session re-arms on publish; a watch this session carried is ended or stays disarmed for its continuations).`

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

### Network path detection now covers /net automount paths

Automounted /net paths now count as network paths for file reads, writes and attachments.

**What**

File read, file write and attachment handling previously treated only UNC paths (`\\server\share` or `//server/share`) as network paths. They now share one check that also recognises automounter paths of the form `/net/<host>/...`, and the attachment error text says so.

**Evidence**

`is a network path (UNC or /net autofs), which is not supported.`

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

### Eval report publishing announces itself and is skipped inside interview runs

Eval report publishing now announces itself, and nested interview runs no longer each publish.

**What**

Publishing an eval report now prints `Publishing report to claude.ai (private to you)` before it happens. Auto-publish is additionally suppressed when the process is an eval interview subprocess, so nested runs do not each publish their own report.

**Details**

- The parent sets `CLAUDE_CODE_EVAL_INTERVIEW_SESSION` before spawning the child; the publish decision checks it on top of the existing `tengu_quartz_thimble` gate, which defaults to on.
- That variable is on the scrub list and is deleted from relaunch environments, so it does not leak into restarted sessions.

**Evidence**

`Publishing report to claude.ai (private to you)`

- Flag `tengu_quartz_thimble`: Off by default, switched on for this account (read for one account on one subscription tier against v2.1.233; this account: on, anonymous baseline: on, compiled default: off) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Plugins
- Names: `CLAUDE_CODE_EVAL_`, `CLAUDE_CODE_EVAL_INTERVIEW_SESSION`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Gateway always sanitizes 400 and 413 errors

Some 400 and 413 responses used to slip through the gateway unsanitized; now they always get cleaned.

**What**

Upstream 400 and 413 responses that carry a recorded message are now always replaced with a sanitized body and copied headers. Previously this rewrite only happened once a capability class had been resolved, so some responses passed through unsanitized.

**Evidence**

`upstream ${e.status} sanitized for CRI caller`

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

### Sending a message clears stale resume and cost dialogs

Sending a prompt clears leftover resume and cost dialogs, and idle notifications wait for dialogs to close.

**What**

Submitting a prompt now dismisses the leftover session-resume and cost-threshold dialogs, and the idle notification no longer fires while any dialog is still open.

**Evidence**

`cost_threshold`

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

### Discovering project agents and commands no longer blocks the UI

Finding your project agents and commands at startup no longer freezes the interface.

**What**

The walk from your working directory up to your home directory looking for `.claude` subdirectories (agents, commands and the rest) now uses async filesystem calls instead of synchronous ones, so cold discovery does not stall the event loop.

**Details**

- The warning printed when the system file-descriptor table is exhausted is unchanged: "hit ENFILE (system fd-table full); skipping".

**Evidence**

`hit ENFILE (system fd-table full); skipping`

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

### Broken YAML frontmatter is reported instead of silently ignored

A skill, command or agent with broken YAML frontmatter now logs an error instead of loading silently.

**What**

A skill, command or agent file whose YAML frontmatter fails to parse now logs an error naming the problem, where previously the failure was discarded and the file was loaded as if it had no frontmatter at all.

**Details**

- The skills loader also emits a telemetry event when this happens.

**Evidence**

`failed to parse and was ignored: `

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

### The install-github-app tip no longer shows on GitLab and Bitbucket repos

The install-github-app tip no longer appears on GitLab or Bitbucket repositories.

**What**

The startup tip suggesting `/install-github-app` is now suppressed when the detected remote host is GitLab or Bitbucket, on top of the existing check on how often the GitHub Actions setup has been run.

**Evidence**

`Run /install-github-app to tag @claude right from your Github issues and PRs`

- Area: Startup
- Names: `/install-github-app`
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Relaunch is up to a second faster

Relaunching Claude Code is up to a second faster because a needless wait was removed.

**What**

The relaunch path no longer waits on a 1000 ms pre-cleanup drain before shutting down; it goes straight to the 2000 ms cleanup step and then the analytics flush.

**Evidence**

`analytics flush timeout`

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

### Windows `\??\` device paths recognised

Windows object-manager paths are now recognised as device paths and handled like the other odd forms.

**What**

Path canonicalisation and the network-path check previously matched only the `\\?\` and `\\.\` device prefixes. They now also match the `\??\` object-manager form, including after Windows normalisation of paths containing `??`, so those paths skip canonicalisation and are treated as device paths.

**Evidence**

`e1c = /^[\\/]\?\?[\\/]/;`

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

### Clearer denial message where no permission prompt can be shown

Background agents denied a tool now get a plain denial message instead of confusing don't-ask wording.

**What**

When a background agent denies a tool because it cannot ask you, the model now sees a plain "Permission to use X has been denied". The older wording about don't-ask mode is kept for that mode only, so the two cases no longer read the same.

**Evidence**

`Permission prompts are not available in this context`

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

### Frontmatter errors now say what actually happens at load time

Broken frontmatter errors now tell you what will actually happen to that component at load time.

**What**

When a component's YAML frontmatter fails to parse, the validator tells you the runtime consequence for that specific component type rather than a generic parse error.

**Details**

- A project agent does not load at all and is treated as a co-located reference document.
- A plugin agent loads with its name taken from the filename and every other field dropped.
- Other component types load with empty metadata.
- A frontmatter block that parses to null is now accepted as an empty mapping instead of reported as a type error.

**Evidence**

`At runtime this agent loads with its name taken from the filename and every other frontmatter field silently dropped.`

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

### Cost warning and resume prompt now use the standard dialog stack

The spend warning and resume prompt now queue with other dialogs instead of popping over your work.

**What**

The $5 spend warning and the prompt shown when returning to a resumed session are now regular dialogs rather than one-off screen state, so they queue with everything else instead of appearing on top of in-flight work.

**Details**

- Acknowledging the cost warning still persists `hasAcknowledgedCostThreshold` and reports `tengu_cost_threshold_acknowledged`.
- The trigger now skips when a cost warning is already open and defers while other work is running.

**Evidence**

`kind: "cost_threshold"`

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

### Plugin and marketplace validation warns about symlinks and checks each plugin.json fully

Marketplace validation warns about symlinks and fully validates every plugin.json, not just its version.

**What**

Validation now warns when a marketplace file is a symlink or sits behind one, and validates each entry's `plugin.json` completely rather than only reading it to cross-check the version.

**Details**

- The file reader distinguishes absent, refused, too large and ok instead of collapsing everything into a failure.
- A missing file or a path component that is not a directory counts as absent; any other unexpected filesystem error is now surfaced rather than swallowed.

**Evidence**

`is or traverses a symlink, so `

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

### Blocked-marketplace tip covers self-hosted GitLab too

When policy blocks a github: source, the tip now mentions self-managed GitLab and GitHub Enterprise too.

**What**

When enterprise policy blocks a `github:` shorthand source, the suggestion now names internal git hosts generally, listing GitHub Enterprise alongside GitLab Self-Managed and Dedicated, with a matching example URL.

**Evidence**

`For an internal git host (GitHub Enterprise, GitLab Self-Managed or Dedicated, `

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

### Eval suites stop at the first authentication failure

An eval suite stops immediately when the first run is rejected for bad credentials.

**What**

If the very first run in an eval suite is rejected for bad credentials, the suite halts immediately rather than working through every remaining case and failing each one the same way.

**Details**

- The stop is reported as `auth_failed`, with a new `auth_preflight_failed` alongside it; previously partial runs were always attributed to `cost_ceiling`.
- The printed message points you at the credential error above and asks you to re-run.

**Evidence**

`every remaining run would fail the same way, so the suite stops here. Fix the credential above and re-run.`

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

### Plugin manifests can no longer point at Windows drive-letter paths

Plugin manifests can no longer point at Windows drive-letter paths like C:\.

**What**

The plugin manifest path check, which already rejected entries containing `..` or absolute POSIX paths, now also rejects anything beginning with a drive letter such as `C:\`.

**Evidence**

`/^[a-zA-Z]:/.test(d)`

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

### Cost threshold prompt now uses the normal dialog stack

The spending-threshold prompt opens as a normal dialog and can no longer appear twice.

**What**

The spending-threshold acknowledgement no longer competes in the startup screen queue; it opens as an ordinary dialog and is guarded against opening twice. Acknowledging it still records `tengu_cost_threshold_acknowledged`.

**Evidence**

`tengu_cost_threshold_acknowledged`

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

### Windows Bash argument scanning sees through `--flag=value` and NT device paths

Windows command scanning now unwraps flag=value arguments and spots NT object-manager paths.

**What**

On Windows, the check that spots network and device paths in Bash command arguments now strips `--flag=` and `-f=` prefixes repeatedly and re-checks what is left, and recognises `\??\` NT object-manager paths. Off entirely on other platforms.

**Details**

- Path checks in Read, Edit and NotebookEdit, plus the file-path display component, moved from ad-hoc `\\` / `//` prefix tests to shared helpers that also cover `/net/<host>` autofs paths.
- NotebookEdit lost its shortcut that accepted UNC paths without further checks, so those paths now go through the `.ipynb` extension check like any other.

**Evidence**

`WAd = /^--?[A-Za-z0-9][\w-]*=/`

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

### Settings and agent-directory lookups no longer block startup

Settings and agent-directory lookups no longer block the event loop while Claude Code starts.

**What**

Directory walks that used synchronous file stats now use their promise-based equivalents, and the settings loader awaits its source list before reading settings in parallel. Same results, less blocking of the event loop while Claude Code starts.

**Evidence**

`ENFILE`

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

### Running monitors are named by what they watch

Background monitor summaries now name artifact comment monitors instead of saying just "monitors".

**What**

The summary of running background monitors now says "1 Artifact comment monitor" or "Artifact comment monitors" when every monitor in the group is one, instead of the generic "1 monitor" / "N monitors".

**Details**

- Prompt text about auto-replies was reworded to say that a watch you explicitly asked for reconnects on its own, without auto-reply.

**Evidence**

`1 Artifact comment monitor`

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

### Eval failure explanations are stripped of control characters

Eval failure text is stripped of control characters so it cannot break the report layout.

**What**

The failure text shown for an eval case, taken from the heaviest failing grader or the run error, now has control characters and line and paragraph separators replaced with spaces so it cannot break report layout.

**Evidence**

`return t ? fd(`${t.name}: ${t.explanation}`) : null;`

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

### HTTP helper forwards `maxRedirects`

The internal request helper now passes a redirect limit through to the HTTP client.

**What**

The internal request wrapper now passes a caller-supplied redirect limit through to the underlying HTTP client, alongside the content-length and body-length limits it already forwarded.

**Evidence**

`...(n.maxRedirects !== void 0 && { maxRedirects: n.maxRedirects }),`

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

## Bug Fixes

### Streamed events are no longer dropped during shutdown

Streaming responses no longer lose the last events when a connection is closing.

**What**

The server-sent-events reader used to cancel mid-stream once the connection was closing, discarding events it had already read. Both of those bail-outs are gone, so those events are now parsed and dispatched.

**Evidence**

`ephemeral_event`

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

### Parked permission requests no longer leave a dangling tool call

In print and SDK runs, an abandoned permission prompt now closes the tool call as rejected.

**What**

In print and SDK sessions, a tool call whose permission request was left parked is now closed out in the transcript with an error result carrying the standard rejection text, instead of sitting there unanswered. The session is marked idle afterwards, and failures to write the result are logged at warn level.

**Evidence**

`retiring parked permission toolUseID=`

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

### Windows device-namespace paths are treated as network paths

On Windows, odd device-style paths are now blocked from attachments, uploads and deep-link directories.

**What**

Paths of the form `\??\...`, including ones that only take that shape after Windows normalization, now count as network paths everywhere the check is made. They are blocked for @-mention attachments and Chrome file uploads regardless of which network directories the session trusts, and deep-link working directories using them are rejected.

**Details**

- One shared predicate now covers both `\\` UNC paths and `\??\` device-namespace paths; `/net/<host>` automounts are recognized by a separate predicate.
- A per-module copy of the old check, which required a non-slash character after the two leading slashes and did no normalization, was deleted and its caller moved to the shared helper.
- The executable-path check uses the combined predicate.

**Evidence**

`Invalid cwd in deep link: UNC / network paths are not supported, got`, `network path not allowed: `

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

### Agent files no longer get spurious `shell:` frontmatter errors

Your agent files stop being wrongly flagged with a shell frontmatter error.

**What**

The frontmatter validator only applies the `metadata` mapping check and the `shell` value check to skill and command files now, so agent definitions stop being flagged with "shell must be 'bash' or 'powershell'".

**Evidence**

`shell must be 'bash' or 'powershell', got`

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

### Custom slash-command arguments are no longer re-expanded

Arguments you pass a custom command are inserted literally, even if they contain $2 or $ARGUMENTS.

**What**

If you pass a custom command an argument whose text contains something like `$2` or `$ARGUMENTS`, that text is now inserted literally instead of being expanded again by the later substitution passes.

**Details**

- Applies to `$ARGUMENTS`, `$1`, `$ARGUMENTS[n]` and named placeholders in a command's prompt.
- Each substituted value is wrapped in a private marker character and its `$` signs are escaped to a second marker, both removed at the end.
- Any of those two marker characters already present in the template or in your argument text are replaced with the Unicode replacement character first, so they cannot be used to smuggle expansion back in.
- The final unescape pass now always runs rather than only when an escape was seen.

**Evidence**

`￾`

- Area: Slash Commands
- Names: `$ARGUMENTS`
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Slash-command arguments can no longer be expanded as placeholders

Text you supply to a slash command can no longer be re-read as another placeholder.

**What**

When a custom slash command's body is filled in with `$1`, `$ARGUMENTS`, `$ARGUMENTS[n]` or named arguments, the text you supply is now protected so it cannot itself be read as another placeholder and expanded a second time.

**Details**

- Each substituted value is fenced with internal marker characters and its `$` escaped; the markers are stripped once expansion finishes.
- The template and every value are first scrubbed of those marker characters (U+FFFF and U+FFFE), which are replaced with the replacement character, so argument text cannot forge a fence.

**Evidence**

`e.replaceAll(vLr, "\uFFFD")`

- Area: Slash Commands
- Names: `$ARGUMENTS`
- Tier: You'll notice
- Useful: 3/5
- Signal: 1/5

### Duplicate skill names reported once at startup

A skill available from two sources now shows up once at startup, not twice.

**What**

The startup payload listing agents, skills and plugins now removes duplicate skill names before filtering, so a skill available from two overlapping sources appears once instead of twice.

**Evidence**

`skills: wA(e.skills, "name")`

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

### Cancelling an IDE diff no longer logs an error

Cancelling a diff in your IDE no longer prints a spurious failure message.

**What**

Opening a diff in your IDE now reads the original file asynchronously and checks for cancellation before and after WSL path translation. Cancelling no longer prints "Failed to show diff in IDE".

**Evidence**

`Failed to show diff in IDE: `

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

### Infinity rejected in numeric command-line options

Numeric command-line options now reject Infinity instead of accepting it.

**What**

The shared parser for numeric options now rejects any value that resolves to Infinity or -Infinity, the same way it already rejected non-numbers, before applying minimum and maximum bounds.

**Evidence**

`if (!Number.isFinite(r)) return;`

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

### Duplicate slash commands listed once in the system prompt

A command defined in two places now appears once in the system prompt.

**What**

The list of prompt commands injected into the system prompt is now deduplicated by command name, so a command defined in more than one place no longer appears twice.

**Evidence**

`n = wA(t, "name").filter((u) => u.type === "prompt")`

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

## In Development

### Artifact comment auto-reply gains a third stand-down case

Auto-replies to artifact comments now skip when the question was already answered when posted.

**What**

The path that auto-replies to artifact comments can now stand down because the question was already answered at post time, alongside the existing answered-elsewhere and foreign-summon cases, each with its own telemetry value.

**Details**

- When a reply is withheld, the model is told explicitly that nothing was posted and what the user should do instead.
- Replies that are posted now name the comment id, tell the model not to post a duplicate, and tell it to resolve the thread when done.
- Requires the artifact comment auto-react gate to be armed.

**Evidence**

`stood_down_answered_post_time`

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

### Bash and monitor permission prompts registered in the dialog renderer

Bash and monitor permission prompts now have proper titles in the shared dialog renderer.

**What**

Two permission dialog kinds that existed but had no entry in this renderer, `permission_bash` and `permission_monitor`, now have titles and components, both using the standard "Claude needs your permission" heading. Which screens route these kinds through this particular renderer is not settled in the code.

**Evidence**

`Claude needs your permission`

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

### Teammates are only told about the task list when they can use it

Spawned teammates only hear about the shared task list when they actually have task tools.

**What**

The instructions given to a spawned teammate now depend on whether that teammate actually has task tools. The task-list path and the line "Check the task list periodically. Create new tasks when work should be divided. Mark tasks resolved when complete." are only added when it does; previously both were sent unconditionally. A teammate without task tools also stops auto-claiming the next open task.

**Details**

- A `hasTaskListTools` field on the team context attachment carries the answer, computed from the teammate's real tool surface.
- Task tooling is gated: `CLAUDE_CODE_ENABLE_TASKS` can force it off, and the todo/task surface otherwise falls back to `tengu_rosy_wren`, which is false in this build.

**Evidence**

`hasTaskListTools`

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

### Document artifact skill stops claiming commenting is wired up

Document artifacts no longer claim commenting is available; sheets and slides still do.

**What**

The doc, sheet and slides artifact skills now each declare whether commenting works, and the note appended to the prompt is built per skill: docs say "editing and persistence are", sheets and slides say "editing, persistence, and commenting are". Previously one fixed sentence always claimed commenting was wired.

**Details**

- These skills are registered with an availability check that returns false in this build, so they do not appear regardless.
- The note is only appended when the artifact capability information is present.

**Evidence**

`editing, persistence, and commenting are`

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

### The document artifact skill drops in-document commenting

Live document artifacts lose the select-to-comment panel; sheets and slides keep it.

**What**

The skill that generates a live working document no longer ships the select-to-comment kit: the comments side panel, composer, bubble styles, hidden comment store and the "All comments" toolbar button are gone. Spreadsheet and slides artifacts keep commenting. The whole family of skills is switched off in this build.

**Details**

- The document description now says it is "published for the team to read and edit in place", dropping "and comment on".
- The self-check step is simplified to no leftover slot markers, no placeholder text, and a status chip saying where the document actually is.
- Gated on an internal productivity-skills check that returns false in this build.

**Evidence**

`Create a document artifact \u2014 a live working document that looks and edits like a word processor page, published for the team to read and edit in place`

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

### Team context mentions the shared task list only when task tools exist

Teammate agents only get told about the shared task list when those tools exist.

**What**

The team context block injected for teammate agents now adds the task list path and the instruction to check it only when task-list tools are actually available, and the in-process teammate agent only receives those tools in the same case. Availability is off by default in this build.

**Details**

- Controlled by a server-side flag with an off fallback, overridable with `CLAUDE_CODE_ENABLE_TODO_TOOLS`.

**Evidence**

`Check the task list periodically. Create new tasks when work should be divided. Mark tasks resolved when complete.`

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

### Auto-reply guidance tells Claude to check for an existing answer before posting

Auto-reply guidance now tells Claude to read the thread and resolve rather than duplicate.

**What**

The follow-up instruction attached to auto-reply notices was rewritten. Claude is now told to read the thread first and skip posting if a Claude reply that already answers the comment stands, then resolve the thread. Same gating as the rest of auto-reply: `CLAUDE_CODE_ARTIFACT_COMMENTS_AUTOREACT`, otherwise the `tengu_sorrel_trellis` flag, off by default.

**Details**

- A second wording is used when the automatic reply did post, naming the id of the comment it created.
- Separate reasons distinguish a reply that already stands from one posted by another session.

**Evidence**

`a reply already stands in the thread`

- Flag `tengu_sorrel_trellis`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.233; 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: `CLAUDE_CODE_ARTIFACT_COMMENTS_AUTOREACT`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Rewind refuses turn ranges containing a delivered external event

You cannot rewind past a turn that received an event delivered from outside the session.

**What**

The check that decides whether a turn can be rewound now rejects any range that includes a delivered external-event record, on top of the existing refusal for entries that came from outside the session.

**Evidence**

`range contains a delivered poll-event record`

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

### Two more dialogs named in the remote status channel

Remote surfaces now see the $5 cost notice and resume-from-summary prompt as pending dialogs.

**What**

The list of pending-dialog descriptions sent to a remote or host surface gained the $5 session cost notice and the resume-from-summary choice, joining the refusal fallback, overage and MCP link prompts. Nothing is emitted unless the session passes the remote-surface check.

**Evidence**

`cost_threshold: "acknowledge: $5 session cost notice"`

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

### SDK hosts can opt into the todo tools

Apps embedding Claude Code can now switch todo tools on through a launch option.

**What**

Applications embedding Claude Code can now switch the todo tools on through a new launch option, mirroring the existing opt-in for search tools. Off unless the host sets it; there is no user-facing setting.

**Details**

- The value is part of the cache key for the memoised tool list, so flipping it re-derives which tools are available rather than serving a stale list.

**Evidence**

`replaceTodoToolsOptIn`

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

### Plugin eval checks your credentials before running any case

Plugin eval checks your credentials first and aborts instead of failing every case.

**What**

`claude plugin eval` now runs an authentication preflight up front. If it fails, the whole suite aborts with exit code 2 and a report status of `auth_failed` rather than burning every case on a broken credential. A preflight problem that is not fatal is printed as a notice and the run continues.

**Details**

- The result object carries an `authPreflightFailed` flag alongside the report status.
- Plugin eval as a whole is early access, gated on `tengu_walnut_spire` or `CLAUDE_CODE_WALNUT_SPIRE`.

**Evidence**

`authentication check failed before running any case`

- Flag `tengu_walnut_spire`: Off in both readings (read for one account on one subscription tier against v2.1.233; 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: Plugin Eval
- Names: `claude plugin eval`
- Tier: Not switched on
- Useful: 3/5
- Signal: 2/5
- Present in the build but not switched on

### Plugin eval stops the suite when authentication fails

A failed login stops the whole eval suite instead of wasting every case.

**What**

An eval run that cannot authenticate, or whose credential is rejected before any case completes, now aborts the whole suite instead of burning every remaining case on the same broken credential. The HTML report banner explains the stop, the CLI prints "stopped: authentication failed", and the outcome is reported separately from the two existing partial reasons, a cost ceiling and interruption. `plugin eval` remains behind an early-access check.

**Details**

- Individual case results carry an auth-rejected outcome with its own label in the run-status table.
- The suite aborts once the first run comes back auth-rejected.
- A new environment variable marks the child process spawned for the `plugin eval init` interview, and its presence suppresses report publishing.
- The command's long description moved into the shared command-help table.

**Evidence**

`A run could not authenticate (or the credential was rejected before any run), so the suite stopped before every case finished.`, `a run could not authenticate \u2014 every remaining run would fail the same way, so the suite stops here. Fix the credential above and re-run.`, `stopped: authentication failed`

- Area: Plugin Eval
- Names: `plugin eval`
- Tier: Not switched on
- Useful: 3/5
- Signal: 2/5
- Present in the build but not switched on

### Ambient monitor sessions stay in the list when auto-react is armed

Ambient monitor sessions now stay listed when they are armed to auto-reply.

**What**

The session list previously hid all ambient monitor entries. It now keeps one when auto-react is armed for it, meaning the session is running, the auto-react memo is enabled and the session is not suppressed. Requires auto-react to be enabled.

**Evidence**

`autoReactArmed`

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

### Slack-verified human turns carried through the print path

In print mode, turns verified as coming from a human on Slack keep that marker.

**What**

In non-interactive print mode, the marker saying a turn came from a verified human on Slack is now copied onto the message record. The queue side already used that marker to decide prompt priority and whether to fold turns together; the print path did not. Slack and relay-connected sessions only.

**Details**

- Shared state also gained a map recording, per session, where a reply came from and which tool-use id it expects, marked consumed once matched.

**Evidence**

`pipelineReplyOrigins`

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

### Plugin eval runs report exit codes, stderr and auth rejection

Plugin eval runs now report exit codes and stderr tails when a case fails.

**What**

Eval run summaries now separate ok, error and exit outcomes. A non-zero exit produces an error built from the result message, or from the exit code plus the tail of stderr, falling back to "(no stderr)" when there is none. Behind the plugin eval early-access gate.

**Details**

- Runs where a top-level assistant message reported `authentication_failed` or `oauth_org_not_allowed` are now flagged as auth-rejected rather than as generic errors.

**Evidence**

`(no stderr)`

- Area: Plugin Eval
- Names: `claude plugin eval`
- Tier: Not switched on
- Useful: 2/5
- Signal: 2/5
- Present in the build but not switched on

### Plugin subcommand help now comes from one table, with eval commands hidden

Plugin subcommand help now comes from one table, with eval commands hidden by default.

**What**

Every `claude plugin ...` subcommand takes its usage line, aliases and description from a single registry instead of inline strings. Entries marked as early access, currently `eval` and `eval init`, are filtered out of the listing unless early access is switched on, which it is not by default in this build.

**Details**

- Visibility of the eval commands is decided by a server-side flag with an off fallback, overridable locally with `CLAUDE_CODE_WALNUT_SPIRE`.

**Evidence**

`earlyAccess: "pluginEval"`

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

### Warning for poll events left in the queue

Leftover queued external events would log a warning, but nothing reaches that code yet.

**What**

A new helper warns and records a `left_queued` reason on the poll event delivery counter when event emission drops to zero with events still queued. The poll-event path is switched off in this build, so nothing reaches it.

**Evidence**

`poll_events emission degraded to 0 for`

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

### Runtime note wording now varies by artifact kind

Claude is told which capabilities each artifact type already provides, per kind.

**What**

The note appended to productivity skills telling the model what the runtime already provides now picks its wording per kind, saying "editing and persistence are" for documents and "editing, persistence, and commenting are" for spreadsheets and slides, instead of one string claiming commenting for all three.

**Details**

- Driven by a per-kind commenting flag in the skill registry: document false, sheet and slides true.
- The note is only appended when the artifact runtime advertises a capabilities field, and the skills are off in this build.

**Evidence**

`editing and persistence are`

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

### Live-update subscriptions say when they are auto-replying

Debug output now shows whether an artifact watch will answer comments or just observe.

**What**

The description on an artifact live-updates connection now appends a note when comment auto-replies are switched on for it, so debug output tells a plain watch apart from one that will answer comments. The note appears only when auto-react is enabled for that subscription.

**Evidence**

`, auto-replying to comments`

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

### Split poll-event deliveries get a "nothing was dropped" marker

Split event batches would say nothing was dropped, but the path is off in this build.

**What**

When more queued events are waiting than fit in a single delivery, a system note is appended saying the batch was split, oldest first, and nothing was lost. The poll-event path this renders on is switched off in this build, so no one sees it.

**Evidence**

`more queued event(s) follow in the next delivery, oldest first; nothing was dropped.`

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

### Grader summaries in plugin eval are scrubbed before truncation

Grader summaries in plugin eval are stripped of escape characters before printing.

**What**

The one-line summary printed per grader now has control and formatting characters replaced with spaces before being cut to 200 characters, so a grader explanation cannot push raw escape sequences into your terminal.

**Details**

- Part of `claude plugin eval`, early access behind the remote flag `tengu_walnut_spire` (default off) or `CLAUDE_CODE_WALNUT_SPIRE`.

**Evidence**

`/[\p{Cc}\p{Cf}\u2028\u2029]+/gu`

- Flag `tengu_walnut_spire`: Off in both readings (read for one account on one subscription tier against v2.1.233; 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: Plugin Eval
- Tier: Not switched on
- Useful: 2/5
- Signal: 1/5
- Present in the build but not switched on

### Live artifacts ask the runtime for their write surface up front

Live artifacts load their write access once at startup, so the first edit feels faster.

**What**

All three artifact templates now request the artifact namespace once at load through `claude.use('artifact')` and cache it, instead of looking it up lazily on each edit, so the first edit rarely waits.

**Details**

- Falls back to the old lazy `claude.artifact || claude.self` lookup when `claude.use` is not a function.
- A new pending state covers the window between asking and answering: edits are tracked as if the document were live, and the status is not prematurely set to `Local only`. When the request resolves the template either saves the pending edits or reports `Local only`.
- Off in this build; the templates only persist on live documents where the runtime grants the artifact capability.

**Evidence**

`Ask for the namespace at load so the first edit rarely waits on it.`

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

### plugin eval --verbose now writes traces to the debug log

Eval --verbose now writes traces to the debug log instead of streaming them live.

**What**

`--verbose` on the plugin eval subcommand no longer streams the trace as it runs; it logs per-message trace events to the debug log and points at `--debug-file` for reading them. The `plugin eval` command is in early access.

**Usage**

`claude plugin eval --verbose --debug-file trace.log` **Evidence** `Log per-message trace events to the debug log (use --debug-file to read them)`

- Area: Plugin Eval
- Names: `--verbose`, `--debug-file`, `plugin eval`
- Tier: Not switched on
- Useful: 2/5
- Signal: 1/5
- Present in the build but not switched on

### Eval report publishing prints a working link and a way to opt out

Published eval reports now print a working link and mention --no-publish.

**What**

After `claude plugin eval` publishes a report, the printed URL now comes from the publish call that was actually awaited rather than a stale value, and a hint offers `--no-publish` for next time unless you already passed it. The `plugin eval` command is early access.

**Evidence**

` \xB7 keep local next time with --no-publish`

- Area: Plugin Eval
- Names: `--no-publish`, `plugin eval`
- Tier: Not switched on
- Useful: 2/5
- Signal: 1/5
- Present in the build but not switched on

### Comment styling split into its own shared stylesheet region

Artifact editor styles were split so documents can drop comment styling cleanly.

**What**

The shared editor stylesheet for artifacts was cut in two, so the document kind can drop commenting while spreadsheets and slides keep byte-identical shared styles.

**Details**

- The original region now covers only the toolbar band, buttons, canvas and save status; bubble, composer, side panel and anchor-mark styles moved into a new comment-only region carried by the kinds that ship the comment kit.
- The `.cmark` class stayed behind and is now documented as the collaborator-edit flash rather than a comment anchor.
- Off in this build along with the rest of the productivity skills.

**Evidence**

`KIT:comment-chrome:begin \u2014 styles for the select-to-comment kit:`

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

## Internal Changes

### Entrypoint and SDK version are sanitized before being reported

Your entrypoint and SDK version are checked against known values before being reported.

**What**

Request metadata used to pass the raw `CLAUDE_CODE_ENTRYPOINT` and `CLAUDE_AGENT_SDK_VERSION` environment values through untouched. The entrypoint is now checked against a known list and reported as "other" if unrecognised, and the SDK version must match a version pattern or is reported as "other" or "none", with "unknown" allowed through. Arbitrary environment strings no longer reach the payload.

**Evidence**

`agentSdkVersion`

- Area: Telemetry
- Names: `CLAUDE_CODE_ENTRYPOINT`, `CLAUDE_AGENT_SDK_VERSION`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Gateway requests carry the signed-in user's identity

Proxy requests now attach your signed-in subject and email unless a managed principal is used.

**What**

Requests through the proxy path that are not made under a managed service principal now attach the user's subject and email, and pass the same pair to the model-list callback. Previously only the principal marker was attached.

**Evidence**

`criPrincipal`

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

### Startup telemetry records proactivity level and screen-reader mode

Startup telemetry now records proactivity level and whether screen-reader mode is on.

**What**

The startup event now reports the session's proactivity level, alongside permission mode. When the accessibility layer reports screen-reader mode active, it also reports that and how the mode was turned on. The proactivity level is also carried into the permission context built for the session.

**Details**

- The screen-reader fields are omitted entirely when screen-reader mode is off.

**Evidence**

`proactivityLevel: Eo(b),`, `screenReaderActivationSource`

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

### Permission and dialog dispatchers are now long-lived objects

Permission and dialog prompts can no longer act on stale theme or permission settings.

**What**

The code that routes tool-permission and user-dialog requests became two objects kept for the session rather than callbacks rebuilt on every render, so it can no longer act on a stale view of the theme, tool registry or permission settings. The same responses are still sent, including the "User aborted" deny and the deny on unmount.

**Evidence**

`Permission dispatcher unmounted`

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

### REPL state read from a generic per-agent map

REPL state now comes from a general per-agent map, and the feature stays gated.

**What**

REPL context accessors were dropped from tool-use context construction in the main loop, print mode and subagents; compaction and REPL detection now read a general tool-state map keyed by agent id. The REPL feature keeps its existing gate.

**Evidence**

`repl-registered`

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

### More services scoped per host

More services are scoped per host, groundwork for several sessions in one process.

**What**

The OS-release detector, installer state, regex-rule helpers and several registries now resolve through the per-host holder, taking its call sites from 55 to 61. Groundwork for more than one host in a single process.

**Evidence**

`readOsRelease: () => Ltm.readFile("/etc/os-release", "utf8"),`

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

### API result telemetry carries the proactivity level

API success and error telemetry now includes your session's proactivity level.

**What**

The proactivity level is now included in both the API success and API error payloads, alongside the permission mode.

**Evidence**

`tengu_api_success`

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

### API telemetry records the proactivity level

Query and result telemetry now carry a proactivity level field from your session.

**What**

The `tengu_api_query` event and the request-result event now include a `proactivityLevel` field, taken from the session, next to the existing permission mode.

**Evidence**

`proactivityLevel: Eo(i),`

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

### Skill and hook directory lookups became asynchronous

Skill and hook directory lookups now await, making room for filesystem-backed resolution.

**What**

Skill loading now awaits the plugin directory lookup, and hook-file paths await their scope directory instead of computing it synchronously. This makes room for directory resolution that touches the filesystem or plugin state.

**Evidence**

`` `Loading skills from: managed=${o}, user=${r}, project=[${i.join(", ")}]` ``

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

### Remote runner prefetches config and batches step events

Remote sessions fetch their config earlier and reuse it only while it is still fresh.

**What**

In remote sessions the runner now starts fetching `/remote` config early and reuses that result only when it is recent enough, otherwise re-fetching so the inference token is fresh.

**Details**

- Event posting was reworked so step events can be held and flushed together with the next activity or step post.
- When a post fails, the number of dropped events is reported.

**Evidence**

`re-fetching for a fresh inference token`

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

### Auto-mode denials moved into a React context

Recent auto-mode denials, capped at twenty, now live in their own shared provider.

**What**

The list of recent auto-mode denials, capped at the most recent 20, now lives in its own provider mounted by the app state provider, and reading it outside that provider throws. The permission flow and auto-mode UI were switched over.

**Evidence**

`useAutoModeDenials cannot be called outside of an <AutoModeDenialsProvider /> (mounted by <AppStateProvider />)`

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

### REPL contexts moved into per-session tool state

REPL contexts moved to a registry that releases timers even on error paths.

**What**

REPL contexts no longer live on the session object. They sit in a registry with explicit release, wired into session teardown and into a `finally` block on the MCP server tool path so timers get cleared on the error route too.

**Details**

- `getReplContexts()` and `setReplContext()` are gone from tool-use context objects, including both non-interactive builders; the REPL tool and the fork/replay-log path now read a keyed entry in tool state instead.
- A teardown step is literally named replContext.
- The REPL feature stays behind `tengu_slate_harbor`, which falls back to false, or `CLAUDE_CODE_REPL`.

**Evidence**

`replContext`, `getReplContexts: () => ({}),`

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

### Incremental repo reset now uses a detached checkout

Managed-compute repo updates now use a detached checkout instead of a merge.

**What**

In the managed-compute git sync path, the fast update taken when a previous HEAD exists switched from a merge into the working tree plus a HEAD update to a single quiet detached `checkout --progress --detach FETCH_HEAD^{commit}`, still with `core.checkStat=minimal` and `core.trustctime=false`.

**Details**

- Gated on the `CCR_DELTA_RESET` environment variable; when it is unset the code goes straight to the `reset --hard FETCH_HEAD^{commit}` path, which is unchanged.
- The precondition is now a plain `rev-parse --verify --quiet HEAD^{commit}` probe rather than resolving and sha-validating the old HEAD.
- The pre-reset `update-ref --no-deref HEAD HEAD` now runs only when the fast path is not taken or as part of the fallback, and an aborted checkout is rethrown instead of falling through to the hard reset.

**Evidence**

`CCR_DELTA_RESET`, `core.trustctime=false`

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

### Stale prompt cancellation reports which tool calls it killed

On resume, cancelled leftover prompts are now reported by id to the headless loop.

**What**

On resume in headless and print mode, the routine that cancels permission and dialog prompts left over from a prior worker now returns the tool call ids it cancelled, and both callers pass that set into the headless streaming loop. It previously returned nothing.

**Details**

- Which prompts get cancelled, and the telemetry emitted for it, are unchanged.

**Evidence**

`tengu_resume_stale_prompt_cancel`

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

### Status line moved out of React into a standalone store

The status line is now driven by its own store with its own refresh and debounce timers.

**What**

The status line is now driven by a subscribable store rather than a React hook, with its own snapshot, cancellation, debounce and refresh timers.

**Details**

- Change detection is explicit over a named input list: token usage, permission mode, vim mode, main loop model, fast mode, effort value, thinking enabled and PR status.
- The trust prompt and the "configured but disableAllHooks is true" warning behave as before.
- Vim state is now handed in when the payload is built instead of read from a global.

**Evidence**

`Status line is configured but disableAllHooks is true`

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

### Plugin manifest reads report why they failed

Plugin manifest reads now say whether the file was missing, refused or too big.

**What**

The bounded manifest reader now returns a tagged result distinguishing a missing file, a refused read, a file over the size limit, and success with content, instead of returning nothing for every failure. A wrapper keeps the old behaviour for existing callers.

**Evidence**

`return { kind: "too-large" };`

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

### React 19 effect-event and timer helpers adopted in the UI

Transcript scrolling and timed checks were rewritten to avoid stale callbacks and overlapping runs.

**What**

Transcript scroll and seek handlers, the global-state subscriber and the sticky-prompt context moved off ref-held callbacks onto React's effect-event hook, and a self-rescheduling timer helper replaced the old interval utility, including the 30-minute update check. This removes stale-closure and overlapping-run hazards.

**Evidence**

`useEffectEvent`

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

### Repo metadata distinguishes GitLab and Bitbucket

Your repo's host is now recorded as GitLab or Bitbucket by name instead of one catch-all label.

**What**

A new classifier maps a git remote's host to "github", "gitlab", "bitbucket" or "other", so repo metadata that previously recorded one lumped "non_github_host" now names GitLab and Bitbucket separately. A companion helper pulls the hostname out of both SSH-style and URL-style remotes.

**Evidence**

`bitbucket.org`

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

### Process-wide state moved into class instances

Lots of shared process-wide state moved into per-instance holders; nothing changes for you.

**What**

MCP connection and cache maps, child-process tracking with its exit-time SIGTERM sweep, the transcript-recording hook, several path and homedir lookup caches, the plans-directory lookup and a text-width cache all moved from module-level globals into class instances reached through accessors, with ad-hoc `reset()` methods dropped in favour of releasing the holder. Nothing changes for users.

**Evidence**

`killAll`

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

### Session telemetry records whether todo tools were on

Session telemetry now records whether todo tools were enabled for you.

**What**

A `todoTools` field, from the same check that gates the Task tools, joins the cached session and permission-context telemetry payload next to fields like `replBridgeEnabled` and `agentDefinitions`.

**Evidence**

`todoTools`

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

### More shared state moved out of module globals

Transcript storage, session pid tracking and skill caches became per-host rather than process-wide.

**What**

Session transcript storage, the pid-file registry that tracks concurrent sessions, the bundled and user skills root caches, the MCP live client and command/resource-template lists, and the changelog cache all became per-host objects rather than process-wide ones. Test-only setters were exported for the transcript store.

**Evidence**

`setRemoteIngressUrlForTesting`

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

### Process-wide caches scoped per host

More process-wide caches are scoped per host, which matters only for embedded multi-session hosts.

**What**

A batch of module-level singletons became per-host state looked up through a WeakMap on the current host object, which matters only for embedded hosts running several sessions in one process.

**Details**

- Moved: the temp-directory cache (Claude temp dir, child-process temp dir, bundled skills root, per-session scratchpad dirs, trusted symlink equivalences, with a reset), concurrent-session registration state, and the cached CHANGELOG contents.
- The MCP client's memoized tool, resource, resource-template and command list caches moved off a fixed max-size argument into the shared MCP state object.
- The background Bash task-output registry moved from static class fields to a module-level registry with register, unregister, startPolling and reset.

**Evidence**

`scratchpadDirBySessionId`

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

### REPL mount-time hooks collapsed into one call

About twenty separate startup hooks now run as one call when the session mounts.

**What**

The main REPL used to run about twenty separate setup hooks when it mounted, covering MCP clients, IDE installation status, the model and dynamic MCP config. They are now one aggregate call plus a few leftovers. No behaviour change is stated, but the amount of work wired up at REPL mount is now visible in one place.

**Evidence**

`ideInstallationStatus`

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

### Remote IO writes inbound data directly

Remote input is written straight through, dropping the guard for writes after close.

**What**

The remote transport now pushes received server events and piped stdin straight into the input stream. The wrapper that guarded against writes arriving after the stream closed, and the telemetry event it fired once per lane when that happened, are both gone.

**Evidence**

`[remote-io] keep_alive sent`

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

### Remote session control requests go through one dispatch object

Remote permission prompts and dialogs now route through one dispatch-and-cancel object.

**What**

Permission prompts and user dialogs raised during a remote session now go through a single dispatch-and-cancel object instead of separate callbacks. Whether anything about the behaviour changes for a user is not settled by the code; the request and cancel paths look equivalent.

**Evidence**

`[useRemoteSession] User dialog request cancelled: `

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

### SSE transport state checks inlined and one error event dropped

An MCP event-stream error report is no longer emitted anywhere in this build.

**What**

The MCP server-sent-events connection replaced its closed-state helper with inline checks, and the stream-read path now bails when its abort signal fires. The `cli_sse_close_handler_threw` event is no longer reported anywhere in this build; `cli_sse_stream_read_error` is unchanged.

**Evidence**

`cli_sse_stream_read_error`

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

### Remote-control requests go through a dispatcher

Remote permission requests get drained through a dispatcher before disconnect; logs are unchanged.

**What**

Permission control requests in remote sessions now go through a dispatch object with cancel and drain calls instead of a raw send/cancel pair, with two dispatchers drained before disconnect. Nothing a user sees changes, including the log lines.

**Evidence**

`[useRemoteSession] Cleanup - disconnecting`

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

### REPL bridge accessors dropped from the tool-use context

Tools no longer receive REPL context accessors; the flag is read directly instead.

**What**

The context passed to tools no longer carries the REPL context getter and setter; the surrounding code reads the outbound-only flag from the store directly. A smaller internal surface, with no visible change.

**Evidence**

`setWebBrowserSlice`

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

### Plugin subcommand help text comes from one table

Plugin subcommand descriptions come from the shared table that also hides early-access entries.

**What**

`plugin update`, `plugin details`, `plugin enable`, `plugin marketplace update` and their siblings no longer carry their own inline description strings; all of them read from the shared table that also hides early-access entries, so help text and the subcommand listing cannot drift apart.

**Evidence**

`"Update marketplace(s) from their source - updates all if no name specified"`

- Area: Plugins
- Names: `plugin update`, `plugin details`, `plugin enable`, `plugin marketplace update`
- Tier: Under the hood
- Useful: 1/5
- Signal: 2/5

### Kill ring and two other UI state pieces become per-session contexts

The cut/yank buffer and two other input state pieces are now created per session.

**What**

The emacs-style cut/yank buffer used by the input box is now created per session and passed into the terminal UI root, instead of being one buffer shared at module level. A scroll-chrome context and an input-selection bridge got the same treatment.

**Details**

- All three hooks now throw if used outside their provider rather than silently falling back to a module default.

**Evidence**

`useKillRing cannot be called outside of a <KillRingProvider /> (mounted around every Ink root by src/ink.ts)`

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

### Version bumped to 2.1.233

The version reported by the banner, /doctor and update checks is now 2.1.233.

**What**

The build stamp compiled into every call site moves to version 2.1.233 with a new build time and commit hash, which is what the version banner, `/doctor`, update checks, npm view, user-agent strings and telemetry now report.

**Evidence**

`BUILD_TIME: "2026-08-14T17:21:48Z"`, `"2026-08-14T17:21:48Z"`

- Area: Internals
- Names: `/doctor`
- Tier: Under the hood
- Useful: 2/5
- Signal: 1/5

### Shutdown collapsed to a single cleanup drain

Exiting no longer runs a separate one-second cleanup pass before the main one.

**What**

One of the three process-wide cleanup registries was removed and its work folded into the main one, so exit no longer runs a separate 1000ms pre-cleanup drain before the main one.

**Details**

- Exit now awaits a single cleanup drain with the existing 2000ms budget.
- The relaunch path lost its own pre-cleanup drain step and awaits one 2000ms drain before flushing analytics.
- The separate pre-exit flush registry is untouched.

**Evidence**

`cleanup timeout`

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

### Kill ring is shared through app-level context

The emacs cut-and-yank buffer is now shared across input boxes instead of one per box.

**What**

The emacs-style cut-and-yank buffer is now provided once at the app root and read by input components from there, instead of each input owning its own. A default one is created if nothing is passed in.

**Evidence**

`killRing`

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

### Permission-mode updates share one setter

Permission mode changes now all run through one shared setter instead of duplicated code.

**What**

Several components that each reimplemented the same update-and-emit logic for tool permission state now call one shared helper, and the REPL drops its own local mutation on mode change. Same behaviour, fewer copies.

**Evidence**

`toolPermissionContext`

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

### The plans directory is resolved once and cached

Your plansDirectory setting is resolved and validated once, then cached.

**What**

Resolving the `plansDirectory` setting, including the check that it sits inside the project root, moved into a small object that remembers the answer and can be reset. Same validation error and same fallback when the path is outside the root.

**Evidence**

`plansDirectory must be within project root: `

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

### Shutdown flushing goes through a drain registry

Exit now waits on one drain of registered flushers instead of a hardcoded timeout race.

**What**

Two exit paths dropped their hardcoded sequence of racing one flush against a one-second timeout and then running the second. Both now await a single drain over whatever flushers registered themselves.

**Evidence**

`async function Pbr() {\n await H1c.drain();\n}`

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

### Startup notifications rebuilt as a descriptor list

Startup notices were rebuilt as one driven list; the messages you see are identical.

**What**

Plugin autoupdate, marketplace checks, settings errors, rate-limit and usage-credit notices, teammate lifecycle and the rest were rewritten from separate React hooks into a list of `{ id, deps, setup }` descriptors run by one runner that compares dependencies and re-runs only what changed. Text and behaviour are unchanged.

**Details**

- One entry, `rollout-cohort-notice`, is wired to a setup function with an empty body, so it does nothing in this build.

**Evidence**

`id: "plugin-autoupdate"`

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

### MCP connection caches folded into one lazily created object

MCP connection and tool caches were merged into one lazily created object.

**What**

The separate module-level maps for MCP connections, tool, resource and command lists, in-flight reauth and live clients are now fields on a single object created on first use and registered with the global cache registry. Connection reuse and live-client bookkeeping read through it.

**Evidence**

`liveClients = new Set();`

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

### Status line payload takes vim enablement as an argument

Your status line script gets the same JSON; vim state is now passed in explicitly.

**What**

The builder for the JSON handed to a custom status line command now receives a `vimModeEnabled` argument from its caller and emits the vim block from it, rather than reading a global at render time. The JSON your script sees is unchanged.

**Evidence**

`vimModeEnabled`

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

### Persistence-suppressed notice now fires once per host

The "persistence suppressed" warning now shows once per host instead of once per process.

**What**

The once-only guard on the warning that session persistence is suppressed moved from a single process-wide latch to one keyed by the current host, and can no longer be re-armed.

**Details**

- The latch class keeps only its claim method; the reset method and the call that reset it when session storage was re-created were both removed.

**Evidence**

`tengu_persistence_suppressed`

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

### Startup notifications rebuilt as a declarative registry

A dozen startup notices became one declarative registry; every message reads the same.

**What**

More than a dozen startup notices are no longer separate React hooks. Each is now a descriptor with an id, a dependency function and a setup function, collected into one array and run by a single driver that re-runs a descriptor only when its dependencies change, cleaning up first. All the messages you see are unchanged.

**Details**

- Covers advisor consent and experimental banner, auto-mode-unavailable, chrome extension check, compliance taint, monitoring notice, model deprecation warning, the three fast-mode notices, IDE disconnected and install error, install issues, LSP initialization and teammate lifecycle.

**Evidence**

`id: "lsp-initialization"`

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

### MCP stdio shutdown bookkeeping moved into its own class

Bookkeeping about force-killed MCP server processes moved into its own small store.

**What**

The record of whether a stdio MCP server process was force-killed or survived shutdown now lives in a small purpose-built store rather than an inline map with eviction logic. Same 64-entry bound and same recorded values.

**Evidence**

`hard_closed`

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

### Plugin command help text comes from one table

Plugin command help text now comes from one table, aliases included, so it cannot drift.

**What**

The `claude plugin` subcommands and `eval init` now read their usage strings, aliases and descriptions from a single shared table instead of repeating them where each command is registered. Help text is unchanged, including aliases `i`, `remove`, `rm` and `autoremove`.

**Evidence**

`usage: "plugin install <plugin>"`, `Remove a configured marketplace`

- Area: Plugins
- Names: `claude plugin`, `eval init`
- Tier: Under the hood
- Useful: 1/5
- Signal: 1/5

### MCP client and other module globals move into per-key holders

MCP client lists and other module globals moved into registries created on first use.

**What**

Both MCP client modules replaced their module-level set of live clients and their connection caches with a single registry created on first use. Several other module globals, including shell process tracking, path caches and ignore-file state, moved to per-key holders backed by a weak map.

**Evidence**

`aJe().liveClients`

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

### Git HEAD check asks git for a yes or no

The check for whether your repo has any commits now just asks git directly.

**What**

The worktree code checks whether HEAD points at a commit by letting git succeed or fail quietly, rather than capturing the commit id and testing it against a hex pattern.

**Evidence**

`"--verify",`

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

## Removed

### Bash input-redirection permission check removed

Bash commands reading from a file with `

**What**

The Bash permission path no longer inspects `<` input redirection targets. The loop that skipped `/dev/null` and evaluated each redirection source as a file read, along with its deny and ask messages, is gone.

**Details**

- "Input redirection from" appeared three times in the previous build and does not appear here; the matching "Output redirection to" checks are unchanged.
- Whether an equivalent read check now runs somewhere else in the permission path is not determinable from the shipped code.

**Evidence**

`Input redirection from`

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

### sort output-file targets are no longer extracted as paths

The `sort` command's output-file flags are no longer treated as write targets for permissions.

**What**

The special handling that pulled `--output=FILE` and `-oFILE` targets out of a `sort` command was dropped, so `sort` is now treated like `cat`, `head`, `tail` and `wc` with plain positional paths only.

**Evidence**

`sort: JM,`

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