# Claude Code v2.1.242

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

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

Several of the 99 unreleased entries center on plugin hook modules written in JavaScript: a plugin's `hooks/hooks.json` can name a module that registers hooks in code and talks to the session through a `$` object, but the loader refuses unless `tengu_plugin_hooks_modules` is turned on remotely, and its built-in value is false. Nine parts of the terminal interface now ask a rendering hook whether it wants to draw them first, and since only those disabled modules can answer, everything looks the same. Cloud work landed dark too: a subsystem that runs your machine's hooks on behalf of a `claude --cloud` session sits behind a helper that always returns false, and the git-aware directory sync for cloud containers needs `CLAUDE_CODE_DIR_SYNC_GIT` set. Also present but unreachable are a per-app computer-use tool family that drives one window through Accessibility, and live artifact rooms whose availability check reads a null client.

Two managed settings are the headline of the 219 shipped entries. `modelPicker` lets an administrator supply an ordered list of models for `/model` and hide the built-in lineup entirely, and `modelPricing` replaces list-price cost math in `/cost`, the status line, `--max-budget-usd` and the telemetry metrics with contracted per-model rates. `/cloud-plugins` asks whether cloud sessions may use your enabled plugins, a separate prompt covers sending CLAUDE.md, rules and preferences, and `--no-home-settings` suppresses the settings copy for one launch. `/plugin-types` writes a `claude-code-mcp.d.ts` typing every connected MCP server's tools. Plugin hooks also gained a budgeted in-process API covering toasts, audio playback, and queueing prompts that cannot start with `/`.

Startup no longer breaks when another running Claude Code holds the local socket path: a leftover from a dead process is deleted, and a live sibling's socket causes a retry at a randomised nearby path. A cached auto-mode killswitch read from disk is now treated as enabled and re-checked against the remote config service, so a stale file cannot keep auto mode off. Computer-use `wait`, typing and drags now stop on interrupt, and a throw inside `computer_batch` fails only that action. Turn duration is finally printed when background swarm tasks outlive the turn, and the screenshot text explaining which apps were hidden has been removed.

The bundle was rebuilt so deferred loads became real dynamic imports; setting `CLAUDE_CODE_LEGACY_BUNDLE` to a truthy value restores the old single-chunk shape. A control message can now mark sandbox auto-allow as suspended, which makes permission checks behave like plan mode without changing the permission mode itself. The socket between local sessions gained `yield_artifact_replies`, `unyield_artifact_replies` and `artifact_replies_yielded` for moving Artifact comment replies between sessions.

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

### Plugins can drive the session directly, with per-plugin budgets

Code-based plugin hooks get a full in-process API to drive the session, budgeted per plugin.

**What**

Plugin hooks written in code now get an in-process API for talking to the running session: status lines and toasts, asking the user a question, reading session messages and turn count, listing and spawning agents, calling and listing tools, registering new tools, and calling MCP servers. Every one of those is budgeted so a misbehaving plugin cannot flood you. Nothing in this build sets a flag for the surface as a whole, so it is present for everyone on 2.1.242.

**Details**

- Toasts past the eighth in a session are dropped to the debug log, with a single warning toast to say so.
- Asking the user a question is refused past a per-session count and within a minimum interval of the previous question.
- Spawning agents is capped both per session and by how many run at once.
- Tool registration is capped per plugin, refuses reserved MCP server names, and refuses to shadow an MCP server you already have configured. Success and failure are measured, including the cases where no registrar exists yet and where the tool is registered but not yet visible.
- The build also ships a separate worker script entry point for running these hooks outside the main process.

**Evidence**

`plugin_function_hooks_register_tool`

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

### A per-app computer-use tool family that drives one window without taking the screen

A set of tools can drive a single app window instead of your whole screen.

**What**

A new tier of computer-use tools operates a single application through Accessibility instead of taking over the whole display: app_screenshot, app_click, app_type, app_key, app_scroll, app_drag, app_menu, app_list_windows, app_ax_find, app_batch, app_release, app_bring_to_current_space and list_apps. They only work when the host program running Claude Code provides an app-scoped implementation; otherwise every call returns "Per-app background tools are not available in this build."

**Details**

- Each call re-checks an enabled flag, so remote configuration can switch the family off part-way through a session. When that happens the message is "Per-app background control (the app_* tools) was just turned off for this device by a remote configuration change."
- app_screenshot returns a JPEG plus an <ax-summary> listing interactive elements; the indices in that summary can be passed back as element_index to click or type into a specific control.
- None of these tool names existed in v2.1.241, and the implementation module moved position in the bundle.

**Evidence**

`Per-app background tools are not available in this build.`

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

### A plugin hook can block a subagent from launching

A plugin hook can block a subagent from starting or change which model it uses.

**What**

Before spawning a subagent, Claude Code now runs an `agent.spawn` hook carrying the tool use id, prompt, subagent type, model, permission mode, and whether the spawn is background, forked, and its working directory. A deny aborts the launch and is recorded as `subagent_spawn_denied_by_hook`. The hook may also swap the model the subagent runs on.

**Evidence**

`Subagent spawn denied by a plugin: `

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

### Git-aware directory sync for cloud sessions, off unless an env var is set

A git sync engine keeps a cloud container's checkout in step with yours, off unless an env var is set.

**What**

The cloud worker gains a large git sync engine that keeps the container's checkout in step with yours: it snapshots the working tree, merges your bundle, replays the agent's unpublished commits onto your HEAD, and parks them under session refs when the replay conflicts. None of it runs unless `CLAUDE_CODE_DIR_SYNC_GIT` is set, and even then it checks that directory sync itself is enabled before arming.

**Details**

- Merging uses `git merge-tree --write-tree`; staging is mirrored across as well as commits.
- It writes long explanatory notices to the agent for branch name collisions, oversized files, nested repositories inside the checkout, and containers that were recreated from the session's starting state.
- The factory returns null immediately when the env var is absent.
- Scale of the change: telemetry names beginning `dir_sync_git` go from 1 occurrence in v2.1.241 to 79 here, roughly 4,000 lines of new code.

**Evidence**

`Directory sync: this checkout was RECREATED from the session's starting state (the cloud container was replaced).`

- Area: Cloud Sessions
- Names: `CLAUDE_CODE_DIR_SYNC_GIT`, `--write-tree`
- Tier: Not switched on
- Useful: 3/5
- Signal: 5/5
- Present in the build but not switched on

### Groundwork for running tools on another machine over the device bridge

Tool calls and Bash can be routed to another machine over a device bridge.

**What**

A new client-side layer can route a tool call, Bash or an attached machine's own MCP tools, to a different machine reached through the device bridge. It negotiates a protocol version, forwards permission prompts back into your session, and reconciles calls that dropped or timed out by asking the host what happened. None of it exists in the previous build.

**Details**

- Reconciliation outcomes are reported as replayed, still running, host restarted, or not received.
- Permission asks forwarded back include automatic-mode classifier verdicts.
- New telemetry events: `tengu_remote_tool_forward`, `tengu_remote_tool_targets`, `tengu_remote_tool_classifier` and `tengu_remote_tool_serve_hook_held`.
- Users would see text listing attached machines, plus errors for an unreachable host or a protocol mismatch.
- Every entry point is behind the same boolean check: the attached-machines notice is only shown when it passes, the runtime is only loaded when it passes, and each routed call re-checks it and fails with the code `gate_off`. The name of that check and what it defaults to are not resolvable from the build.

**Evidence**

`The attached machine could not be reached through the device bridge right now; the call did not run. Try again shortly.`

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

### Hooks running on your machine on behalf of a cloud session, switched off in this build

Cloud sessions could trigger hooks that run on your own machine, but the path is hard-disabled.

**What**

A large new subsystem lets the machine you launch `claude --cloud` from register its local hooks with a cloud session and then run them locally when the cloud session fires them, translating paths between the container and your machine. It cannot run here: the code that creates the session is behind a helper that unconditionally returns false in this build, so none of its notices can appear.

**Details**

- Includes lease-based registration, a servicer that executes hooks locally, replay and dedup caches, and staging of verified copies of hook scripts.
- Also skipped for view-only sessions even if the gate were on.
- The worker side of this (`register_device_hooks`) already existed in 2.1.241; the machine-side half is new.

**Evidence**

`[deviceHooks] device hook session created for this attach`

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

### Plugin hooks modules: a JavaScript engine for plugin hooks, off unless enabled remotely

Plugin hooks.json gains a modules key for code-registered hooks, off unless enabled remotely.

**What**

A plugin can now ship a JavaScript module that registers hooks in code with `register(on, options)`, instead of only declarative matchers in hooks.json. That file gains a `modules` key alongside `hooks`, and either or both may be present. The engine loads and evaluates the module, dispatches each hook event through a chain with `next()`, reports failures, and reloads or prunes when a plugin is disabled. It is controlled by `tengu_plugin_hooks_modules`, whose built-in fallback is off, so nothing loads unless server config turns it on.

**Details**

- A plugin manifest may declare a hooks module path, and hook config parsing now returns both the matchers and the modules.
- The gate also drives a new plugin-types entry in the capability and notice map.

**Evidence**

``hooks.json must have `hooks` (the hook matchers) or `modules` (hooks modules), or both``, `var Lnt = "tengu_plugin_hooks_modules"`

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

### Plugin modules with a `$` API that can hook events and redraw the terminal UI

Plugins can install code that intercepts tool calls and repaints parts of the terminal interface.

**What**

A plugin can load a hook module that receives a `$` object exposing event chains and host capabilities, letting it intercept tool calls, prompts and agent turns, and substitute the props Claude Code uses to draw its own interface. Message rendering was rewritten so that named draw sites can be targeted individually, and modules run inside their own sandboxed globals so host objects and host stack frames never reach plugin code. Nothing switches this on or off, and a site is only overridden when a loaded module declares a handler for it, so a stock install behaves and renders exactly as before.

**Details**

- Event chains available on `$` are `tool.call`, `tool.describe`, `prompt.submit`, `prompt.section`, `agent.spawn`, `turn.start`, `turn.step`, `turn.complete` and `ui.render`, with `PreToolUse` also carried by the same dispatch layer.
- Capabilities on `$` are `model.complete`, `mcp.call`, `audio.play` and `audio.speak`, the `session.*` group, `tool.list` and `tool.register`, `ui.toast`, `ui.status`, `ui.log` and `ui.notice`, `fs.readFile`, `fs.writeFile`, `fs.listDir`, `fs.exists` and `fs.stat`, `store.get`, `store.set`, `store.delete` and `store.keys`, and `http.fetch`.
- Each capability enforces its own policy check rather than deferring to one shared permission switch.
- Every call runs its handler chain and fails if nothing at the bottom of the chain terminates it, reporting `no core at the bottom of the chain (the site did not install one and the call supplied none)`.
- The draw sites a `ui.render` handler can target are `UserMessage`, `AssistantMessage`, `ToolUse`, `ToolResult`, `TurnDuration`, `Spinner`, `InfoNotice`, `SessionMode` and `AskUserQuestion`.
- Modules execute against a replacement set of globals: AbortController and AbortSignal, TextEncoder and TextDecoder limited to UTF-8, URL and URLSearchParams, atob and btoa, a hand-written structuredClone, `crypto.subtle.digest`, `randomUUID` and `getRandomValues`, `performance.now`, and classic-runtime JSX helpers that emit `JSX element <` markers.
- Errors thrown by the host are re-thrown as errors belonging to the plugin's own environment, with stack frames outside the plugin directory trimmed away.
- The hook runtime ships as a bundled worker whose path now appears in build metadata alongside version, build time and git sha.
- A module that fails to load is reported as `plugin_function_hooks_load` with a declaration-failed reason.

**Evidence**

`/$bunfs/root/src/plugins/functionHooks/hooks-worker/hooks-worker.js`, `no core at the bottom of the chain (the site did not install one and the call supplied none)`, `JSX element <`

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

### Plugins can ship JavaScript hook modules that run in a shared Bun worker, built but switched off

Plugins could ship JavaScript hook modules running in a worker, but loading is refused here.

**What**

A plugin's `hooks/hooks.json` may now name a JavaScript module that registers hooks in code rather than as shell commands, and that module runs in a background worker thread shared by all plugins, with crash containment, a heartbeat and a broad capability object passed in. Nothing loads in this build: the loader refuses with a message saying the rollout flag is off, the flag `tengu_plugin_hooks_modules` has a built-in value of false, and no plugin bundled with Claude Code declares a hooks module, so even a server-side switch would only enable third-party plugins.

**Details**

- The `modules` array in `hooks.json` names exactly one module per plugin, resolved relative to `hooks.json` and exporting a `register` function; a second entry is refused.
- The worker's entry point is baked into the binary as a build constant pointing at an internal hooks worker script, falling back to a relative `hooks-worker.ts` path when that constant is missing.
- Host and worker speak a framed protocol covering load, dispatch, build, call, ping and flush, and the host heartbeats the worker.
- A hook that spins without yielding, or a plugin that repeatedly ignores its abort signal, causes the worker to be respawned and that plugin to be unloaded; repeated crashes that cannot be pinned on one plugin switch hook modules off for the rest of the session.
- Setting the environment variable `CLAUDE_CODE_HOOKS_SAME_THREAD` runs hooks in the main process instead of the worker.
- Each module receives a `$` object of named capabilities: `$.ui` with ask, toast, status, notice, log and resolve; `$.model` with complete plus classify, which builds its own classifier prompt and maps the reply back to a label; `$.store` with get, set, delete and keys under a character limit; `$.clock` with sleep, after and every returning cancellable timers; `$.fs` with readFile, writeFile, listDir, exists and stat; plus `$.http.fetch`, `$.mcp.call`, `$.tool.register/list/call`, `$.agent.spawn`, `$.prompt.submit` and `$.audio.play/speak`.
- Arguments are validated per capability and errors name the offending plugin; tool schemas and interface text have size limits, and a hook can claim work whose promise nobody is waiting on.
- Telemetry is already wired for `plugin_function_hooks_load`, `plugin_function_hooks_worker` and `plugin_function_hooks_register_tool`, with respawns recorded on the worker event and a plugin unloaded by a crash recorded on the load event as "crashed_worker".
- The same flag also reveals a plugin-types row in the doctor output and in the plugin dialog listing.

**Evidence**

`tengu_plugin_hooks_modules`, `hooks worker spawned (one for every plugin)`, `hooks.json `modules` names one hooks module per plugin; a second entry is refused`, `$.audio.play with loop needs options.signal: the clip repeats until it aborts`

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

### Sandbox auto-allow can be suspended by a control message

A control message can suspend sandbox auto-allow, making the session behave like plan mode for permissions.

**What**

A new application state, set by an incoming control message rather than any local flag, marks sandbox auto-allow as suspended. While set, permission checks treat the session like plan mode, so commands inside the sandbox stop being auto-allowed without the permission mode itself changing.

**Evidence**

`sandbox_auto_allow_suspended`

- Area: Sandbox
- Tier: Not switched on
- Useful: 3/5
- Signal: 5/5

### cloud-plugins added to the slash-command map with a per-machine consent store

A cloud-plugins command exists with a per-machine record of whether your plugins may be used in cloud sessions.

**What**

The command table gained a `cloud-plugins` entry, absent in 2.1.241, backed by a store that remembers per machine whether your enabled plugins may be used in cloud sessions. A failed read of that store is logged rather than treated as consent.

**Evidence**

`cloud-plugins consent read failed`

- Area: Plugins
- Names: `/cloud-plugins`
- Tier: Not switched on
- Useful: 3/5
- Signal: 5/5

### /cloud-plugins and a matching opt-in for sending this machine's settings to cloud sessions

You now decide separately whether cloud sessions may use your plugins and your local settings.

**What**

Two separate consents now decide what a cloud session inherits from this machine. `/cloud-plugins` asks whether cloud sessions may use the plugins you have enabled here. A second prompt, "Use this machine's settings in cloud sessions?", covers CLAUDE.md, rules, output styles, preferences and portable permission rules. Both default to unanswered, and until you answer nothing is sent. A new hidden flag `--no-home-settings` suppresses the settings copy for a single launch.

**Details**

- `/cloud-plugins` offers "Yes, use my enabled plugins in cloud sessions" / "No, keep them on this machine only" / "Not now", with "Not now" focused by default. The answer is stored per machine and logged as `tengu_cloud_plugins_consent` with the new and previous choice. Undecided sessions report `no_consent` and forward nothing; elsewhere a nudge reads "Your plugins are not used in cloud sessions from this machine yet: run /cloud-plugins in claude on this machine to decide."
- The command is offered only when a capability check passes and `CLAUDE_CODE_DISABLE_PLUGIN_FORWARDING` is unset. The forwarding machinery itself already shipped in 2.1.241; this build adds the way to say yes.
- Settings forwarding is stored as `remoteHomeSettingsMode`, values `forward` / `keep_local` / unanswered, with prompt and change events `tengu_home_settings_mode_prompt_shown` and `tengu_home_settings_mode_set`. Forwarding requires all of: the account having settings-to-cloud enabled server-side, a launch that may forward, and the mode being exactly `forward`.
- A `/config` row named "Use this machine's settings in cloud sessions" can turn it off but not on: enabling from the settings list returns "Turn this on from the /config panel, which shows what will be sent", which lists what would be sent. If the account flag is off, turning it on is refused with "Could not turn this on: forwarding settings to cloud sessions is not enabled for you right now".
- On the cloud side the forwarded pack is signed, schema-checked and written under the config home with symlink, hard-link and parent-escape checks, then announced as "Started with settings from your machine: ". With the account flag off that path throws "settings forwarding is not enabled for this account" and does nothing.
- `--no-home-settings` requires `--cloud` or `--environment` and is rejected otherwise.
- Off-switch reasons you may see reported: `launch_flag`, `flag_off`, `declined`, `no_consent`, `unbound`, `not_seeded`.
- Plugin installs that a reload stopped waiting for now report their late outcome (`tengu_cloud_plugins_late_install`).

**Evidence**

`Use this machine's settings in cloud sessions`, `Choose whether cloud sessions use the plugins enabled on this machine`, `Use this machine's settings in cloud sessions?`, `Your plugins are not used in cloud sessions from this machine yet: run /cloud-plugins in claude on this machine to decide.`, `forwardHomeSettings`, `settings forwarding is not enabled for this account`, `Could not turn this on: forwarding settings to cloud sessions is not enabled for you right now`, `"Choose whether cloud sessions use the plugins enabled on this machine"`, `"Don't send this machine's settings (CLAUDE.md, rules, output styles, preferences, portable permission rules) into the cloud session this launch creates or attaches to. Requires --cloud or --environment."`, `tengu_cloud_plugins_late_install`, `forwardHomeSettings: e.homeSettings !== !1`, `tengu_home_settings_mode_prompt_shown`

- Area: Cloud Sessions
- Names: `/cloud-plugins`, `--no-home-settings`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### /login asks how you want to sign in to a Console account

/login now offers signing in to your Console account instead of only creating an API key.

**What**

Choosing "Anthropic Console account" in `/login` no longer jumps straight to creating an API key. A second screen offers "Sign in with your Console account (recommended)" or "Create an API key (legacy) - adds a key to your Console workspace", plus "Go back". The sub-menu only appears on the first-party API with no managed policy settings, no pinned login organization, and login not forced to claude.ai; otherwise the old API-key path runs unchanged.

**Details**

- The check is entirely local (settings and environment), not a server-side flag.
- Picking the non-API-key path on a machine with pinned settings fails with "Settings on this machine pin the login method or organization, so signing in without an API key is not available here."
- Two new telemetry events distinguish the choices: `tengu_oauth_console_token_selected` and `tengu_oauth_console_api_key_selected`. Neither exists in 2.1.241.

**Evidence**

`tengu_oauth_console_token_selected`

- Area: Auth
- Names: `/login`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### `plugin eval` can mock a plugin's MCP servers, with docs to match

Plugin evals can swap a plugin's MCP servers for fakes defined in a mocks folder.

**What**

Evaluation runs of a plugin can now swap that plugin's real MCP servers, the external tool servers a plugin declares, for fake ones defined as files under `<eval dir>/mocks/`, so an eval exercises tool calls without starting anything real. The mode is controlled by a new `--mocks` flag that defaults to `record`, and the bundled quick reference for `plugin eval` now carries a Mocks section describing the whole layout. The commands remain early access.

**Details**

- `--mocks record` (the default) serves canned responses from `mocks/<server>/<tool>.md`; `--mocks off` spawns the real servers instead; any other value exits with "Error: --mocks must be record or off".
- A responder file may carry an `expect:` guard on the input, which aborts the run with a record of the server, tool and reason when the call does not match.
- Responders also support `error: true` to return a failure, and `type: agent` with its own abort conditions.
- `mocks/<server>/_tools.json` supplies the saved tool list; without it a tool is served with a permissive schema and no description.
- `_server.md` lets a single responder answer several tools for that server.
- Directory and tool names must be valid tool-name segments, `__` is rejected because it separates server name from tool name, and a directory is capped at 256 entries.
- Mocks can shadow a server that the plugins under test declare, or stand alone with no real counterpart.
- Grading gains `mock_calls` as a source, so a grader can assert on which mock tools were called, and the LLM judge can now grade image files.
- The reference synopsis also lists `--report`, `--publish-report` and `--no-publish`.
- Enabled per organization: when your organization is not enabled, the commands print an early-access line and exit with status 1.

**Evidence**

`Mock stand-ins for MCP servers, from <eval dir>/mocks/ (record | off; default: record`, `--mocks record|off`

- Area: Plugin Eval
- Names: `--mocks`, `plugin eval`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Effort display names ultracode and warns about the environment override

The effort line lists ultracode and warns when your environment variable is overriding your choice.

**What**

The effort line now shows "ultracode" as a level, says when an effort setting applies to the current session only, and warns when `CLAUDE_CODE_EFFORT_LEVEL` in your environment is overriding the effort you chose, telling you to clear it.

**Details**

- The override clause appears only when that environment variable is set.

**Evidence**

`overrides effort this session; clear it and `

- Area: Effort
- Names: `CLAUDE_CODE_EFFORT_LEVEL`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Four Artifact page templates, plus workshop, components and diagram skills

Four Artifact page templates plus workshop, component and diagram skills, with a required design pass.

**What**

The Artifact menu now offers dashboard, report, data-table and explainer templates, with entries like "Publish a dashboard Artifact from a template". New skills add an iterative design workshop that publishes an evolving plan page with decision points readers can answer, a library of reusable page components starting with that decision component, a diagramming skill for inline SVG that reads correctly in both light and dark themes, and a design pass that is now required before writing any Artifact, Markdown included.

**Details**

- Templates point at a separate runtime-capability skill when a page needs live data, persistence, identity or file storage. Those capabilities are described as granted per user by the control plane, so what a page can do depends on the account rather than on anything set locally.

**Evidence**

`Publish a data-table Artifact from a template`

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

### New `modelPicker` setting curates the /model list

A new modelPicker setting curates or replaces the model list you see in /model.

**What**

An administrator can now supply an ordered `options` list of `{model, label?, description?}` rows that appear in `/model`, and set `replaceBuiltInOptions` to hide the built-in lineup, gateway-discovered models and `ANTHROPIC_CUSTOM_MODEL_OPTION` rather than add to them. It is honored from managed settings, `--settings`/SDK and user settings only, never from a project checkout, and the highest-precedence source wins outright with no merging.

**Details**

- Rows that fail validation are dropped one at a time with a warning; the rest of the file still applies.
- Consumed where the picker's rows are assembled, so it takes effect in the `/model` UI directly.
- Ships alongside `modelPricing`, which prices usage at contracted rates via a `multiplier` in (0, 1] and per-model `overrides` and is restricted to managed sources.
- Neither key exists in 2.1.241, and no flag gates either: writing the setting is enough.

**Evidence**

`replaceBuiltInOptions`

- Area: Models
- Names: `modelPicker`, `replaceBuiltInOptions`, `/model`, `ANTHROPIC_CUSTOM_MODEL_OPTION`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Prompts pass through a submit hook chain that can rewrite or drop them

Hooks can now rewrite or drop your prompt before it is sent.

**What**

Before a prompt is sent, it runs through two tiers of `prompt.submit` hooks, core ones and managed ones. A hook can drop the prompt, which shows a warning and runs no query, or rewrite it, which replaces the text in your message.

**Details**

- When a rewrite changes the text, @-mentions are recomputed: file, directory, MCP resource, agent and nested memory attachments are discarded and re-resolved with read state cleared.
- Bash mode, slash commands and inputs that never query skip the chain, and settle with a fixed reason naming those cases.
- A rewrite that cannot find the matching user message logs a warning saying the model will see the prompt as you typed it.

**Evidence**

`the prompt did not go through prompt.submit (a command, bash mode, an input that did not query, or a failed pass)`

- Area: Hooks
- Names: `prompt.submit`
- Tier: Use it now
- Useful: 4/5
- Signal: 4/5

### Hooks are held rather than run for tool calls served to a cloud session

Hooks for tool calls served to a cloud session can be held instead of run, turning the call into an ask.

**What**

When your machine runs a tool call on behalf of a cloud session, command, HTTP and MCP-tool hooks are no longer executed blindly. Each is checked for reachability first and can be held, with reasons such as `model_hook`, `project_configured`, `in_reach` and `unreadable`. A held PreToolUse hook turns the call into a permission `ask`; other events get a system message instead. This only applies to calls served for a cloud session, not to ordinary local hook runs.

**Details**

- A PreToolUse hook that errors or is cancelled on a served call now blocks the call instead of being skipped.
- A hook returning `permissionDecision=defer` on a served call is converted to a deny with an explicit message and a warning, because there is no resume path for served calls.
- Every hold is counted through `tengu_remote_tool_serve_hook_held`, and non-blocking events are marked `heldForServedCall: true`.

**Evidence**

`tengu_remote_tool_serve_hook_held`, `deferral is not supported for calls served to a cloud session, so nothing ran.`

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

### Sonnet 5 priced at $2/$10 per million tokens

Claude now quotes Sonnet 5 at $2 in and $10 out per million tokens, with the introductory framing gone.

**What**

The bundled model catalog and migration guide now state Sonnet 5 at $2 input and $10 output per million tokens. The earlier framing of $3/$15 with an introductory $2/$10 rate expiring on 2026-08-31 is gone. This changes what Claude tells you about cost, not what the API charges.

**Details**

- The migration guide's token advice changes with it: Sonnet 5's roughly 30% higher tokenizer count is now paired with a lower per-token rate, rather than described against unchanged pricing.

**Evidence**

`per-token pricing is lower than Sonnet 4.6: $2/$10 vs $3/$15 per MTok`

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

### A headless client for cloud sessions appears in the bundle

A full headless client for driving cloud sessions over stream-json is in the bundle.

**What**

This build contains a complete client that reads SDK host messages from stdin as stream-json and drives a Claude session running in the cloud, with separate paths for creating a session and attaching to an existing one. It holds incoming frames until the session reports `system`/`init`, keeps a ledger of what it sent so echoes can be replayed, forwards control requests and interrupts, and exits with a run status code.

**Details**

- Nothing in the previous build mentions this subsystem or its `tengu_remote_headless_client_*` telemetry, so this is where it becomes visible.
- The module identifies itself only as entry point `cloud_headless`; which command-line flag reaches it is not settled by anything in the bundle, so it may not be reachable yet.
- Running it with a terminal on stdin is refused outright.

**Evidence**

`Error: headless --cloud reads the SDK host messages from stdin as stream-json; stdin is a terminal here.`

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

### Live artifact rooms ship complete but can never switch on in this build

Live rooms on artifacts, with messages and consent, are complete but can never turn on here.

**What**

The whole live-room feature is present: joining a room when you publish, room event notifications, sending messages of up to 4 KiB on a topic, and per-artifact consent before joining. The availability check reads a null client, so it always answers no, and the room instructions and schema are left out of the artifact tool description this build assembles.

**Details**

- Artifact types are gated separately, by a snapshot taken when the tool schema is built, with independent switches for listing types, creating them and browsing the catalogue.
- That snapshot is computed from the shape of the server-provided schema plus feature checks, so which type actions a session sees is decided server-side.

**Evidence**

`return null?.isArtifactRoomEnabled() === !0;`

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

### Owner messages from MCP results can count as you speaking, currently dark

Project owner messages fetched by a tool could be replayed as if you said them, currently dark.

**What**

In Claude Code Projects, a tool result from the project fetch tools can now carry verified rows of messages from the project owner, and those get replayed into the conversation as turns that count as the user speaking. The remote setting `tengu_hearth_resolved_rows` defaults to "off" and this path requires the value to be exactly "full", so absent server config nothing here runs.

**Details**

- Rows arrive under a new result metadata key, `anthropic/hearth.rows`, alongside the existing attachment-based path. The key does not appear in v2.1.241.
- Replayed turns are flagged as harness-generated.
- New telemetry event `auto_mode_projects_owner_rows` reports one of ok, `invalid_rows`, `truncated` or `no_owner_rows`; skipping the feature reports `flag_not_full`, deduplicated once per session.
- The scan loop is wrapped so it iterates nothing unless the caller passes an enable flag, which the caller computes as a thread check plus the setting above. The setting also accepts "model_only", but only "full" turns this on.

**Evidence**

`anthropic/hearth.rows`

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

### Terminal output is routed through a rendering hook that nothing currently answers

Nine parts of the terminal ask a hook before drawing, but nothing can answer yet.

**What**

Nine parts of the terminal interface now ask a hook whether it wants to draw them before drawing them the normal way: user and assistant messages, tool calls and results, turn duration, notices, the spinner, the session mode line and the question prompt. Only a plugin hooks module can answer, and those are disabled in this build, so everything renders exactly as before.

**Details**

- A hook may return a small tree of boxes, text and basic formatting with hex colours; it is rebuilt into real terminal elements, and if the returned tree fails validation Claude Code falls back to its own drawing.
- The whole path short-circuits when no loaded hook module handles the render event, which is always the case while `tengu_plugin_hooks_modules` is off.

**Evidence**

`useRenderInput`

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

### Three new messages between local sessions for handing over Artifact replies

Local sessions gained wire messages for handing Artifact comment replies from one session to another.

**What**

The local socket that Claude Code sessions use to talk to each other gained `yield_artifact_replies`, `unyield_artifact_replies` and `artifact_replies_yielded`, the wire messages behind moving Artifact comment replies between sessions.

**Details**

- Each message has a defined shape: page slugs, a message id, a reason of `resume` or `claim`, a send timestamp, and the requesting session's working directory and tmux details.
- Before giving up the subscription, the receiver checks the requester is a live session of the same conversation, matching both session id and process id, and refuses stale requests.
- If the answer cannot be delivered back, the hand-over is reverted.
- No feature flag guards the new messages; telemetry is recorded under "artifact_comments_autoreact" and "artifact_live_subscribe".

**Evidence**

`[uds-messaging] yield_artifact_replies refused: requester is not a verified live session of this conversation`

- Area: Artifacts
- Tier: Not switched on
- Useful: 2/5
- Signal: 5/5

### Administrators and --bare can block plugin hooks from loading

Two managed settings can block plugin hooks entirely or allow only managed plugins.

**What**

Even with the rollout flag on, the loader filters which plugins may contribute hooks. Two managed settings keys control it: disableAllHooks blocks every plugin, and allowManagedHooksOnly restricts loading to managed plugins.

**Details**

- Running with --bare disables hooks entirely.
- When two plugins share a name, only the first loads: the managed one, or otherwise the one from the earlier source.
- Every exclusion is logged with the reason it was excluded.

**Evidence**

`only managed plugins run (allowManagedHooksOnly / disableAllHooks)`

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

### New `modelPricing` managed setting bills usage at contracted rates

Organizations can bill your reported spend at contracted rates everywhere costs appear.

**What**

An organization can replace list-price cost math everywhere Claude Code reports spend: `/cost`, the status line, the SDK's `total_cost_usd`, `--max-budget-usd` and the OpenTelemetry cost metrics. It takes per-model `overrides` giving input, output, cache-read and cache-write USD per million tokens, plus a global `multiplier` in the range (0, 1]. It is honored only from managed settings, and a compile failure falls back to list pricing.

**Details**

- Per its own description: "Only honored from managed settings (server-managed, MDM / OS policy, or managed-settings.json); ignored in user, project, local and --settings sources."
- The pricing table is compiled once per session; duplicate model rows produce a warning.
- Emits telemetry `settings_model_pricing` carrying `rows` and `multiplier`.
- When the config came from an untrusted origin the telemetry records a reason of `untrusted_origin` or `unverified_remote_cache`; a compile failure records `compile_threw` and list pricing is used instead.
- Neither the key nor its telemetry exists in v2.1.241. No remote flag is involved.

**Evidence**

`Price usage at your organization's contracted rates instead of list price. `

- Area: Managed Settings
- Names: `modelPricing`
- Tier: Use it now
- Useful: 3/5
- Signal: 4/5

### Plugins can play sounds and speak, within per-session budgets

Plugins can play sounds and speak aloud, capped per session, on macOS and browser hosts only.

**What**

Plugins get two new calls, `$.audio.play` and `$.audio.speak`. Playback works only where there is a player: a browser host, or macOS via `afplay` for clips and `say` for speech. On Linux and Windows terminals playing a clip does nothing and speaking is rejected. Each plugin has a cap on total plays per session and on concurrent plays.

**Details**

- A clip is exactly one of `asset` (a path inside the plugin's own directory), `url` (fetched through the same URL safety check used elsewhere), or `base64` plus `mime`. There is a byte cap on the clip.
- `loop` is optional and requires a signal to stop it. `gain` is clamped to a maximum.
- Speech prefers the browser `speechSynthesis` API when present, then macOS `say`.

**Evidence**

`$.audio.play: clip must be { asset }, { url } or { base64, mime }`

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

### Plugins can queue a prompt into the conversation

Plugins can drop a prompt into your conversation, labelled as theirs and rate-limited.

**What**

`$.prompt.submit` lets a plugin put a prompt into the running conversation, tagged as coming from that plugin rather than from you. Text starting with `/` is refused so a plugin cannot fire a slash command in your name, and attachments are refused. Each plugin has a per-session submission count and a minimum gap between its prompts.

**Details**

- Requires an interactive session attached to the process. Headless `-p` runs and the SDK reject the call.
- The submitted prompt is marked with the plugin's name as its origin.

**Evidence**

`$.prompt.submit submits a prompt to the model; a text beginning with / would run a command as the user`

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

### A plugin may relabel a question prompt but not change what it asks

A plugin cannot secretly change what a question asks you; tampered prompts fall back to the original.

**What**

If a plugin rewrites the questions in an AskUserQuestion prompt, the rewrite is re-parsed against the tool's schema and compared with what was actually asked. A different number of questions, a missing question, a flipped multi-select flag, changed option labels or previews, or two questions with the same text all cause the original to be drawn, with a warning naming the specific violation.

**Details**

- Only applies when a plugin supplies an override for that render site.

**Evidence**

`ui.render (AskUserQuestion): a rewrite may relabel the questions but not `

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

### Artifact comment replies can move to another session of the same conversation

Automatic replies to Artifact comments can move between sessions of the same conversation.

**What**

Only one session at a time answers comments on a published Artifact automatically, and that job can now move between sessions of the same conversation. A session that resumes the conversation or republishes the Artifact takes replies from the session currently holding them, the permission dialog says plainly that approving takes replies back from the other session, and both sessions get notices when replies move, come back, or were stopped elsewhere. Claude Code is also told to explain the state to you: the other session answers the comments, no comment notifications arrive in yours meanwhile, and asking to publish or calling `resume_replies` brings them back. The takeover rides on local session-to-session messaging; with that off it does nothing.

**Details**

- The Artifact `status` output gains a `yielded` auto-reply state, described as "auto-replies handed to another session of this conversation, which answers the comments now", alongside the existing armed, paused, stopped and disarmed states.
- A `yielded` page is retaken by a publish you ask for in this session or by `resume_replies`; merely asking about the page's status does not take replies back.
- When replies resume in your session, the notice can say they were "moved here from" the previous session, which "now only watches for new versions".
- The resume notice for auto-replies gains a third explanation branch, replies "handed to another session of this conversation that resumed it or published there", beside the existing paused-on-interrupt and killed-watch-task cases.
- The handoff sentence is only produced when the session actually has the comment auto-reply capability, so sessions without it never mention the handoff.
- Outcomes are counted as `took_over_...`, `takeover_handed_back`, `takeover_unyield_precautionary`, `unyield_holder_gone`, `holder_unreachable` and `nothing_freed`; when local messaging is unavailable the paths return `disabled` or `messaging_off` instead.
- Resume guidance was corrected: rather than promising at most one watch survives `--resume` or `--continue`, it now says the most recently published or read Artifact's watch usually returns in an interactive terminal, along with every comment-replying watch, and that other clients may restore nothing.
- An `other_org` failure reason joins the existing `not_found` for Artifacts that cannot be acted on.
- The older resumed-publish consent wiring around carried Artifact watches, with reasons such as `ws_open_error`, `not_editor` and `arm_in_flight`, was dropped from that call site in favour of a plain catch.

**Evidence**

`unyield_artifact_replies`, `handed to another session of this conversation that resumed it or published there`, `auto-replies handed to another session of this conversation, which answers the comments now`, `auto-replies handed to another session of this conversation`

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

### Artifacts can be shared with everyone who can reach the agent that made them

Artifacts can be shared with everyone who can reach the agent that created them.

**What**

Artifact sharing gains an audience where access follows the agent that created the item rather than a named list of people. Claude Code describes it as "everyone with access to the agent that created it", treats it as shared but not live-updating unless a view is explicitly set, and warns when a pinned version has gone stale. There is no flag to turn this on; it appears only when the service reports that audience for an Artifact.

**Details**

- Recognised in four places: share-mode parsing, the plain-English audience line shown to you, the audience summary handed to the model, and the stale-pin warning.
- Because no explicit view is set, the artifact is shared but not treated as live.
- Someone with write access to such an Artifact sees provenance framed for this audience rather than the ordinary shared-list framing.

**Evidence**

`everyone with access to the agent that created it`

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

### Backgrounded Bash commands now keep a task awake

Waiting on a backgrounded shell command now keeps a task alive, controlled by a remote setting defaulting on.

**What**

Work parked waiting on a backgrounded shell command is now treated as a live reason to keep the task alive, the same as waiting on a subagent, a monitor or a workflow. Killing a parked agent also cleans up shell tasks it left running. Controlled by the `tengu_concurrent_shore` remote setting, which defaults to on.

**Details**

- Previously only `agent:`, `monitor:` and `workflow:` waiters counted; `bash:` waiters now do too.
- Absent any server value the fallback is true, so this is live in this build.

**Evidence**

`tengu_concurrent_shore`

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

### Bash permission prompt can name the machine a command runs on

The Bash approval dialog can name the remote machine a command would run on.

**What**

The Bash approval dialog now has a third title form that names a remote host, built as "Bash command (runs on " plus the host name, next to the existing "Bash command (unsandboxed)" and plain "Bash command". It appears only when the tool request carries a host value.

**Details**

- Pairs with a new branch in the tool loop that sends a tool call to another machine and reports progress as it runs.
- What populates the host value on a request is not visible from this code; with no host, the title is unchanged.

**Evidence**

`Bash command (unsandboxed)`

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

### Owner messages in Claude Code Projects arrive as marked user turns

Project owner messages arrive as marked user turns that cannot approve prompts on your behalf.

**What**

In a Claude Code Project, messages the server says came from the project owner are replayed into the session as your own user turns, each opening with a marker the harness writes. New instructions say such a turn counts as you speaking, but a bare "yes" in one clears no soft block, answers no permission prompt, and never licenses editing settings or CLAUDE.md. Everything else relayed is treated as external content, and asking Claude to do what a sender was already denied is called "permission laundering - BLOCK".

**Details**

- Applies to messages arriving by relay and through the `fetch_messages`, `fetch_thread` and `fetch_project_timeline` tools.
- The supporting code caps how many owner rows are surfaced, de-duplicates by id and version, and drops rows that are truncated or badly timestamped.
- No environment variable or flag guards this; it applies to sessions that are a thread in a Project.

**Evidence**

`[Verified message from this session's user written`

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

### Prompt snippet for cutting Opus 5's time to first token

Model guidance adds a one-line instruction to cut Opus 5's delay before its first visible output.

**What**

The bundled model guidance adds a note that Opus 5 sometimes thinks before emitting its first visible block, which raises the delay before anything appears in chat and voice, along with a one-line system instruction to reduce it.

**Details**

- The suggested line is `Latency-sensitive; begin your visible answer immediately.`
- The advice is to apply it only where first-token latency is visible to a person, not everywhere.
- It also appears as a new tuning item in the Opus 5 checklist.

**Evidence**

`Latency-sensitive; begin your visible answer immediately.`

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

### Sandbox requirements attached to a remote call are enforced before Bash spawns

Commands arriving with sandbox constraints are refused unless a fully confining sandbox exists.

**What**

When a call arrives with constraints attached, Bash now refuses to run it unless a fully confining sandbox is available, and refuses outright if the constraints carry extra file read/write deny lists, which cannot yet be enforced locally. Both refusals emit sandbox telemetry and the command never starts.

**Details**

- Only applies when the incoming call actually carries constraints; ordinary local Bash calls are unaffected.
- The deny-list refusal is unconditional rather than degrading to an unsandboxed run.

**Evidence**

`This call carries file deny lists that cannot be enforced here yet; the command was not run.`

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

### Server-side refusal fallbacks documented as supported on Claude Platform on AWS

Server-side refusal fallbacks are now documented as working on Claude Platform on AWS, not just the Claude API.

**What**

The `fallbacks` request parameter, which lets the server retry a refused request against another model, is now documented as available on the Claude API and Claude Platform on AWS. It was previously Claude-API-only with AWS support described as still being validated.

**Details**

- Bedrock, Vertex and Foundry are still excluded and keep the client-side middleware instead.
- A new row in the platform-availability table names both beta headers: `server-side-fallback-2026-07-01` for the `"default"` form and `server-side-fallback-2026-06-01` for the array form.

**Evidence**

`available on the Claude API and Claude Platform on AWS`

- Area: Model Docs
- Names: `fallbacks`
- Tier: You'll notice
- Useful: 3/5
- Signal: 4/5

### Subagents inherit the session model, and delegated reports are asked for as HTML

Subagents now run on your session's model, and delegated reports are asked for as HTML pages.

**What**

The instructions Claude follows when handing work to a subagent now say to leave the model unset so the worker runs on the same model as your session, and never to move substantive work to a cheaper one. Where the page-publishing tool is available, delegated reports are asked for as `.html` pages instead of a Markdown file.

**Details**

- the HTML paragraph is appended only when the publish tool is in the delegating agent's tool list; otherwise the prompt is unchanged

**Evidence**

`Omit the model parameter so workers inherit the session model`

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

### A `cloud` field on hooks, and rules for which hooks a cloud session may use

Hook entries accept a cloud setting deciding whether a cloud session may run them.

**What**

Hook entries in settings can carry `cloud: "device"` or `cloud: "skip"`. A hook whose script lives somewhere the cloud session can write, such as the checkout or a synced directory, is withheld unless the entry is explicitly marked `cloud: "device"`; `cloud: "skip"` keeps it local. Part of the device-hooks subsystem that is switched off in this build.

**Details**

- A `cloud: "device"` mark is ignored when it comes from `.claude/settings.local.json` inside the checkout.
- Forwarded scripts are pinned by SHA-256 and re-checked before every run; a script that changed, moved or has multiple hard links is refused with a stated reason.
- Limits: 128 forwarded hook entries, a 180 second registration lease, and roughly 1 MiB of hook input before a PreToolUse call is denied.

**Evidence**

`is marked cloud: "skip" (or a value this version does not recognise) and stays on this machine.`

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

### A planner deciding, hook by hook, what a cloud session gets, plus a /hooks panel for it

A planner decides which of your hooks a cloud session gets, with a /hooks panel to review it.

**What**

New code decides for each locally configured hook whether it is offered to a cloud session driven from this machine, and `/hooks` gained a cloud-session panel to review the decisions. Command hooks are pinned by digest and refused when their script sits somewhere the cloud session can write; HTTP hooks have no script to pin.

**Details**

- A new table classifies each hook event that is not forwarded, surfacing as "this event happens on this machine, not in the cloud" or "this event is not forwarded yet".
- No feature flag for this appears in the bundle; the only visible condition is that a cloud-session panel exists, so what makes that panel appear is decided elsewhere.
- None of these strings exist in 2.1.241.

**Evidence**

`event_container_internal`

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

### Artifact comment reading now sits behind a server-side switch

Reading artifact comments is now behind a server switch that is off, so nothing is fetched.

**What**

Reading comments on an Artifact is gated on `tengu_onyx_sluice`, which falls back to off in this build. With it off nothing is fetched at all and you get "comments are not available on this artifact right now", counted as `cp_read_disabled`. With it on, transport errors and HTTP 5xx or 499 are retried once after a random backoff and the unavailable message includes the HTTP status.

**Details**

- Retries are reported as `server_read_retried`.
- The old path that fetched comment JSON directly from the content host is gone, along with its `__frame_t` and `json_egress_blocked` handling.

**Evidence**

`cp_read_disabled`

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

### Cloud directory sync gains git and tar-archive engines beside the per-file lane

Cloud file sync gains git-bundle and tar-archive modes, but you still sync file by file.

**What**

Syncing your working directory to a cloud session can now run through two new engines instead of shipping file rows one at a time. In a git checkout, each turn packs a snapshot commit into a bundle and exchanges it with the session; in a plain folder with no git root, an archive engine packs the tree into a gzipped tar. The per-file lane remains the fallback and the only always-on path, and the client-side selector is wired up while the cloud-side workers stay off unless separately enabled, so a plain `claude` in this build still syncs file by file.

**Details**

- `CLAUDE_CODE_DIR_SYNC_ENGINE` takes a comma-separated list and is also readable from settings; unset keeps the defaults, in which the git engine is allowed.
- Accepted words are `git`, `archive` and `always-git` to select engines, `files` or `rows` to force the older per-file sync, and `none`, `off`, `0`, `false` or `no` to run no new engine. `always-git` additionally overrides the per-project memory that pins a directory to per-file sync.
- An unrecognised word produces a single warning listing the vocabulary: "known: git, archive, always-git (defaults), files/rows (per-file sync), none/off/0/false/no (no new engine)".
- The variable is in the allowlist of environment variables forwarded to child processes, alongside `CLAUDE_CODE_DIR_SYNC_FFWD`; the older `CLAUDE_CODE_DIR_SYNC_OVERLAY` name has been dropped from that allowlist.
- On the cloud side the git worker is constructed only when `CLAUDE_CODE_DIR_SYNC_GIT` is set and an async enabled check passes, and the archive worker only when `CLAUDE_CODE_DIR_SYNC_ARCHIVE` is set. With neither set nothing beyond the row lane runs, whatever `CLAUDE_CODE_DIR_SYNC_ENGINE` says.
- Arming the git engine reads the repo layout, pins HEAD, and uses `git reflog` to spot a commit landing while the session was being created. Each failure falls back to per-file sync with its own message and reason: arm timeout, head moved, head unreadable, layout unsupported, arm failed.
- A directory that fell back is remembered as a `dirSyncEngine` entry in `~/.claude.json`, and the message tells you to remove that entry to retry. Repeatedly unacknowledged uploads stop the session and pin the directory to `"files"`, preceded by a softer one-off warning.
- The archive engine ships its own tar writer and reader: pax headers for paths over 100 bytes, refusals for bad checksum, bad magic, unsafe path and unsupported entry, a gzip framing check, staged unpack directories, per-session state in `archive-sync.json`, a trash directory for peer deletes, and a 100 MiB archive cap.
- New telemetry includes `tengu_dir_sync_folder_session`, `tengu_dir_sync_git_worker_armed`, per-turn git worker start and end events, and `dir_sync_origin` and `dir_sync_engine` properties.

**Evidence**

`archive-sync.json`, `tengu_dir_sync_folder_session`, `A commit was made here while the session was being created; this session syncs file by file instead of through git`, `known: git, archive, always-git (defaults), files/rows (per-file sync), none/off/0/false/no (no new engine)`, `tengu_dir_sync_git_worker_armed`, `claude: fast-forward to the cloud session`, `remove this directory's dirSyncEngine entry from ~/.claude.json`, `dir_sync_worker_archive_switch_on`, `; known: git, archive, always-git (defaults), files/rows (per-file sync), none/off/0/false/no (no new engine)`

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

### Cloud session sync for folders that are not git checkouts

Cloud sessions could sync a plain folder, not just a git checkout, if an env var is set.

**What**

Cloud sessions can now be started from an ordinary directory rather than a git checkout, using a second sync engine that applies file archives sent from your machine, keeps a session-side trash for cloud deletions, and tells the model exactly which files never cross. The consent prompt that asks whether to copy your files into a cloud session already picks its title, body and "without sync" wording depending on whether you are in a git repository or a plain folder, so the folder wording is visible in this build. The folder engine itself is only constructed when the environment variable `CLAUDE_CODE_DIR_SYNC_ARCHIVE` is set and its enabled check passes, so with that variable unset the prompt wording is present but no folder sync happens.

**Details**

- The folder version of the consent prompt spells out what stays behind: anything the top-level `.gitignore` excludes, dot-named files and directories, and git repositories nested inside the folder.
- The sync itself excludes dot-files, dependency and build folders, credential-named files, symlinks, editor leftovers, and files over the size limit.
- Which engine is in play is decided by the archive engine check, and on the worker path by `CLAUDE_CODE_DIR_SYNC_ARCHIVE`; with the variable unset nothing is constructed and the git-based engine remains the only path.
- When your copy and the cloud copy of a file conflict, your version is kept under the original name.
- Sync bookkeeping lives in a `.ccr-dir-sync` directory at the top of the synced folder.
- New reasons are recorded internally for why the consent prompt was skipped, and an event named `dir_sync_worker_archive_armed` marks the folder worker being armed.

**Evidence**

`Allow Claude Code to copy the files in this folder (never ones its top-level .gitignore excludes, dot-named files or directories, or git repositories inside it) into cloud sessions you start here, so a session works on your current files and can keep going while this machine is off. Synced files are encrypted at rest.`, `dir_sync_worker_archive_armed`

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

### Consent screen explaining what forwarding your hooks to a cloud session means

A consent screen explains what letting cloud sessions run your local hooks means.

**What**

A new screen asks whether hooks from your own settings (user `settings.json`, a checkout's `settings.local.json`, or a `--settings` file) may run on this machine when a cloud session asks for them. It offers three answers and explains that whatever a hook returns, such as a block reason or added context, becomes part of the cloud session's transcript.

**Details**

- The answer is stored per machine; other `claude --cloud` terminals already running keep the answer they started with.
- Hook forwarding itself shipped in 2.1.241, with `CLAUDE_CODE_DISABLE_HOOK_FORWARDING` to turn it off and a server-side flag path that reports `hook_forwarding_disabled: flag_off`. This build adds the consent dialog on top.
- No slash command in this build opens the screen, and the code that builds its rows passes a placeholder session identifier, so how a user reaches it is unclear.

**Evidence**

`Yes, run this machine's hooks for cloud sessions`

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

### Device hook templates that cannot be installed now block instead of passing through

If a device hook template cannot install, the tools it guarded are now denied rather than allowed.

**What**

When a hook template registered by another device cannot be installed on this machine, the tool calls it was meant to guard are now denied instead of being allowed through unchecked. This sits behind the same gate as device hook forwarding, so it only applies where that forwarding is active.

**Details**

- Triggers when the template is still awaiting upload, its interpreter is unavailable, or preparing it fails or times out.
- The blocking stand-in is registered on the same matcher as the real template and reports outcome `stand_in_blocked` on the tengu_device_hook_template_run telemetry point.
- At run time a template that fails cleanly either blocks the call (for fail-closed templates) or attaches an explanation from a fixed reason list, such as "its file no longer matched what was verified", "its interpreter can no longer be trusted" or "it took too long".

**Evidence**

`could not be installed on this worker; matching calls are blocked until it is (register again to retry)`

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

### Each hook event enforces what a plugin is allowed to change

Plugin hooks are limited in what they may rewrite, and cannot take over question prompts.

**What**

Return values from a hook are validated per event rather than trusted. A subagent spawn hook may only rewrite the model. A pre-tool-use hook must keep the tool envelope's keys intact. A prompt submission hook may return replacement text or a `drop` string. The question component is refused as a render target outright, because its answer authorises an action; a plugin can add context around it with a notice instead.

**Details**

- The full event list a module may hook is PreToolUse, tool.call, tool.describe, ui.render, ui.resolve, agent.spawn, prompt.submit, prompt.section, turn.start, turn.step, turn.complete and engine.create.
- Validation is structural to the hooks engine, not tied to a separate flag.

**Evidence**

`is drawn by the engine alone; its answer authorises an action. A plugin adds context with $.ui.notice`

- Area: Plugins
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5

### Per-machine decision about whether cloud sessions may run your hooks

A Hooks settings row lets you decide whether cloud sessions may run this machine's hooks.

**What**

The Hooks settings screen gains a row for deciding whether cloud sessions are allowed to run hooks configured on this machine. The answer is saved on this machine and shown as on, off, or not yet decided. Until you decide, nothing from this machine is offered to a cloud session.

**Details**

- The saved record holds a version, the choice, when it was decided and the hostname, written with owner-only permissions under the name `device-hooks-consent`, either through the shared storage service or the local config directory.
- If that consent file itself sits somewhere the cloud session can write to, the saved answer is ignored for that session.
- A separate settings toggle, "Use this machine's settings in cloud sessions", appears in the settings list but refuses to be switched on from there and points you at the `/config` panel that shows what would be sent.
- The hooks row appears only when the dialog is given the matching callback, and the settings toggle is behind a condition satisfied when the machine's forwarding mode is `forward`. The check that decides overall availability is not resolvable from the build.

**Evidence**

`device-hooks-consent`, `Decide whether cloud sessions run this machine's hooks\u2026`

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

### Plugin HTTP calls are https-only and blocked from internal addresses

Plugin network calls are https-only and blocked from internal and metadata addresses.

**What**

The plugin fetch capability refuses anything but https, refuses URLs with embedded credentials, and blocks private, loopback, link-local, carrier-NAT, multicast and reserved addresses in both IPv4 and IPv6, cloud metadata endpoints, `localhost` / `.local` / `.internal` names, and Anthropic-operated hosts. Calls time out at 30000ms. It ships as part of the plugin hook-module feature, which is off unless the server enables it.

**Details**

- Hostnames are resolved and the connection pinned to the resolved address; that pinning is skipped only when a proxy is configured and `CLAUDE_CODE_PROXY_RESOLVES_HOSTS` is set.
- Redirects are capped, and a cross-origin redirect strips every header except accept, accept-language, content-type and user-agent.
- Request and response bodies are size-capped.
- Two policies refuse the call before any of this: sessions with nonessential network traffic disabled, and the plugin network policy check being off.

**Evidence**

`network access from plugins is disabled by policy`

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

### Plugin file access is confined to the project and blocked from credential folders

Plugin file reads stay inside the project and cannot touch credential folders.

**What**

The plugin file capability refuses paths that escape the project root, non-regular files and hard links, and re-checks the opened file against what it saw before opening so a file swapped in mid-check is caught. It arrives with the plugin hook-module feature, off unless the server enables it.

**Details**

- Writes additionally refuse the project's config and git directories, plugin and marketplace folders, the session config directory, and home tool folders including `.ssh`, `.aws`, `.kube`, `.gnupg`, `.docker`, `.npm`, `.bun`, `.rustup`, `.m2`, `.gradle`, and launchd/systemd/autostart locations.
- Win32 short names and paths reached via relocation variables such as `XDG_CONFIG_HOME`, `ZDOTDIR`, `BASH_ENV`, `GIT_CONFIG_GLOBAL`, `NPM_CONFIG_USERCONFIG` and `KUBECONFIG` are refused too.
- The refusals themselves are unconditional path checks, with no separate switch.

**Evidence**

`the open landed on a file the check did not see`

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

### Plugins get a small persistent key/value store

Plugins get a private key/value store that survives between sessions.

**What**

Plugins can read and write a private JSON store that survives across sessions, kept in one file per plugin and guarded by a lock so two processes cannot corrupt it.

**Details**

- The file name comes from a sanitized plugin id, falling back to a hashed name when the id will not sanitize cleanly.
- Writes go through a per-plugin queue and take a file lock with 10 retries, warning if the lock is compromised.
- Keys have a length limit and the store as a whole has a character limit; a write that would exceed it is refused.
- A store file that is unreadable or is not a JSON object raises an error naming the file.
- Behind the tengu_plugin_hooks_modules rollout flag.

**Evidence**

`is malformed (the JSON is not an object)`

- Area: Plugins
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5

### Resume can re-attach to a tool call that was interrupted

Resuming can pick up an interrupted tool call, but only with two env vars set.

**What**

Resuming a session can pick up a tool call that was still in flight instead of discarding it, when the pending action names a tool use that is still the last real turn. Both `CLAUDE_CODE_RESUME_TOLERATES_CONTEXT_APPENDS` and `CLAUDE_CODE_RESUME_INTERRUPTED_TURN` must be set for this path; with either unset the old behaviour stands.

**Details**

- When the recorded tool name does not match the pending action, resume reports a name mismatch rather than re-attaching.
- `CLAUDE_CODE_PARKED_PERMISSION_WAIT_MS` sets how long a parked permission prompt waits, defaulting to 2000ms.

**Evidence**

`CLAUDE_CODE_RESUME_TOLERATES_CONTEXT_APPENDS`

- Area: Resume
- Names: `CLAUDE_CODE_RESUME_TOLERATES_CONTEXT_APPENDS`, `CLAUDE_CODE_RESUME_INTERRUPTED_TURN`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Screenshots can zoom into a rectangle; the image scale option is off in this build

Screenshots can zoom into a rectangle, but the documented image scale option is not exposed in this build.

**What**

The computer-use screenshot tool takes a `region` of four integers naming a rectangle to zoom into, measured against the full-screen screenshot taken before the batch. A `scale` factor is also documented, with an explanation of its effect on token cost, but both capability profiles shipped here turn adaptive resolution off, so `scale` is not exposed.

**Details**

- `region` is described as `(x0, y0, x1, y1)` and applies to zoom only
- the repeated sentence about the frontmost application having to be in the session allowlist is now drawn from one shared string across the key, type and hold descriptions

**Evidence**

`(x0, y0, x1, y1): Rectangle to zoom into. For zoom only.`

- Area: Computer Use
- Names: `region`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Shape rules for Claude in Chrome batched actions, off without server config

Browser batch calls get strict shape rules with denials, but only under server config.

**What**

Browser tool calls are inspected before they run: a batch must target exactly one tab, may not act on a page after navigating to it in the same batch, may not contain another batch, and switching browsers must be its own call. Violations are denied with a message explaining the correct shape, and the denial is a safety decision the permission classifier cannot approve. Enabled only when the server-supplied auto mode configuration sets Chrome navigation to true, which nothing in this build does locally.

**Details**

- The tab's URL is resolved and compared against the last URL executed for that scope, which is how the navigate-then-act case is detected.

**Evidence**

`Claude in Chrome: a browser_batch cannot act on a page after navigating to it. Issue the navigate on its own, then batch the actions on the loaded page.`

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

### Subagent reports get an anti-injection wrapper, switched off in this build

Subagent reports can be wrapped so embedded instructions cannot pass as your commands, off here.

**What**

When a subagent finishes, its report is re-emitted indented under a preamble telling the model the text is model output carrying no user authority, so instructions embedded in it cannot pass as commands. The section list is hashed so the notes and tail split cannot be forged, and a failed split is reported as degraded. Off unless enabled: `CLAUDE_CODE_HANDBACK_PROVENANCE` is checked first, otherwise the `tengu_melodic_wolf` setting, which falls back to off.

**Evidence**

`[Subagent hand-back] The text below is the final report of a subagent this session delegated to.`

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

### Tool calls and turn steps dispatched through the plugin event bus

Tool calls and turn steps now flow through the plugin event bus, so plugins can observe a running turn.

**What**

Tool invocation and turn stepping now go through the same event dispatch that plugins observe, rather than being called in line. This is the plumbing that lets a plugin see and act within a running turn.

**Details**

- Each tool call builds an event input, dispatches through core and managed handlers, and does open/close bookkeeping around the call.
- The interactive session subscribes to turn events and dispatches turn steps, appending their results as messages.

**Evidence**

`createTurnEventTail`

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

### `--print` with `--cloud` can message an existing cloud session

You can now use --print with --cloud to message an existing cloud session.

**What**

`--print --cloud` was previously refused as interactive-only. It now sends the prompt to an existing cloud session when you identify one, and the error tells you the accepted forms if you pass anything else.

**Details**

- Accepted identifiers: a session ID starting `session_` or `cse_`, or a claude.ai/code URL.
- Passing a bare description (to start a new session) still fails, and the message now says to drop `--print` for that.
- A parallel message covers running without a terminal attached.

**Usage**

`claude --print --cloud session_abc123 "summarise the failing test"` **Evidence** `is not a cloud session ID or URL.`

- Area: Cloud Sessions
- Names: `--print`, `--cloud`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### `disableArtifact` deprecated in favour of `enableArtifact`

Use enableArtifact instead of disableArtifact, which now only counts when set to true.

**What**

The old setting is now marked deprecated and only still does anything when set to `true`. The replacement key documents which settings tier wins: an off in managed settings, `--settings` or your user settings is final, while project and local settings can only turn the feature off, never back on.

**Details**

- Unset means on, once the feature is otherwise available.
- `disableArtifact: false` no longer has an effect; use `enableArtifact: false` to turn it off.

**Evidence**

`Deprecated: use enableArtifact: false. Still honored`

- Area: Settings
- Names: `enableArtifact`, `disableArtifact`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### API requests that never send response headers are now aborted

Hung requests are aborted when headers never arrive, and you can tune the window.

**What**

Streaming requests, and Bedrock POSTs to the invoke path, are now wrapped in a watchdog that aborts if the first response headers never arrive. The budget is computed from the request body size and `API_TIMEOUT_MS`, and `CLAUDE_STREAM_FIRST_BYTE_TIMEOUT_MS` overrides it. Time the machine spent asleep is measured and reported separately, so a laptop that suspended mid-request is not mistaken for a hung server.

**Details**

- Applies to armed requests only; other calls keep their existing timeout behaviour.
- Aborts are reported under the `tengu_api_no_response_timeout` event with the sleep-adjusted duration.

**Evidence**

`tengu_api_no_response_timeout`

- Area: Internals
- Names: `CLAUDE_STREAM_FIRST_BYTE_TIMEOUT_MS`, `API_TIMEOUT_MS`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### CLAUDE_CODE_LEGACY_BUNDLE restores the old non-split bundle shape

Set CLAUDE_CODE_LEGACY_BUNDLE to true to go back to the old single-file build shape.

**What**

The re-bundle that turned deferred loads into real dynamic imports comes with an opt-out: set `CLAUDE_CODE_LEGACY_BUNDLE` to true to get the old shape. It must be truthy, so unset means everyone gets the chunked build.

**Details**

- The variable was added to the allowlist of environment variables forwarded to and inspected in child processes, next to `CLAUDE_CODE_DIR_SYNC_ENGINE` and `CLAUDE_CODE_DIR_SYNC_FFWD`.
- Paths moved out of the main bundle include the event-loop stall detector and Google auth; much of the surrounding diff is renaming churn from the same re-bundle.

**Evidence**

`CLAUDE_CODE_LEGACY_BUNDLE`, `startEventLoopStallDetector`

- Area: Bundle
- Names: `CLAUDE_CODE_LEGACY_BUNDLE`, `CLAUDE_CODE_DIR_SYNC_ENGINE`, `CLAUDE_CODE_DIR_SYNC_FFWD`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### Gateway routing is passed down to spawned agent-team processes

CLAUDE_CODE_USE_GATEWAY now passes down to spawned agent processes, so gateway routing is no longer dropped.

**What**

`CLAUDE_CODE_USE_GATEWAY` is now on the allowlist of environment variables copied into processes Claude Code spawns, alongside the other provider switches such as `CLAUDE_CODE_USE_BEDROCK` and `CLAUDE_CODE_USE_MANTLE`, so a session routed through an enterprise gateway hands that routing down instead of dropping it.

**Details**

- Unconditional for anyone who has the variable set.

**Evidence**

`CLAUDE_CODE_USE_GATEWAY`

- Area: Agent Teams
- Names: `CLAUDE_CODE_USE_GATEWAY`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### New /plugin-types command writes TypeScript types for your connected MCP tools

Type /plugin-types to generate a TypeScript file typing every connected MCP tool's arguments.

**What**

`/plugin-types` generates `claude-code-mcp.d.ts` in `.claude/types`, or in a directory you pass, containing an `McpToolInputs` interface built from every connected MCP server's advertised tool schemas. A plugin checking `e.tool === "mcp__<server>__<tool>"` then gets that tool's exact arguments typed. It reports how many tools from how many servers it wrote.

**Details**

- Refuses to write outside the project directory or through a symlink.
- Registered as an ordinary local command and usable non-interactively.
- Types come from the servers connected in the current session, so reconnecting or adding a server means re-running it.

**Usage**

`/plugin-types /plugin-types src/types` **Evidence** `Write claude-code-mcp.d.ts: the inputs of the connected MCP tools, for typing a plugin against this session`

- Area: Slash Commands
- Names: `/plugin-types`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### Prompt cache lifetime is now configurable

You can pin the prompt cache to five minutes or an hour, separately for subagents.

**What**

Two new settings, `promptCacheTtl` and `subagentPromptCacheTtl`, pin the prompt cache to `"5m"` or `"1h"`. The first covers the main conversation (interactive, `-p` and SDK turns and the helpers that run inline with it), the second covers subagents, workflows and background requests. `CLAUDE_CODE_PROMPT_CACHE_TTL` and `CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL` override the settings. No experiment gate; available to everyone on this build.

**Details**

- Unset means automatic: 1 hour on a Claude subscription within usage limits, 5 minutes on an API key, Bedrock, Vertex or Foundry for the main conversation; 5 minutes for subagents unless `ENABLE_PROMPT_CACHING_1H=1`.
- Resolution order is `FORCE_PROMPT_CACHING_5M`, then the environment variable, then the setting, then the 1h environment flags.
- Both environment names were added to the allowlist, and request telemetry gained `prompt_cache_ttl` and `prompt_cache_ttl_reason`.

**Usage**

`# in settings.json { "promptCacheTtl": "1h", "subagentPromptCacheTtl": "5m" } # or per run CLAUDE_CODE_PROMPT_CACHE_TTL=1h claude` **Evidence** `CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL`, `Prompt cache TTL for the main conversation (interactive, -p and SDK turns, plus the helpers that run inline with it)`, `Prompt cache TTL for the main conversation (interactive, -p and SDK turns, plus the helpers that run inline with it): "5m" or "1h".`

- Area: Performance
- Names: `promptCacheTtl`, `subagentPromptCacheTtl`, `CLAUDE_CODE_PROMPT_CACHE_TTL`, `CLAUDE_CODE_SUBAGENT_PROMPT_CACHE_TTL`
- Tier: Use it now
- Useful: 4/5
- Signal: 3/5

### Your local branch is moved forward onto the cloud session's commits

Your local branch fast-forwards onto a cloud session's commits unless you turn it off.

**What**

When a cloud session's commits sit directly on top of your checkout's current HEAD and your working tree is clean, Claude Code now advances your local branch onto them and tells you your branch has Claude's commits. It waits instead when you have staged changes, an operation in progress, differing files or protected paths, each with a plain explanation. Set `CLAUDE_CODE_DIR_SYNC_FFWD` to switch this off; it is on otherwise.

**Details**

- The move takes the git index lock, merges the index, and updates the branch reference without following symbolic refs. If the index cannot be brought along, the whole move is undone.
- Named waiting reasons include staged_changes, operation_in_progress, files_differ, not_yet_sent, untracked_dependency_path and protected_path.
- Files whose names look like credentials, files that are git-ignored, files withheld from sync, and dot-led paths are never taken from the cloud.
- Reported in telemetry as `branch_rule` on the git sync open event.

**Evidence**

`claude: undo the fast-forward (the index could not follow)`

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

### A cloud session holds your first message until it confirms it has your local changes

A cloud session seeded from your local files holds your first message until it confirms your changes arrived.

**What**

When a cloud session is seeded from your local working tree rather than from the remote repository, the first message is now held until the session confirms your changes arrived. If it has not, sending is refused with an explanation that the session would otherwise be working from GitHub's copy of the repository; send again to go ahead anyway.

**Details**

- The hold is only armed for sessions seeded from an overlay of your local checkout. Sessions seeded any other way behave as before.
- A separate wording covers the case where you stopped waiting.

**Evidence**

`the cloud session has not confirmed it has your local changes; send it again to go ahead regardless`

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

### A socket path taken by another running Claude Code no longer breaks startup

Startup survives when another Claude Code already holds the socket path, retrying nearby.

**What**

When Claude Code picks its own local socket path for inter-process messaging, it now probes the existing file first: a leftover from a dead process is deleted and retried, and a socket a live sibling is using causes a bind at a randomised nearby path, up to three attempts. Sockets moved aside on earlier runs are cleaned up. A socket path you specify explicitly still fails hard if it is in use.

**Details**

- The failure message after three attempts warns that siblings may be in a different pid namespace, which is why liveness could not be judged.

**Evidence**

`listen EADDRINUSE on the auto socket path and its moved-aside siblings`

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

### A stale on-disk auto-mode killswitch no longer keeps auto mode off

A stale local file can no longer keep auto mode switched off; startup re-checks the server.

**What**

If the cached auto mode configuration said disabled and that value came from disk rather than a live override or server payload, it is now read back as enabled, and startup re-checks the killswitch against the remote config service before deciding. A stale local file can no longer keep auto mode switched off indefinitely.

**Details**

- Two new startup awaits: initialising the remote config before mode selection, and the killswitch recheck itself, timed as `growthbook_killswitch_recheck_ms`.
- Only values sourced from an explicit override or a server payload are still honoured as disabling.

**Evidence**

`growthbook_killswitch_recheck_ms`

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

### Answers sent back to a cloud session are retried, and losses are reported

Your answers to cloud-session prompts are retried with backoff, and you are told if one is lost.

**What**

When you answer a permission prompt, dialog or hook for a session running in the cloud, the answer is now kept if it cannot be delivered, re-posted with a 1s, 5s, 15s and 45s backoff, and re-sent on reconnect. If it is finally lost you get a message naming which kind of answer went missing and suggesting you interrupt and retry.

**Details**

- Retention is behind the manager setting `keepUndeliveredResponses`; without it an answer is posted once and forgotten.
- Hook answers have a separate "overtaken" path for when the cloud worker cancelled the request before the answer arrived.
- Also here: hook callbacks forwarded from the cloud and answered on this machine, send gates that can hold or withdraw a queued message, and a withheld first prompt that is retried and reported through `tengu_home_seed_prompt_resequenced`.

**Evidence**

`A permission answer you gave could not be delivered to the cloud session, which may still be waiting for it. If the session looks stuck, interrupt it and retry.`

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

### Computer-use actions can be interrupted, and inner failures no longer kill the whole call

You can interrupt computer-use waits and typing, and one failed action no longer sinks the batch.

**What**

`wait` now polls in slices and stops on interrupt, and typing, key repeat, hold-key and drags re-check for interrupt before each keystroke or movement. In `computer_batch` and `teach_step` each inner action is wrapped so a throw becomes an `executor_threw` tool error for that action instead of failing everything.

**Details**

- Batch results are rendered per action as `[i/n] action:` lines, with images dropped after an error.
- Inner throws are logged as `[computer-use] computer_batch action=… threw:`.

**Evidence**

`Wait aborted (user interrupt).`

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

### Lower-priority mode reports extra usage taking over, and shows an allowance percentage

Fast mode now tells you when extra usage takes over and shows your remaining allowance percentage.

**What**

When lower-priority ("fast") work stops, a new reason says extra usage is now covering your requests. The status line can show a remaining-allowance note with the percentage filled in, and the "no room for lower-priority work" message now suggests when to re-run the slash command.

**Details**

- The status line, allowance note and exhausted-budget copy all come from remote-configured strings, so the wording can change without an update.
- The extra-usage end reason itself is unconditional.

**Evidence**

`Extra usage is now covering your requests`

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

### Artifact tool manifests are checked before publish, and app built-ins refused

Pages must declare MCP servers this session actually has, and the Claude app's own built-ins are refused.

**What**

A page that declares which MCP servers it needs is now validated against the servers this session is actually connected to before it can be published, with each failure named rather than a generic error. Pages declaring the Claude app's own built-in servers (cowork, scheduled-tasks, session_info, workspace) are refused.

**Details**

- Failure reasons include `not_object`, `no_servers`, `entry_shape`, `no_tools`, `tools_shape`, `tool_name`, `opaque_id`, `known_id`, `connector_as_host` and `undeclarable_name`.
- Duplicate entries that resolve to the same server are merged, and manifests are rejected outright if they declare too many tools or too many servers.
- The guidance shown to Claude changes depending on whether the session has claude.ai connectors and a meta connector.

**Evidence**

`manifest entries resolve to `

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

### A broader classifier for destructive shell commands

A broader detector for destructive shell commands exists, but nothing visibly uses its verdict.

**What**

A new module scores Bash commands as destructive, covering cases that simple pattern matching misses, and recursing into `sh -c`, `eval` and backtick subcommands. It carries no flag of its own, and where its verdict is consumed is not visible in the added code, so it may not affect any prompt you see yet.

**Details**

- Flags `rm`, `git reset`, `clean`, `checkout`, `restore` and `stash` written with quoting or brace expansion, `git push` with force-like refspecs or `--delete`, `find -delete`, `shred`, `git clean -f` without a dry run, `xargs` and `-exec` wrappers around those, and `git -c alias.`.
- Strips wrapper prefixes such as `sudo`, `env`, `nice`, `timeout`, `command` and `busybox`, splits pipelines while ignoring redirections and `VAR=` assignments, and re-checks nested commands up to a depth limit.
- Input that is too long or nested too deeply is treated as risky by default.

**Evidence**

`/(^|[;&|\n(`][ \t]*)shred\s/`

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

### Agent-backed mock responders are present but switched off

Mocks could be answered by a prompt instead of a fixed body, but that type is rejected.

**What**

Mock files can declare a responder that is answered by a prompt rather than a fixed body, with an `abort_when:` condition, but the code path is behind a constant that is false in this build. Any mocks file using `type: agent` fails and is told to use a fixed body for now.

**Details**

- Supporting machinery ships unreachable: `abort_when`, the `tools:` key in `_server.md`, and a heuristic warning that prose about failing the run does not actually abort it.

**Evidence**

`mocks: agent responder unavailable`

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

### Agent-played mock servers are documented but rejected

The docs describe a model-played mock server, but the loader refuses that type here.

**What**

The mocking docs describe a mock type where a small model plays the server for the run, with conditions for aborting, but the loader refuses it in this build.

**Details**

- The text says it arrives in a follow-up release.

**Evidence**

`arrives in a follow-up release; refused at load until then`

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

### An every-app grant mode that must ship with a denylist

There is a grant mode treating every app as allowed except ones on a denylist.

**What**

Besides granting named applications, there is now a mode that treats every application as granted at the full tier except those a denylist excludes, and it skips the step that hides windows before acting.

**Details**

- Constructing it with an empty denylist throws at runtime on purpose; the code calls that a wiring bug.
- It is only used where the host program supplies an every-app policy. The default behaviour, granting specific apps, is unchanged.

**Evidence**

`WildcardGrantSet: deniedBundleIds must be non-empty`

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

### Artifacts carry an unshipped "live paths" capability

Artifacts carry an unshipped live-paths concept that only appears when three conditions hold.

**What**

Publishing an Artifact gains a live-paths concept throughout: publish results carry live paths and a born-live marker, share status stores the paths and when they were issued, and the publish request sends a live-files flag that is also passed to publish telemetry. The visible part is extra top-level fields spliced into the Artifact tool's input schema, and that only happens when three conditions all hold.

**Details**

- Schema fields appear only when the live-edit gate is open, the files capability is enabled, and a live-paths predicate returns true.
- That predicate lives on a module loaded lazily from a separate chunk, absent from the main bundle, so what it defaults to is decided outside this code.
- The combined boolean is also stored as a latch on the durable artifact state. It is written at exactly one place and read nowhere in the build, so it currently affects nothing.
- None of these fields exist in 2.1.241.

**Evidence**

`livePathsGateLatch`

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

### Asking the user to escalate from one app to the whole screen

A session can ask to escalate from one app to full screen control, and hand it back.

**What**

Two tools, request_full_control and release_full_control, let a session ask permission to move from controlling one application to controlling the whole screen, and hand that control back. Escalation needs the host's takeover handler and an explicit user approval; during scheduled or unattended runs it is refused with the reason code unattended_no_approver.

**Details**

- The permission dialog now carries a preferred-mode default, so a user who prefers per-app control keeps it as the default choice.
- The access-request result includes guidance steering the model toward the per-app tools when that is the user's preference.
- Neither tool name nor the preferred-mode setting exists in v2.1.241.

**Evidence**

`release_full_control`

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

### Console "profile" sign-in that stores credentials per profile

A per-profile sign-in can store logins under a named profile, but it is switched off here.

**What**

A new sign-in path writes an OAuth login into `configs/<profile>.json` plus an `active_config` marker under the Anthropic config directory, with the profile name taken from `ANTHROPIC_PROFILE` (letters, digits, underscore, dot and hyphen only). Whether it is reachable is decided by a flag the login screen is handed, whose source is not visible in the bundle, so treat it as off.

**Details**

- It refuses, each with its own plain-English message, when: workload identity federation is configured (`ANTHROPIC_FEDERATION_RULE_ID` plus `ANTHROPIC_ORGANIZATION_ID`), the session uses Bedrock, Vertex or Foundry, `ANTHROPIC_API_KEY` is set, an API-key helper or injected token is in play, the profile was written by another tool, the profile has a custom credentials path, or the profile is bound to a different Anthropic deployment.
- Signing out deletes the profile's credential file and can revoke the token.
- None of these messages exist in 2.1.241.

**Evidence**

`Console profile login refused: env credential shadows the profile`

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

### Forwarded-hook bridge for cloud sessions, behind an unresolved condition

A bridge answering a cloud session's hooks on your machine is built behind an unresolved condition.

**What**

A new component wires a cloud session's hooks back to your machine, so hook callbacks raised remotely can be answered and released locally. It is constructed at exactly one place, and that place returns nothing unless the session is not view-only and a second condition holds.

**Details**

- The bridge registers callbacks for a forwarded hook firing and for one being cancelled, backed by the session manager's respond and release operations, and re-registers on stream connect, worker init, settings change and consent.
- The condition at the call site is a bare function call. The only declaration of that name in the file returns false unconditionally, but this bundle reuses the same short names for different functions across chunks, so the file alone does not establish which one is in scope there.

**Evidence**

`onForwardedHookCallback`

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

### Groundwork for handling model refusals

Groundwork for spotting when the model refuses, with a banner slot that is still empty.

**What**

A pattern was added that recognises the openings of a refusal ("I can't", "I cannot", "I'm unable", "I'm sorry", "I apologize", "Sorry,"), and the streaming handler gained an `onRefusalFallbackBanner` slot that is still an empty stub. A retry check was narrowed at the same time: it now retries only when the failure reason is absent or is a full lane, rather than anything that was not a refusal.

**Details**

- An environment variable `CLAUDE_CODE_REFUSAL_FALLBACK_CATCH_ALL` sits in the environment map alongside this code; nothing in this build wires it to the banner slot.
- With the banner unimplemented, the visible effect today is only the tighter retry rule.

**Evidence**

`onRefusalFallbackBanner`

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

### On-disk storage layer gains symlink and permission rules per data type

Where Claude Code stores your data gains per-type symlink and file-permission rules, off by default.

**What**

The layer that maps transcripts, sessions, tasks, mailboxes, jobs, memory, logs, recordings and daemon state to files now decides per data type whether to follow symlinks and what file mode to use. It only runs when `tengu_hover_rest` is on, which is off unless the server enables it or `CLAUDE_CODE_HOVER_REST` forces it.

**Details**

- Commands, agents and skills follow symlinks; session-env, uploads, usage-data and the MCP discovery cache refuse them.
- Opened files are identity-checked, preferring /proc/self/fd and falling back to a stat recheck after opening when /proc is unreadable, logged once per machine.
- Locks and streams live under a "storage-v2" root; failures classify as LockContended, LiveRecordUnverified or IdentityUnverified.
- Some of the surrounding code moved during re-bundling, so not every changed line here is new behaviour.

**Evidence**

`storage: /proc descriptor record unreadable for hardened reads on this host; using the post-open recheck`

- Flag `tengu_hover_rest`: Off in both readings (read for one account on one subscription tier against v2.1.242; this account: off, anonymous baseline: off, compiled default: not a boolean we can read)
- Area: Internals
- Names: `CLAUDE_CODE_HOVER_REST`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### Placeholder credential values for the gh, AWS and gcloud CLIs

Stand-in credential values for gh, AWS and gcloud are defined, with substitution rules unsettled.

**What**

A table now maps each of these command-line tools to the credential environment variables it reads and to stand-in values for them, covering `GH_TOKEN` and `GITHUB_TOKEN`, the full AWS set including container credential URIs, and the Google Cloud SDK token and application default credentials path. A startup step primes these and logs without failing if it cannot. What decides when the placeholders are substituted is not settled by the code that defines them.

**Evidence**

`AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`

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

### Plugin hook code is parsed and rejected unless it follows a strict shape

Plugin hook code is parsed and rejected before running if it strays outside a narrow shape.

**What**

Before a plugin's hook file runs, its source is parsed and walked, and anything that does not match a narrow permitted shape is refused with the file name, the line and an excerpt. This makes what a hook can do checkable before execution rather than after.

**Details**

- The file must export a register function taking the event registrar and options, and event names must be plain string literals naming real events.
- Every use of the plugin API object must be written out fully at the call site. Storing it in a variable, spreading it, shadowing it, reading arguments, or using computed property access all cause a refusal.
- At runtime the host also checks that a plugin only calls the operations its scan found.
- The whole hooks host is behind the tengu_plugin_hooks_modules rollout flag.

**Evidence**

`$ is always spelled $.noun.event(...) at the call site, and on is always on("<event>", hook)`

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

### Scaled screenshots for computer use are built but off in both config tables

Screenshots could be scaled and zoomed to a region, but both config tables switch it off.

**What**

A new adaptive-resolution capability adds a `scale` parameter to the screenshot and batched computer-use tools and switches to a richer batched schema with a `region` for zoom. Both places in this build that supply the capability set it to false, so absent a server-supplied config, no user sees `scale`.

**Details**

- The two sources are the terminal computer-use descriptor, which sets adaptive resolution false alongside native screenshot filtering, and the computer-use settings defaults object.
- That defaults object is merged with remote configuration under `tengu_malort_pedway`; the same local fallback also has the whole feature disabled.
- The `scale` description tells the model that coordinates stay in the full-resolution frame regardless of the returned image size.
- The capability key is new in this release.

**Evidence**

`adaptiveResolution`

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

### Starting an Artifact from a type in a cloud session, behind a second gate that is off

Creating an artifact from a type inside a cloud session sits behind a second switch, off.

**What**

Artifact types (listing and describing available types, filtering by type, both treated as read-only for permissions) remain behind `CLAUDE_CODE_ARTIFACT_TYPES` or the flag `tengu_cobalt_plinth_larch`. New here is a second switch specifically for creating an Artifact from a type while in a cloud session: `CLAUDE_CODE_ARTIFACT_TYPE_CLOUD_CREATE` or the flag `tengu_cobalt_plinth_hazel`. Both fall back to off, so on a stock cloud session this is closed.

**Details**

- Local sessions allow type-based creation unconditionally; only remote sessions need the second gate, on top of types being enabled at all.
- The decision is frozen once per session and then selects between the working prompt and the refusal wording.
- When closed, publishing with a type URL returns a sentence aimed at the model telling it not to retry there and to give the user the type's link instead.

**Evidence**

`Starting a new Artifact from a type isn't available in this cloud session right now, so nothing was created; do not retry here.`, `tengu_cobalt_plinth_hazel`

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

### The Artifact tool can browse published types, but not in this build

Claude could browse published artifact types to start from, but both actions are switched off.

**What**

Two read-only actions are added to the Artifact tool: `list_types` takes a `type_query` and returns published types this account could start a new Artifact from (title, url, description, tier), and `describe_type` takes a `type_url` and returns that type's files, instructions file, capabilities and whether it is creatable. Both are switched off here. Availability is read from `CLAUDE_CODE_ARTIFACT_TYPE_CATALOG`, falling back to the flag `tengu_cobalt_plinth_rowan`, whose built-in value is false, and neither exists in the previous build.

**Details**

- The value is computed once when the tool's schema is built and frozen for the session, and the wider artifact-types feature must also be on.
- With it off, the actions do not appear in the tool's action list, the paragraph of the prompt that advertises them is omitted, and calls answer with an unavailable message or "artifact types not available to this account".
- Permission text warns that titles and descriptions in these listings are written by their publishers and are read into the conversation.
- Results render as "listed N artifact types" and "described artifact type ... (N files ... ships instructions)", and a not-found reply points you at using a `type_url` from `list_types`.

**Evidence**

`describing an Artifact type is not available in this session`, `artifact types not available to this account`, `if (i) c.push("list_types", "describe_type");`, `list published artifact types (read-only; titles and descriptions written by their publishers)`

- Flag `tengu_cobalt_plinth_rowan`: Off in both readings (read for one account on one subscription tier against v2.1.242; this account: off, anonymous baseline: off, compiled default: not a boolean we can read)
- Area: Artifacts
- Names: `CLAUDE_CODE_ARTIFACT_TYPE_CATALOG`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### The remote bridge can push an attachment notice, including after compaction

A remote host can add an attachment notice to a turn, including right after compaction.

**What**

A remote host can now be asked to contribute an attachment notice, both when attachments are gathered for a turn and when they are rebuilt after a conversation is compacted, in the latter case tagged with the trigger `post_compact`. The call is skipped entirely when no remote host is attached, and both call sites sit behind a condition that is not decided anywhere in this build.

**Details**

- The entry point is new; it does not exist in 2.1.241.

**Evidence**

`post_compact`

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

### Turns emit start and complete events, and a plugin can append to the final answer

Each turn now emits start and complete events a plugin module can hook and append text to.

**What**

Every interactive turn now opens a turn id, emits `turn.start` with the text you submitted, and on finishing emits `turn.complete` with the last real assistant message, elapsed milliseconds, whether it was aborted, and the turn id. A module handler that returns text differing from the model's answer has that text appended to the transcript as a system notice prefixed with the names of the handlers that contributed. `turn.step` exists as a third event.

**Details**

- The events are emitted unconditionally for interactive turns; nothing happens unless a plugin module registers handlers for them.

**Evidence**

`turn.complete`

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

### Two sessions on one artifact can hand comment replies to each other, claim step gated off

Two sessions watching the same artifact comments can hand off, but the takeover step is off.

**What**

When two live sessions of the same conversation are both set to reply to artifact comments, the old behaviour was a warning that every comment would get answered twice. This build adds a real handover protocol with wording for all three outcomes: one session took over, one yielded, or they are still doubling up. The step that actually makes a takeover stick is behind `tengu_cobalt_plinth_thistle`, which falls back to off.

**Details**

- New exported operations request a takeover, hand a claimed artifact back, and announce that a taken-over artifact stopped.
- The takeover request is issued at the conflict site, but the claim it depends on is only computed when the gate reads true.
- There is a second early return inside the takeover routine that reports a disabled state; what drives that check is not identifiable from the surrounding code.
- The gate name is new in this build. Without server configuration, users still see the old double-reply warning.

**Evidence**

`tengu_cobalt_plinth_thistle`

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

### Build constants point at a hooks worker script

Build metadata now points at a bundled worker script, so plugin function hooks can run separately.

**What**

The metadata block carrying the version, build time and commit now also carries a path to a bundled worker script for plugin function hooks, present in every copy across the bundle, meaning hooks can run in their own entry point rather than inline.

**Evidence**

`"/$bunfs/root/src/plugins/functionHooks/hooks-worker/hooks-worker.js"`

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

### Hook commands are parsed to find the script behind them

Hook commands are now parsed to work out which script they run, without running them.

**What**

A new parser reads a hook's shell command and works out whether it ultimately runs an identifiable script file, which lets Claude Code reason about a hook without executing it.

**Details**

- Handles quoting, `$HOME` and `${CLAUDE_PROJECT_DIR}` prefixes, interpreter names such as python, bash, node, deno and bun, and `uv run`.
- Returns either the interpreter and resolved script path, or a reason it gave up: `shell_syntax`, `unsupported_interpreter`, `uv_project`, `args_form`, `no_path`, `shell_prefix` or `unsupported_platform`.
- Refuses paths inside Claude Code's own bookkeeping directories (`shell-snapshots`, `session-env`, `projects`, `file-history`, `backups`).
- Reading a candidate script rejects anything that is not a regular file, is hard-linked (`multiply_linked`), or is oversized (`too_large`).

**Evidence**

`unsupported_interpreter`

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

### Hook events are tagged by where they could run

Each hook event is now labelled by where it should run: locally, deferred, low value or container-internal.

**What**

A new table labels each hook event with where it belongs: session start, session end and notification hooks run locally; permission requests are deferred; setup, compaction, teammate-idle, task and elicitation hooks are marked low value; and config, worktree, instructions, directory, file-change and message-display events are marked internal to the container.

**Details**

- The table is classification data with no user-facing surface, and reads as triage for which events a separate hooks worker would be handed.
- No flag or code path was observed acting on the labels in this build.

**Evidence**

`container_internal`

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

### Plugins can carry a server-issued id in a sidecar file

A plugin can now carry a server-issued id in a file next to it, tying it to a server record.

**What**

The loader now reads a JSON file sitting next to a plugin and accepts a `server_plugin_id` from it, letting a plugin be tied back to a server record.

**Details**

- The id must match `plugin_` followed by up to 64 alphanumeric characters.
- A sidecar that is not valid JSON, is not an object, or has a missing or malformed id is logged and ignored, and the plugin still loads.
- Read whenever the sidecar exists; nothing gates it.

**Evidence**

`server_plugin_id`

- Area: Plugins
- Names: `server_plugin_id`
- Tier: Under the hood
- Useful: 2/5
- Signal: 4/5

### `/plugin validate` inspects hooks modules instead of a declared capabilities list

Plugin validation now loads your hooks modules and reports what they hook; the capabilities field is ignored.

**What**

Validating a plugin's hooks.json now loads each JavaScript hooks module it names, refuses any path that points outside the plugin directory, and reads the module to report which events it hooks and which plugin APIs it calls. A `capabilities` field in the file is now reported as ignored and safe to delete.

**Details**

- A plugin naming more than one hooks module is reported as refused by the loader.

**Evidence**

`'capabilities' is no longer read`

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

### `/web-setup` tip and status row for Claude Code on the web

A tip points you at /web-setup to connect GitHub for running sessions on the web.

**What**

Claude Code now points you at `/web-setup` to connect GitHub for running sessions on the web. The one-off tip appears only when the `gh` CLI is signed in locally, the repo remote is not GitLab or Bitbucket, and the web connection currently reads as not connected.

**Details**

- A status line reports either "GitHub connected" or "Not set up · /web-setup to connect GitHub".
- The tip is shown once, and uses the GitHub account `gh` is already signed in to.

**Evidence**

`to use Claude Code on the web with the GitHub account gh is signed in to`

**Usage**

`/web-setup`

- Area: Cloud Sessions
- Names: `/web-setup`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### `claude plugin eval init` refuses to scaffold in the wrong directory

Eval scaffolding refuses to create files outside a plugin or skill folder unless you pass --eval-dir.

**What**

Running eval scaffolding outside a plugin or skill folder now errors and exits 1 instead of creating files where they do not belong, unless you pass `--eval-dir`. Output directories are also vetted: a symlinked base is refused, and when ownership vetting is on, so is a directory owned by another user.

**Details**

- The refusal is recorded as `cwd_not_a_plugin`.
- Path checks throw an escape error rather than silently writing outside the intended base.

**Usage**

`claude plugin eval init --eval-dir ./evals` **Evidence** ` is not a plugin or skill folder`

- Area: Plugins
- Names: `claude plugin eval init`, `--eval-dir`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Clearer error when an effort level needs thinking turned on

You now get told when an effort level needs thinking on, and what turned thinking off.

**What**

A new error branch explains that the effort level you picked is not available with thinking turned off on this model, tells you to raise effort or re-enable thinking, and names what disabled thinking: the `MAX_THINKING_TOKENS` environment variable, the `alwaysThinkingEnabled` setting, or the way the session was started.

**Details**

- Triggered by the shape of the API error, so it applies wherever the request is made from.

**Evidence**

`isn't available with thinking turned off on this model`

- Area: Effort
- Names: `MAX_THINKING_TOKENS`, `alwaysThinkingEnabled`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Cloud sessions report queued commands dropped by an interrupt

Headless cloud clients now see a cancelled status for each queued command an interrupt dropped.

**What**

A tool driving a cloud session through the headless `--cloud` client now gets a 'cancelled' status, and only that status, for each queued command an interrupt with `cancel_queued:true` dropped, so it can tell that those commands will never run.

**Details**

- The cancellation messages follow the interrupt's own success response and are not ordered against the result of the turn that was interrupted.
- Identifiers you do not recognise may appear, because another client connected to the same session may have queued the prompt.
- Documented for the stdout stream in `-p` and SDK sessions, headless `--cloud` client only.

**Evidence**

`A host driving a cloud-hosted session through the headless `--cloud` client also receives 'cancelled' (and only that state) after its interrupt with cancel_queued:true`

- Area: Cloud Sessions
- Names: `--cloud`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Goal check-ins now quote the goal

Goal check-ins quote your goal, and you can change how often they run with an environment variable.

**What**

Periodic goal check-in reminders now open with the goal itself in quotation marks, so it is clear which goal is being checked. Check-ins run unless a server flag turns them off, and the interval can be overridden with `CLAUDE_CODE_GOAL_CHECKIN_MINUTES`.

**Details**

- The remote flag `tengu_saffron_wren` controls the feature and falls back to on when the server says nothing.

**Evidence**

`Goal check-in: \xAB`

- Flag `tengu_saffron_wren`: Off by default, switched on for this account (read for one account on one subscription tier against v2.1.242; this account: on, anonymous baseline: on, compiled default: off)
- Area: Autonomous Mode
- Names: `CLAUDE_CODE_GOAL_CHECKIN_MINUTES`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Hooks can opt in or out of running for cloud sessions, per entry

Each hook can carry a cloud field choosing whether cloud sessions may run it.

**What**

Each hook entry in settings can now carry a `cloud` field. `device` offers that hook to a cloud session even when its script lives somewhere the session could write to or cannot be pinned to fixed contents; `skip` never offers it. Leaving it out keeps the default, where only pinned, unwritable command hooks are offered. An unrecognised value is read as `skip`.

**Details**

- The setting applies only to that one hook entry in that one settings scope, not to hooks generally.
- The message that forwards a hook to a cloud session now carries a matching `author_opt_in` flag recording that the hook was explicitly opted in.
- The description of pinning changed: the machine re-verifies the script's bytes before every run, rather than capturing them once.
- Forwarding as a whole still depends on a server-side feature flag and on policy; when the flag is off, the reason reported is `hook_forwarding_disabled: flag_off`.

**Evidence**

`'skip': never offer it to cloud sessions.`

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

### Malformed `modelPicker` and `modelPricing` settings now warn instead of being silently ignored

Bad modelPicker or modelPricing settings are now dropped with a warning naming the file and path.

**What**

Two new validators drop bad values and say so, naming the settings file and the path within it. A `modelPicker` that is not an object with an `options` array is dropped whole, a non-boolean `replaceBuiltInOptions` is dropped, and individual malformed option rows are dropped while the remaining rows still apply.

**Details**

- `modelPricing` is dropped if it is not an object, if `multiplier` is not a number in the range (0, 1], or per-model `overrides` rows are malformed.
- Keys named `constructor` or `__proto__` in `overrides` are refused.
- Validation results are memoised per settings file, so the warnings do not repeat on every read.
- Runs on settings load; nothing gates it.

**Evidence**

`"multiplier" must be a number in (0, 1]. It was ignored.`

- Area: Settings
- Names: `modelPicker`, `modelPricing`, `replaceBuiltInOptions`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Managed settings can now merge across sources instead of the top one winning

Admins can set managedSourcesBehavior to merge managed settings sources instead of only the top one.

**What**

A new enterprise settings key, `managedSourcesBehavior`, accepts `"first-wins"` (the default, and exactly today's behaviour) or `"merge"`. Under first-wins the highest-priority managed source present is the whole managed tier. Under merge, all present managed sources are deep-merged with fixed precedence and arrays unioned, so a lower source can contribute keys instead of being discarded. Any admin who sets the key gets it on this build; everyone else sees no change.

**Details**

- Managed source precedence is server-managed, then MDM / OS policy, then `managed-settings.json`.
- Some keys never merge and are owned whole by the highest source present: `fallbackModel`, the restriction allowlists (`allowedMcpServers`, `availableModels`, `strictKnownMarketplaces`, `allowedChannelPlugins`) and the login pins (`forceLoginOrgUUID`, `forceLoginMethod`, `forceLoginGatewayUrl`).
- The key is only honored when set in the highest-priority source, so a lower source cannot opt itself into merging.
- Windows HKCU settings and `--managed-settings` never take part in the merge.
- The key is stripped from the settings object before composition, so it never leaks into effective settings. It does not exist in 2.1.241.

**Evidence**

`managedSourcesBehavior`

- Area: Managed Settings
- Names: `managedSourcesBehavior`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Memory writes into read-only stores say plainly that nothing was shared

Writes into read-only memory stores now warn they were saved locally only and will be overwritten.

**What**

Writing to a file under a memory store mounted read-only now returns a notice that the write was saved locally only, will not sync, and will be overwritten by the next pull, along with a remedy naming the writable stores to use instead. A per-turn scan also lists files that exist only on this machine and tells the model to correct the user if it claimed the content was saved. Set `CLAUDE_CODE_DISABLE_MEMORY_RO_UNSAVED_NOTICE` to turn the scan off.

**Details**

- The remedy line is computed from the stores that are actually writable, not a fixed string.
- The per-turn summary is queued as context for the model, so the correction happens in the conversation rather than only in tool output.

**Evidence**

`This file's memory store is mounted read-only: writes are never synced, and the next sync pull will overwrite local edits with server content. This write was saved locally only and is NOT in shared memory.`

- Area: Memory
- Names: `CLAUDE_CODE_DISABLE_MEMORY_RO_UNSAVED_NOTICE`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Mocked MCP servers register under the real server name and deny everything else

Mock servers replace real ones by name, allow mocked tools and deny the rest; a mock_calls grader is added.

**What**

The stand-in servers take the place of the plugin's own MCP servers under the same name, auto-allow the tools that have mock files, and deny every other tool on that server. A new grader target, `mock_calls`, grades what the plugin asked the server to do rather than what it said.

**Details**

- A case can carry its own `mocks/` directory, and `mocks/` becomes a reserved name inside eval directories.
- Frontmatter supports `expect:` input assertions that abort the run on violation, and `error: true` to return a tool error.
- Each run's JSON gains a `mocks` block with per-server responder kinds, a call tally of total/errors/unmocked, and warnings, plus an `aborted` record.
- Failures are reported through telemetry key `cli_plugin_eval_mocks` with outcomes standin_registration, standin_identity, standin_tools_missing, standin_integrity and aborted_by_mock.
- `claude plugin eval` is itself early access, enabled per organisation or by an env var, and prints that it is unavailable otherwise.

**Evidence**

`mock_calls`, `--mocks <mode>`, `Mock stand-ins for MCP servers, from <eval dir>/mocks/`

- Area: Plugin Eval
- Names: `mock_calls`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### New `tool_host_result` system message in stream-json output

Tool host output arrives as its own system message in stream-json, easier to script against.

**What**

Output from a tool host (a separate process that runs a tool on Claude Code's behalf) now arrives as its own system message in `--output-format stream-json`, so scripts can read it directly instead of parsing it out of an attachment.

**Details**

- The message carries the `tool_use_id`, the host name and its working directory, an optional disposition and label, an `unverified` flag, and the output lines.
- No flag gates it; it is emitted whenever the underlying attachment appears.

**Evidence**

`tool_host_result`

- Area: Agents
- Names: `--output-format stream-json`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### The first reply can wait briefly for forwarded settings to arrive

A cloud session can hold its first reply until your settings arrive; two env vars shorten the wait.

**What**

A cloud session can hold the first turn until your forwarded settings have been applied or a timeout fires, so they affect that first reply instead of the next one. Two environment variables tune the wait, CLAUDE_CODE_HOME_SEED_HOLD_TIMEOUT_MS and CLAUDE_CODE_HOME_SEED_VERDICT_TIMEOUT_MS, and both are clamped against built-in caps, so they can only shorten it.

**Details**

- The confirmation notice switches between " (in effect for this reply)" and " (from your next message)" depending on whether the hold succeeded.
- Setting the hold timeout to zero or less skips the hold entirely.
- Outcomes reported to telemetry: released, timeout, verdict_timeout, flag_timeout, interrupted, gate_off.

**Usage**

`CLAUDE_CODE_HOME_SEED_HOLD_TIMEOUT_MS=500 claude` **Evidence** `CLAUDE_CODE_HOME_SEED_HOLD_TIMEOUT_MS`

- Area: Cloud Sessions
- Names: `CLAUDE_CODE_HOME_SEED_HOLD_TIMEOUT_MS`, `CLAUDE_CODE_HOME_SEED_VERDICT_TIMEOUT_MS`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Zoom takes a scale, and batched actions can now return images

Zoom takes a scale for the returned image, and batched actions can now return screenshots inline.

**What**

Zoom accepts an optional `scale` for the returned image, bounded by fixed limits, with the instruction that region and click coordinates always stay in the full-resolution frame. Batches may now include screenshot and zoom actions, whose images come back interleaved with the other results.

**Details**

- Coordinates written inside a batch refer to the full-screen screenshot taken before the call; afterwards, the most recent full screenshot the batch produced becomes the reference for the next call.
- The `scale` property is only present when the adaptive-resolution capability is on, and both places in this build that supply that capability set it to false.
- The repeated allowlist sentence in tool descriptions was factored into one shared string rather than repeated per tool.

**Evidence**

`Screenshot and zoom actions are allowed and their images are returned interleaved with the per-action outputs. `

- Area: Computer Use
- Names: `scale`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### open_app can launch without stealing focus, and teach mode can be declined for good

Apps can now be launched in the background without stealing focus, returning a window id.

**What**

Launching an app during computer use now has two modes: a background launch that does not bring the app to the front and leaves it reachable through the per-app tools, and the normal launch that brings it forward. The launch returns a window id when one is available.

**Details**

- The request to record a teach-mode demonstration now has an explicit declined result, which tells the model not to ask again in this session.
- A separate refusal, cu_lock_held, is returned when another Claude session took the computer while the approval prompt was still open.
- Screenshot coordinate conversion now reports which coordinate frame a point is in whenever that frame differs from the scaled image.
- Background launching needs a host that supplies app-scoped control and an app lock already held; otherwise the front-of-screen behaviour applies.

**Evidence**

`In background app mode, the launch does NOT bring it to the front`

- Area: Computer Use
- Names: `open_app`
- Tier: Use it now
- Useful: 3/5
- Signal: 3/5

### Listing and describing artifact types no longer asks permission, and listing can be filtered

Listing and describing artifact types no longer prompts, and listing takes a filter.

**What**

Listing the published artifact types available to you and describing one are now treated as read-only and skip the permission prompt with a stated reason. Listing gained a text filter, capped at 200 characters. Creating an artifact type in the cloud is still off: it needs `CLAUDE_CODE_ARTIFACT_TYPE_CLOUD_CREATE` or a remote-config gate whose built-in fallback is false.

**Evidence**

`Listing the published Artifact types available to the user is a read-only action`

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

### /code-review says when --post did nothing

Using --post on a local /code-review now tells you it was ignored and points you to --comment.

**What**

Typing `--post` on a local `/code-review` now gets a one-line correction instead of silent ignoring: the flag "applies only to the `/code-review ultra` cloud review and was ignored", and it points you to `--comment` for posting findings as inline PR comments, worded differently depending on whether the target is a GitHub pull request.

**Details**

- The same preamble carries the existing ultra-unavailable notices, "ultra (cloud review) requires claude.ai account access this session doesn't have" and "isn't available in this environment", both linking https://code.claude.com/docs/en/ultrareview.
- It also still carries the "Ignoring unrecognized effort" and last-used-level notices.

**Evidence**

`cloud review and was ignored`

- Area: Code Review
- Names: `/code-review`, `--post`, `--comment`
- Tier: Use it now
- Useful: 4/5
- Signal: 2/5

### Clearer account of a tool call interrupted by an MCP disconnect

When an MCP server drops mid tool call, you now see whether the call ran instead of ambiguity.

**What**

When the connection to an MCP server drops mid tool call, Claude Code now says what became of that call instead of leaving it ambiguous. You get a spinner while it reconnects to find out, a plain statement when the call never ran, and a note when a reply arrives afterwards.

**Details**

- The spinner line reads `Reconnecting to ${e} to learn what happened to the call…`, with the server name filled in.
- One outcome states the call "did not run. It is safe to retry."
- A late reply after reconnect is labelled as the recorded result of the original call, making clear the command was not executed a second time.
- No flag found; this rides the existing MCP reconnect path. None of these strings exist in v2.1.241.

**Evidence**

`this is the recorded result, the command was not run again`

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

### MCP OAuth stops sending a client id that only works for loopback

MCP OAuth stops sending a client id that only works for loopback, and repairs saved entries.

**What**

Both MCP OAuth providers now withhold the metadata-document URL as the client id whenever the redirect URI is not that document's loopback `/callback` address, falling back to a configured client or dynamic registration instead. A saved client entry holding a stale metadata URL as its client id is repaired in place.

**Details**

- `MCP_OAUTH_CLIENT_METADATA_URL` still overrides which document URL is used.

**Evidence**

`stale CIMD client_id repair`

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

### MCP servers reconnect automatically when the connection drops

Dropped MCP connections now retry with backoff, showing attempt counts before being marked failed.

**What**

Connected MCP servers over HTTP or SSE now retry the connection with backoff when the transport closes, showing the server as pending with the current attempt number and the maximum, and marking it failed only after the attempts run out.

**Details**

- Applies only to non-stdio, non-SDK servers; locally spawned stdio servers and in-process SDK servers are unaffected.
- Pending state carries `reconnectAttempt` and `maxReconnectAttempts`; the terminal failure reads "Connection closed again while reconnecting".
- The watcher is installed unconditionally with no flag, and stands down while another reconnect is already in flight or the run is shutting down.

**Evidence**

`Connection closed again while reconnecting`

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

### PDF reads return the document and page images

Reading a PDF now returns the document itself plus one image per page.

**What**

Reading a PDF now returns the actual file as a base64 document block, and page extraction returns one image block per page instead of a summary line. A page that fails to render falls back to a placeholder for that page only.

**Details**

- The document block is `{ type: "document", source: { type: "base64", media_type: "application/pdf" } }`, included only when base64 data is present.
- A failed page yields text saying it "could not be processed as an image"; the other pages still come back.
- Re-reading the same file path carries base64 data and already-extracted pages forward from the previous result.
- Page ranges are validated up front: formats like `1-5`, `3` or `10-20` are accepted, and a range exceeding the per-request page maximum is rejected.

**Evidence**

`could not be processed as an image`

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

### The remote-tools runtime loads as a separate chunk on first use

Running tools on another machine now loads only when used, and its gate is off in this build.

**What**

The machinery for running tools on another machine is fetched with a dynamic import and cached rather than being linked into the main bundle, keeping it off the startup path entirely while its gate is off.

**Details**

- If the import throws, the failure is logged and the loader resets, so a later access retries instead of staying broken.
- With no runtime loaded, there is simply no remote tool host.

**Evidence**

`createRemoteToolHostsRuntime`

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

### Build metadata and a mock-server entry point

This is version 2.1.242, and the CLI gained an --eval-mock-server mode taking two arguments.

**What**

This is version 2.1.242, built from commit 2482ce9083708842d832441fde1721626f306157. The CLI gained a first-argument mode `--eval-mock-server` taking two positional arguments, which prints the error and exits 1 on failure, and the build constants gained a URL for the bundled hooks worker script.

**Evidence**

`--eval-mock-server: `

- Area: CLI
- Names: `--eval-mock-server`
- Tier: Use it now
- Useful: 2/5
- Signal: 3/5

### Hook settings accept `cloud`, and unknown values fail safe

Hook entries accept a cloud field, and unrecognised values are treated as skip rather than erroring.

**What**

The hooks settings schema gained an optional `cloud` field on each entry, taking "device" or "skip". Anything it does not recognise is read as "skip", so a settings file written by a newer version still loads instead of erroring.

**Details**

- `cloud: "skip"` holds the hook on this machine, reported with the reason "author_skip".
- `cloud: "device"` exempts a command hook that could not otherwise be pinned, but is not honoured when the entry lives in a file inside the checkout, and is ignored for after-edit hooks.
- The surrounding forwarding path can still be refused by policy ("hook_forwarding_disabled: policy"), so this is a per-entry knob on an existing gated path, not a feature on its own.

**Evidence**

`@internal Where this hook may run when a cloud session is driven from this machine.`, `@internal Where this hook may run when a cloud session is driven from this machine. 'skip': never offer it to cloud sessions; 'device' or omitted: offered (an HTTP hook has no script to pin). Applies to this entry only. An unrecognised value reads as 'skip' (the file still loads).`

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

### Policy helpers can declare whether their settings merge or replace

Admins can say whether a per-OS policy helper's settings replace or merge over the source.

**What**

A managed-settings policy helper, the per-OS script an administrator points Claude Code at to generate settings, now takes an `outputBehavior` field that says whether the settings payload it produces replaces that source's settings or merges over them. The field is only read on the per-OS `policyHelpers` entries; on the singular `policyHelper` key it is ignored and the helper's output replaces the policy tier regardless. An unrecognised value on a per-OS entry is not tolerated: the whole entry is dropped and no policy helper runs on that operating system.

**Details**

- `"merge"` is the one value checked explicitly at the points where the generated payload is applied; anything else on a valid entry means replace.
- The field is new in this build and did not exist in 2.1.241, so existing helper configurations keep replacing until they opt into merging.
- Setting `outputBehavior` on the singular `policyHelper` produces a status-only warning during managed-settings loading and validation, saying it is ignored; nothing about the helper's behaviour changes.
- An unrecognised value is reported as leaving it unknown whether the payload replaces or merges over that source's settings, and the entry is discarded rather than guessed at.
- Helper fingerprinting now also records the byte size of each inline helper script under its `policyHelpers.*` key, and the helper store keeps a set of retired helper paths.

**Evidence**

`"outputBehavior" is unrecognized, so whether the payload replaces or merges over this source's settings is unknown.`, `on the singular policyHelper is ignored`, `"outputBehavior" on the singular policyHelper is ignored`

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

### Unix-socket messaging explains how to connect

The unix-socket messaging listener logs the exact command to connect to it.

**What**

The unix-socket messaging listener now logs how to connect, including the exact `nc -N -U` form, and warns that a connection which does not send a complete line within the first-line deadline is closed.

**Evidence**

`"nc -N -U"`

- Area: Cross-Session
- Names: `nc -N -U`
- Tier: Use it now
- Useful: 2/5
- Signal: 3/5

### Cloud headless can ask its host to refresh the OAuth token

A host program can take over refreshing your login token, off unless an env var is set.

**What**

Instead of refreshing the login token itself, the client can hand that job to the program embedding it. This is installed only when `CLAUDE_CODE_SDK_HAS_OAUTH_REFRESH` is set and `CLAUDE_CODE_ENTRYPOINT` names one of a fixed set of entry points, so it is off by default.

**Details**

- Whether the bridge was installed is reported as `oauth_bridge` in the session-started event.

**Evidence**

`CLAUDE_CODE_SDK_HAS_OAUTH_REFRESH`

- Area: Auth
- Names: `CLAUDE_CODE_SDK_HAS_OAUTH_REFRESH`, `CLAUDE_CODE_ENTRYPOINT`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Artifact `write_db` accepts a batch of writes under one approval

Artifact database writes can be batched so several documents commit under one approval.

**What**

The artifact database tool takes `db_op: "batch"` with a `writes` array, so several set, update and delete operations commit together under a single approval instead of prompting once per call. Each entry can supply its document inline or point at a local JSON file to read it from. The tool description now tells the model to prefer a batch over separate calls when it has more than a couple of documents to write.

**Details**

- each entry in `writes` names an op of set, update or delete, plus `collection`, `doc_id`, and exactly one of `data` (the document inline) or `file_path` (a local file holding a JSON object to use as the document)
- a batch is capped at a fixed number of entries
- where the server supports batch writes the whole batch commits all-or-nothing; where it does not, the entries are applied one at a time in order, the result states which of the two modes ran, and a failure part-way through names the entries already written
- two entries targeting the same document in one batch are refused with the error `db_batch_duplicate`
- reading a `file_path` goes through the normal Read permission rules, and the approval is re-checked before the batch runs so entries cannot be added, removed or switched between inline and file after you approved it
- collection paths were tightened from 1-31 to 1-15 slash-separated segments, each segment allowing letters, digits and `_ - . ~ : @ +`, so subcollections nest like `boards/b1/columns`
- available wherever the artifact tool is offered, with no separate flag or setting to turn it on

**Evidence**

`applied one at a time in order (this server has no batch write yet); a failure part-way leaves earlier entries written`, `such writes at once`, `Database collection path: 1-15 "/"-separated segments (letters, digits, _ - . ~ : @ + per segment), so subcollections nest like "boards/b1/columns". Required for read_db and write_db.`

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

### Artifact publish can delete files and set per-file live state

Artifact publishes can delete files and set per-file live state, but only in patch publishes.

**What**

The `files` argument now takes `null` entries to remove a file, and per-file `live` and `reseed` flags. Removals only work in a patch publish that names `baseVersion`; a first publish or one without a named base is refused with an explanation.

**Details**

- `index.html` cannot be removed.
- Duplicate paths in one publish are rejected.
- The manifest-size error now counts files plus removals together.

**Evidence**

`"index.html" is the page itself and can't be removed. Drop that entry.`

- Area: Artifacts
- Names: `baseVersion`, `reseed`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Enabling a plugin now fails when its dependencies are disabled

Enabling a plugin with disabled dependencies now stops and prints the enable commands to run first.

**What**

If a plugin's dependencies are installed but switched off, enabling it now stops with a message naming them and giving the exact `plugin enable` commands to run first.

**Evidence**

`currently disabled. Enable `

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

### Keybindings that terminals cannot tell apart are rejected

Binding ctrl+[, ctrl+i or ctrl+h is now an error since terminals send them as Escape, Tab and Backspace.

**What**

Binding `ctrl+[`, `ctrl+i` or `ctrl+h` is now an error, because terminals send those as Escape, Tab and Backspace and Claude Code cannot distinguish them.

**Details**

- Reported at severity error, so the binding is rejected rather than warned about.

**Evidence**

`Cannot be rebound - identical to Escape in terminals`

- Area: Terminal UI
- Names: `ctrl+[`, `ctrl+i`, `ctrl+h`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Publishing an artifact can now remove files from it

You can remove files when publishing an artifact instead of re-listing everything you keep.

**What**

A publish can delete supporting files as well as add them, so an update no longer means re-listing everything you want to keep. Pass a `removeFiles` list of paths, or set a path to `null` in the map form of `files`, and those files are dropped from the artifact's next version while every file you left out stays published as it was.

**Details**

- The permission prompt counts the deletions as "removing N file(s) from its current version", and appends "(its other published files stay)" when only some of the artifact's files are going.
- Removal is refused when the publish would create a new artifact rather than update an existing one, since there is nothing yet to remove from.
- Removed names are resolved and checked to sit inside the artifact's `files/` directory before being unlinked, and are recorded in the published `manifest.json` under a new `removed` key.
- Publish arguments also accept `liveFiles`, new in this build; `files`, `removeFiles` and `liveFiles` each count as a content-bearing update on their own.
- The local publishing stub used during development no longer wipes the whole `files` directory on each publish, so unlisted files persist instead of vanishing.
- The defence against symlinks pointing outside the artifact now walks every parent directory up to the artifact root instead of checking only the immediate parent, and a file that fails the check is skipped and logged rather than aborting the whole publish.
- If the account does not have multi-file publishing, the refusal now states plainly that nothing was published or removed and the artifact is unchanged.
- Publish telemetry gained a count of removed files.

**Evidence**

`artifact stub: skipping removal of `, ``A `null` entry in `files` removes a file from an existing artifact``, `files left out of the map are kept and null removes that path`

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

### SDK `interrupt()` can cancel queued messages and reports which ones

SDK interrupt can now cancel queued messages and tells you which ones it cancelled.

**What**

In the Agent SDK, `interrupt()` now accepts an options object. Passing `cancelQueued: true` also cancels messages waiting in the queue, and the resolved value includes a `cancelled` list of message ids alongside the existing `still_queued`. Previously it took no arguments.

**Details**

- `cancelQueued: true` adds `cancel_queued: true` to the control request sent to the CLI.
- Available to any SDK caller on this build, with no flag.
- The surrounding SDK transport and query code was re-bundled from lazily loaded module wrappers into plain top-level classes, with no behaviour change.

**Usage**

`const result = await query.interrupt({ cancelQueued: true }); console.log(result.cancelled, result.still_queued);` **Evidence** `cancel_queued`

- Area: Agents
- Names: `cancelQueued`
- Tier: Use it now
- Useful: 3/5
- Signal: 2/5

### Background shells that are still working are no longer killed during cleanup

Cleanup no longer kills background shells that are still doing work.

**What**

Cleanup checks a background shell's status before tearing it down and skips the kill when the shell is still running or otherwise still in use.

**Evidence**

`bash:`

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

### Disabled plugins no longer report load failures

Plugins you turned off stop complaining about failing to load.

**What**

Plugin loading now tracks whether each entry is enabled, so a missing plugin path, a manifest that fails to load, a failed copy into the version cache or a failed download only surfaces an error when the plugin is actually turned on. Plugins you disabled stop producing noise about failing to load, while enabled ones report as before.

**Details**

- The disabled check matches on the manifest name as well as the path, and honours plugin content authored in the repository itself.
- Applies unconditionally in the session plugin loader, with no flag or setting to turn it off.
- Loaded plugins can now also carry a server-issued plugin id.
- When a plugin installed through a command has a missing cache entry and background command execution is switched off, the error names the real update command for that plugin, `claude plugin update <plugin>@<marketplace>`, falling back to the generic form when the plugin's marketplace is not known.
- Credentials are now passed through the path that copies a plugin into the version cache, so authenticated sources copy rather than fail.

**Evidence**

`Skipping load-failure error for disabled plugin`, `claude plugin update <plugin>@<marketplace>`

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

### Artifact watch explains when the artifact belongs to another organization

Watching an artifact from another of your organizations now tells you to sign in there.

**What**

Trying to watch an Artifact that lives in a different organization of yours now returns a message telling you to run `/login` and sign in to that organization, alongside the existing no-subscription and watch-limit refusals.

**Usage**

`/login` **Evidence** `This Artifact is in another of your organizations. Run /login and sign in to that organization to watch it.`

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

### Auth status shows whether analytics are off

Auth and doctor output now show whether analytics are switched off.

**What**

The status output used by the auth and doctor views gained an `analyticsDisabled` field next to the existing logged-in state, auth method and API provider, so you can confirm from one place that telemetry is switched off.

**Evidence**

`analyticsDisabled`

- Area: Telemetry
- Names: `analyticsDisabled`
- Tier: Use it now
- Useful: 2/5
- Signal: 2/5

### Blocked skills-directory plugins say which policy to change

A blocked skills-directory plugin names the two managed policies your admin would need to change.

**What**

When managed settings stop a plugin from a skills directory loading, the message names the two policies responsible, `strictKnownMarketplaces` and `blockedMarketplaces`, so you know what to ask an administrator to adjust.

**Evidence**

`strictKnownMarketplaces or blockedMarketplaces`

- Area: Plugins
- Names: `strictKnownMarketplaces`, `blockedMarketplaces`
- Tier: Use it now
- Useful: 2/5
- Signal: 2/5

### OIDC federation tokens get an on-disk cache, with an env override for its location

Federation tokens are cached on disk, and you can move the cache with a new env var.

**What**

Setups that federate to Anthropic through an OIDC identity token now cache the resulting federation token on disk, so a token is not re-fetched every time. The directory is created mode 0700 and the location can be pointed elsewhere with the new environment variable `CLAUDE_CODE_FEDERATION_CACHE_DIR`.

**Details**

- The cache file name is derived from a hash of the federation rule id, org, workspace, service account, scope, base URL and the identity token, so a change in any of those uses a different file.
- Caching is refused, with the reason logged and the session continuing uncached, if the directory is group- or other-accessible, is owned by a different user, if no identity token can be read, or if there is no config directory.
- Only applies when the auth type is OIDC federation and the environment-variable configuration path is in use.

**Evidence**

`CLAUDE_CODE_FEDERATION_CACHE_DIR`

- Area: Auth
- Names: `CLAUDE_CODE_FEDERATION_CACHE_DIR`
- Tier: Use it now
- Useful: 2/5
- Signal: 2/5

### Policy helper output can merge into a source's settings instead of replacing it

Policy helper output can merge into a source's settings, so a failure costs less.

**What**

An entry in an enterprise policy helper can declare how its produced managed settings combine with the settings of the source that delivered it: `replace`, the default and current behaviour, or `merge`, a deep merge over that source's own settings. Under merge a helper that fails costs only the keys it would have contributed, rather than the whole policy.

**Details**

- Under merge, helper keys win and arrays replace rather than concatenate.
- Helper validation now also records the script's size and a flag for whether the entry merges its output.
- Status notices include non-status errors when the helper merges.

**Evidence**

`"How the helper's managedSettings compose with the settings of the source that delivered this entry: 'replace' (default) — the output is the policy; 'merge' — the output is deep-merged over that source's own settings (helper keys win, arrays replace), so a failed helper costs only the delta"`

- Area: Managed Settings
- Names: `merge`, `replace`
- Tier: Use it now
- Useful: 2/5
- Signal: 2/5

### /reload-plugins now warns about LSP tool changes too

Reloading plugins now warns when the reload will add or remove the LSP tool, not just MCP servers.

**What**

The warning that a reload will invalidate your prompt cache used to mention only MCP server changes. It now also says whether the reload adds or removes the LSP tool, softened to "may add"/"may remove" when the plugin loader failed in a way it can recover from.

**Details**

- The LSP part is skipped when LSP servers are disabled, when tool search is on, or when there are no cached tools to compare against.

**Evidence**

`may add the LSP tool`

- Area: Plugins
- Names: `/reload-plugins`
- Tier: Use it now
- Useful: 2/5
- Signal: 1/5

## New Features

### Cloud workflow launches survive a worker restart

A cloud workflow turn cut off by a worker restart now resumes instead of vanishing.

**What**

A workflow launch that the server dispatched but that was cut short by a cloud or runner worker restarting mid-turn is now retried on startup instead of being dropped, so the turn picks up where it left off. Resumption is deliberately narrow: the restarted worker must be strictly newer than the one that dispatched the launch, and the saved bundle of work must still match the digest the server reported. Bundle downloads retry on failure too.

**Details**

- Only runs on remote transports, requiring a remote session with both the `CLAUDE_CODE_REMOTE` and `CLAUDE_CODE_REMOTE_SESSION_ID` environment variables set; a local run never takes this path.
- Additionally gated on `CLAUDE_CODE_RESUME_INTERRUPTED_TURN`, which the supervisor process sets only from the second worker start onward, so a first start never attempts a resume.
- Skipped resumes log a reason: `not_a_later_epoch` when the restarted worker is not newer than the dispatching one, and `digest_differs` when the recorded work no longer matches the server's digest.
- After a cap on retry attempts the launch is settled as spent rather than retried forever, and launch telemetry gained an `attempt` field; bundle download retries log `workflow_launch_bundle_fetch_retry`.
- The resume code is loaded on demand at startup, and only when the restored worker state actually carries a pending workflow launch.

**Evidence**

`workflow_launch_attempts_spent`, `workflow_launch_resume_skipped`

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

### Computer use refuses to confirm saves into protected locations on macOS

While driving a Mac app, Claude refuses to confirm saves into sensitive startup or ssh files.

**What**

When Claude is driving a Mac app and a save sheet is open, several actions are now refused: putting a path or a shell-startup, ssh or launch-agent file name in the file-name box, pressing Return in the Go-to-Folder box, dragging or holding a press while the sheet is up, and typing into a window that is editing a protected document.

**Details**

- All refusals report `save_panel_restricted`, a reason code that does not exist in the previous build.
- Return in Go-to-Folder is refused as unverifiable, since the destination cannot be checked before it commits.
- Navigation shortcuts still work in a window editing a protected document; only text entry is blocked.
- The checks run on macOS and depend on the host being able to inspect the open sheet and the document being edited.

**Evidence**

`save_panel_restricted`

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

### The remote-control bridge recovers when the server forgets it

Remote control re-registers and reattaches your sessions if the server forgets this machine.

**What**

If the server replies 404 to the bridge's work poll, meaning it no longer knows this machine, the bridge now re-registers itself and resumes instead of shutting down, re-attaching every live session. It is controlled remotely by `tengu_glimmering_glade`, which is on when the server says nothing.

**Details**

- Re-registration asks to reuse the same environment id, up to 3 attempts; if the server hands back a different id, the replacement is deregistered and the bridge gives up.
- On success it adopts the new environment secret and re-queues each active session.
- Verbose output shows `Environment re-registered; resuming poll.` or `Re-registration failed; retrying after backoff.`
- New event `tengu_bridge_env_reregister` with outcomes register_rejected, register_transient, replaced, reregistered, recovered, requeue_dropped, gave_up, plus a `bridge_env_reregistered` log line.

**Evidence**

`tengu_bridge_env_reregister`

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

### Directory sync can upload from a git-tracked snapshot, and refuses whole batches by name

Directory sync can upload only git-tracked changes and refuses whole batches with a named reason.

**What**

Directory sync gained a path that compares your working tree against a pinned commit and uploads only tracked changes and removals. Hashing runs with git filter drivers disabled, so smudge/clean filters cannot alter what is uploaded. When a batch cannot be represented or afforded it is refused outright rather than partially uploaded, with a named reason: tracked_unrepresentable, tracked_too_large, tracked_unreadable, overlay_over_budget, or too_many_removed.

**Details**

- Builds its file list by consulting `git check-attr` (text, crlf, eol, ident, working-tree-encoding, filter), `git check-ignore`, `git ls-files -s`, plus the `core.filemode` and `core.autocrlf` settings.
- A warning telemetry point, dir_sync_overlay_pin_attrs_unread, fires when the attributes of the pinned commit cannot be read.
- None of these reasons or telemetry names exist in 2.1.241.

**Evidence**

`tracked_unrepresentable`

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

### Cloud-hosted sessions now refuse local CLI options instead of quietly dropping them

Running with --cloud now tells you which local CLI options were ignored or refused, and why.

**What**

When a run targets a cloud-hosted session (the agent runs in a cloud container rather than on your machine), each CLI option is now either forwarded, ignored with a visible notice, or refused with a reason. Refusals are grouped as bypass, tool_restriction, sdk_mcp, then unsupported. This applies on the `--cloud` path with no separate flag.

**Details**

- Refused as bypass: `--dangerously-skip-permissions`, `--permission-mode bypassPermissions`, or a settings file whose defaultMode is bypassPermissions.
- Refused as tool restrictions: `--disallowed-tools`, `--tools` (unless it resolves to just "default"), `--disable-slash-commands`, `--safe-mode`, `--strict-mcp-config`, and `--settings`/`--managed-settings` carrying deny or ask rules.
- Refused as sdk_mcp: in-process SDK MCP servers declared in `--mcp-config`. Refused as unsupported: `--json-schema` and `--bare`.
- `--settings`, `--managed-settings` and `--mcp-config` that only express preferences are ignored, with a notice telling you so.

**Evidence**

`bypassPermissions is not available in a cloud-hosted session`

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

### Cloud sessions answer each control request with a specific yes, forward, or reason it cannot

Mid-run changes to a cloud session are either handled, forwarded, or refused with a stated reason.

**What**

Requests that change a running session (switch model, toggle MCP, rewind, change permission mode) are now routed one of four ways for a cloud session: handled locally, forwarded, forwarded while later sends are held, or rejected with a message naming the reason. Anything anchored to your local machine is rejected rather than silently failing.

**Details**

- Handled locally: initialize, interrupt, end_session, cancel_async_message. Forwarded with sends held: set_model, mcp_toggle, rewind_conversation, set_permission_mode and others.
- Rejected because they name a path on this machine: set_cwd, add_directory, register_repo_root, rewind_files.
- Also rejected: mcp_set_servers and mcp_message (in-process MCP servers), remote_control, channel_enable, ultrareview_launch, the claude_/mcp_ auth subtypes, set_color, poll_event, stage_file, and agent-originated requests such as can_use_tool, hook_callback and elicitation.
- Unrecognised request types get a generic "is not supported in a cloud-hosted session" rejection, recorded in telemetry as "unknown".

**Evidence**

`MCP server changes are not available in a cloud-hosted session yet; this machine's MCP servers reach cloud sessions through the device link`

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

### Computer use caps what Claude can be granted per app class

Browsers can only be seen, terminals only clicked, and Claude's own app never granted.

**What**

When Claude asks for control of an application, browsers can now only ever be granted see-only access, terminals and IDEs only clicking (no typing or pasting), and Claude's own application can never be granted at all, because that would let the model change its own permissions.

**Details**

- Browser refusals point the model at the Claude in Chrome extension MCP for actual interaction; terminal and IDE refusals point it at Bash.
- Browser and terminal refusals allow one re-request in the same turn, confirmed once, and that allowance expires at the end of the turn.
- First-request warnings can be turned off ahead of time with skipFirstRequestWarnings.

**Evidence**

`You requested access to Claude's own application.`

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

### Files Claude deletes in a cloud session go to a session trash, with a reason for every case

Deletions from a cloud session move your files to a session trash, never erasing them.

**What**

Directory sync can now bring deletions back down to your machine, and it never erases anything: removed files are moved to a per-session trash folder. Each refusal has its own user-facing line, so you can see why a delete was skipped.

**Details**

- Deletes are refused in bursts, capped per turn, and skipped entirely when no trash folder can be created.
- Skipped for protected names, credential-looking names, symlinked names, and when `.git` sits on a different volume.
- Kept, not deleted, when your local copy changed or does not match what Claude removed. Conflicted copies sit beside the files they belong to.
- Bookkeeping (state, staging, trash) lives in a `.ccr-dir-sync` directory at the top of the working tree, which the model is told to ignore.
- Each sync engine has its own enabled check driven by remote config; the archive engine's check raises "directory sync flag unknown just now" when the flag has not resolved yet, so what happens before it resolves depends on the server.

**Evidence**

`conflicted copies sit beside the files they belong to.`

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

### Permission prompts can open on the decline option

Some permission prompts now open on No, with digit shortcuts hidden so you cannot approve accidentally.

**What**

A permission request can now be marked so its dialog opens focused on No, with the decline option listed first and the digit shortcuts hidden, so an ask "must not be approvable by a single stray keystroke". It is set per request by whatever raises the prompt rather than by any setting, and a couple of internal permission results, including some Artifact-related asks, already use it in this build.

**Details**

- The field is named `default_to_no` and carries guidance that a host "should not pre-select approve when set".
- No flag, setting or environment variable gates it, and there is no way to turn it on for all prompts.
- The flag travels over the control protocol Claude Code uses to talk to a host UI, added to the set of boolean permission fields carried across that boundary.
- Editors and other hosts that draw their own permission prompts have to honour it themselves; nothing forces the behaviour on them, and a host that ignores it falls back to its usual prompt with approve selectable as normal.

**Evidence**

`a terminal-style prompt opens on its decline option and takes no digit shortcut`, `default_to_no`

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

### Tools can suppress every permission update a hook proposes

Some tools now discard every permission rule a hook proposes and hide "always allow".

**What**

A tool can now declare that it discards all permission changes returned by hooks or permission handlers, not just "always allow" rules. When it does, those updates are stripped before they reach the session's permission context and the "always allow" option is withheld.

**Details**

- The new per-tool method `suppressesAllPermissionUpdates(input)` sits alongside the existing `suppressesAlwaysAllowRule`.
- Available on this build for any tool that implements the method; tools that do not are unaffected.
- A dedicated refusal message covers the case where a hook tried to inject a machine target into a call: only the model's own input may name a machine.

**Evidence**

`only the model's own input can name a machine, so it was not run.`

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

### Incoming file bundles are quarantined and audited before they land

Incoming file bundles are unpacked and verified in a scratch directory before touching your repo.

**What**

When a cloud session sends your machine a bundle of file changes, Claude Code now unpacks it into a throwaway directory first and checks it end to end before letting any of it into the repository. A bundle that does not match what it claims to contain is refused whole.

**Details**

- The bundle is indexed with `index-pack --strict` into a per-run quarantine directory, then verified four ways: every tip it names must be a commit the bundle actually delivered, the stated commit range must name exactly the objects carried, and anything it depends on must be history this side already has.
- A record of the delivered object ids is written before the bundle is moved into permanent storage, so a half-applied transfer is detectable later.
- Failures produce one fixed reason rather than a per-check message, and the quarantine is swept. Stale quarantine directories, leftover `.keep` files and orphaned delivery records are cleaned up on subsequent runs.
- Part of the git sync engine; there is no separate switch for the audit.

**Evidence**

`the pack was refused: malformed, not self-contained, or not exactly what its range names`

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

### Secrets in commits now block sync, not just uploads

Sync now refuses to carry commits containing credential-looking or Read-denied files.

**What**

Before taking a snapshot, sync scans the commits the cloud session has not seen yet. If any of them contain files whose names look like credentials, or that your Read permission rules deny, sync refuses to carry those commits and tells you that deleting the file in a later commit does not fix it.

**Details**

- Staged files matching the same rules also stop the snapshot, with advice to unstage rather than commit.
- The remedy given is to amend or reset the offending commits.
- Paths held back are listed back to you as "Left on this machine, not synced".

**Evidence**

`amend or reset the commits (deleting the file in a later commit is not enough)`

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

### New `list_apps` tool for picking a Mac app to request access to

Claude can list your installed and running Mac apps before asking to control one.

**What**

When Claude is driving individual Mac apps, it can now list installed and running applications to find the right identifier before asking for access. Parameters are `query`, `running_first` (default true), `limit` (default 25, maximum 200) and an opaque `cursor` for paging.

**Details**

- Declared as having no side effects and callable before any app has been granted.
- Offered only when the host is app-scoped and the platform is macOS; on other platforms the call path answers `list_apps is not available on this platform.`

**Evidence**

`list_apps is not available on this platform.`

- Area: Computer Use
- Names: `list_apps`
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Apps on another macOS Space are now reachable, with a tool to pull them over

Claude can pull a Mac app from another Space to the one you are viewing.

**What**

A new `app_bring_to_current_space` tool moves a granted app's window to the Space you are looking at. Claude is also told that when an app sits on a different Space it can still screenshot it and usually click and type there.

**Details**

- Grouped in the read tier alongside listing windows, screenshotting and accessibility search.
- Offered when the host exposes the bring-to-active-Space capability; where it does not, Claude is instructed to ask you to switch Spaces yourself.
- Minimized windows are described as reachable: the first click or type un-minimizes the window without bringing the app to the front.

**Evidence**

`app_bring_to_current_space`

- Area: Computer Use
- Names: `app_bring_to_current_space`
- Tier: You'll notice
- Useful: 2/5
- Signal: 3/5

### Computer use tells the model when background app control is switched off remotely

If background app control is switched off remotely mid-session, Claude is told to stop retrying.

**What**

If per-app background control is disabled for your device by remote configuration while a session is running, the model now gets an explicit notice that app locks were released, that it should not retry the app_* tools for the rest of the session, and that it should fall back to display-scope tools.

**Details**

- The screen-takeover consent flow records distinct failure kinds in telemetry: allowlist_empty, cu_lock_held, takeover_unavailable, takeover_declined, takeover_not_answered, unattended_no_approver, feature_unavailable, state_conflict.
- The consent dialog gives up after 290000 ms.
- Whether the control is on is decided by remote configuration, not by anything in this build.

**Evidence**

`Per-app background control (the app_* tools) was just turned off for this device by a remote configuration change.`

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

### Sessions are told when Artifact comment replies change hands

You get on-screen notices when Artifact comment replies move to or from another session.

**What**

When one session of a conversation takes over the automatic replies to Artifact comments, the session that gave them up now says so, says when they come back, and says when they come back already stopped by the other session. Headless runs get this as log lines fed to the model, interactive runs get the same events as on-screen notices, and the wording distinguishes a handover from the two outcomes that already existed.

**Details**

- Applies when a second session of the same conversation claims the automatic replies to Artifact comments, and again when that claim is released back.
- In headless mode the events are written to the log and given to the model, starting with `[headless] yielded Artifact comment replies for `.
- Interactive sessions carry the same three events as notices in the terminal rather than log lines.
- A resume message can now report ` that another session of this conversation took over`, alongside the existing cases of the replies being swept up and the watching task being killed.
- The comment-monitor notice states that monitors stay off after you switch conversations until the Artifact is published again.

**Evidence**

`[headless] yielded Artifact comment replies for `, ` that another session of this conversation took over`

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

### Plugins can submit prompts, and the model is told where they came from

A prompt submitted by a plugin is labelled to Claude as coming from that plugin.

**What**

When a plugin submits a prompt, Claude Code now wraps it in text naming the plugin and telling the model to address the message, whether the prompt starts a turn in your place or arrives partway through one alongside the next tool result.

**Details**

- Two cases are distinguished in the wrapper: a prompt submitted between turns, and one that lands inside a turn already running.
- The wrapper is emitted whenever a plugin submits a prompt; no setting turns it on or off.

**Evidence**

`This is how Claude Code surfaces a prompt a plugin submits between `

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

### Permission prompts can be marked so a stray keystroke cannot approve them

SDK hosts are told when a permission prompt must default to no and not pre-select approve.

**What**

The permission request sent to SDK hosts over the control protocol carries an optional flag saying the prompt must default to no. A terminal-style prompt opens on the decline option and accepts no digit shortcut, and hosts drawing their own approve buttons are told not to pre-select approve.

**Details**

- the field is optional, so requests without it keep the existing behaviour
- it is advisory for custom hosts: the schema states the expectation rather than enforcing it

**Evidence**

`"True when the ask must not be approvable by a single stray keystroke (PermissionAskDecision.defaultToNo): a terminal-style prompt opens on its decline option and takes no digit shortcut. Hosts rendering approve options should not pre-select approve when set."`

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

### Scheduled loops get a usage table

Scheduled loops now show a table with runs, tokens and cost per loop.

**What**

Scheduled loops now render as a table with columns Loops, every, runs, tokens, per run, and last run, so you can see cost per loop at a glance.

**Details**

- The "per run" column is dropped at narrow terminal widths.
- Rows beyond what fits are summarised as "… N more".
- Schedules are abbreviated to short forms such as `5m`, `2h`, `1d` or `at HH:MM`; loops that are not on a fixed schedule show `dynamic`.

**Evidence**

`per run`

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

### Messages to a cloud session wait until it has your local files

Messages to a cloud session wait until your uploaded files have actually landed there.

**What**

When directory sync is on, a message you send to a cloud session is held until the session confirms it applied the files uploaded from your machine, polled with exponential backoff up to a cap. If that never happens you get a plain-language explanation instead of silence.

**Details**

- Timeout produces its own message; a partial apply names how many files could not be fetched.
- Each container-side refusal maps to a sentence, covering a bundle timeout, the uploaded state not being a descendant of what the session has, a dirty working tree, a refused checkout and a git timeout.
- Two new telemetry events record the wait and each held send.
- Only sessions with directory sync active reach this path.

**Evidence**

`tengu_dir_sync_seed_gate_send`

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

### SDK sessions can set max thinking tokens and thinking display at startup

SDK sessions can set a thinking token limit and thinking display when they start.

**What**

When a session starts, the control channel now sends a `set_max_thinking_tokens` request carrying a maximum thinking token count, a thinking display setting, or both, if the caller supplied them.

**Details**

- Sent only when `thinking.maxThinkingTokens` or `thinking.display` is provided; otherwise nothing extra goes out.
- Sits next to the existing set-mode request, which switched from generating a random UUID to a shared id helper.

**Evidence**

`subtype: "set_max_thinking_tokens"`

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

### Dedicated error when an API stream sends no first byte

A stream that never starts now gives a specific error with timings and a matchable code.

**What**

A stream that produces nothing within the first-byte window now raises a specific error rather than a generic timeout, and it carries the window length, how long was actually waited, and a stable error code you can match on.

**Evidence**

`"No response from API within the first-byte window"`

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

### Skills report unsaved and read-only problems as a heads-up block

Skill problems like unsaved or read-only locations now print as a visible heads-up block.

**What**

Loading or refreshing skills can now queue warnings, such as a skill being unsaved or its location being read-only, and print them as a bulleted block under a "⚠ Heads-up:" heading instead of failing quietly.

**Details**

- The queueing call is made from three points in the skill load and refresh paths.
- Each note is truncated to 1000 characters when serialised.
- A second printer emits one indented pointer-prefixed line per note for a compact form.

**Evidence**

`\u26A0 Heads-up:`

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

### Cloud session creation shows per-step progress and a clearer cancel message

Starting a cloud session shows labelled steps, byte sizes, and a clearer cancel message.

**What**

Starting a cloud session now walks through labelled steps: checking the checkout, packaging and uploading local changes or the repository, then creating the session, with byte sizes and a note on how much history is included. Cancelling tells you whether the create request had already gone out, in which case a session may exist anyway.

**Details**

- History scope is annotated on the line, for example "with history" or "snapshot without history".
- A fallback line explains that the session will start from the branch tip with file sync off when local changes cannot be packaged.

**Evidence**

`Cancelled after the create request was sent, so a cloud session may still have been created`

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

### Cloud file sync now refuses unsafe project roots and explains every skip

Cloud file sync refuses to run from your home or root directory and explains every skip.

**What**

Sync will not run at all when the checkout root is your home directory or above it, the filesystem root, or a directory that holds Claude Code's own configuration folder, and it declines hidden or system directories under home. When it does run, the messages now name the files and say why each one was skipped.

**Details**

- Three outright refusal reasons for dangerous roots, plus two more for hidden and system directories under home (recorded as `folder_hidden_under_home` and `folder_system_under_home`).
- New per-file explanations: nested git repositories are never entered, files inside dependency and build folders are skipped, uploads that are too slow are kept local and retried, and slow uploads that eventually landed are reported.
- Deletions can now be held back or withheld as a group rather than silently applied.
- The over-budget message now states the cap on new files per message.
- These refusals are unconditional once sync is considered; the notifier text grew from about 9 lines to 17.

**Evidence**

`File sync is off here: this checkout's git root holds, or sits inside, Claude Code's own configuration folder`

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

### Only one Claude Code window per machine syncs a given session

If the same session is open twice, only one window syncs files.

**What**

File sync now takes a lock before writing, so if you have the same session open in two windows only the first one syncs. The second says so and stays quiet instead of both writing at once.

**Details**

- The lock is a file under a `writer-locks` directory keyed to the session's side repository.
- If a window's lock is taken over or lost while it held it, sync stops in that window with an explicit message rather than continuing blind.
- If the process stalls long enough to be caught by the interval check, the lock is re-taken and cached state is thrown away rather than assumed to still be valid.

**Evidence**

`Another Claude Code window on this machine is syncing this session's files; this one won't`

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

### Sync explains skipped dependency folders and cloud container restarts

Sync tells you when vendor or node_modules files are skipped, and pauses if the cloud side restarts.

**What**

Per-file sync now tells you when tracked files under directories like `vendor/`, `build/` or `node_modules/` are being left out for the session, and it recognises when the cloud side has stopped: if that container is restarted and loses track of which files were synced, sync halts with a message saying so, and resumes when the peer comes back.

**Details**

- Two reasons are given for leaving a dependency directory out: it holds more files than a single turn can track, or those files are not checked out on this machine.
- The halted-peer state and the dependency-exclusion reporting are both new; neither exists in v2.1.241.

**Evidence**

`its container was restarted and lost track of your synced files`

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

### Binary updates can download a zstd-compressed build

Updates can arrive as a compressed build, so downloads get smaller with no setting to change.

**What**

The updater can now fetch a compressed build and decompress it locally, cutting download size. There is no client setting: it happens when the release channel's update manifest publishes a checksum and size for your platform, so it can be enabled per platform without shipping a new Claude Code.

**Details**

- The updater fetches a compressed manifest alongside the normal one. When it supplies a 64-character hex checksum for your platform and a positive size, it downloads the `.zst` variant, streams it through a zstd decoder, enforces the manifest's decompressed size as a hard cap mid-stream, and verifies the sha256 of the compressed bytes.
- Any failure (missing fields, checksum mismatch, size overrun, a rejected zstd frame) falls back silently to the uncompressed URL and records `update_download_zst_fallback`.
- Successful downloads now report a `compressed` field on `tengu_binary_download_success`.
- Manifest fetches now retry when the connection drops.

**Evidence**

`Decompressed binary exceeds the manifest size of `, `Decompressed binary larger than manifest size`

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

### Artifact document writes can go out as one atomic batch, falling back to one at a time

Artifact document writes can commit as one batch, falling back to one at a time.

**What**

Several documents can now be written to an artifact database in a single request. The permission prompt and result text were rewritten to describe the batch ("Write N documents ... in one batch") and ask-rules are matched per write by index. If the server does not accept batches, the writes are applied sequentially and the model is told plainly that the result is not atomic. Which path you get is negotiated with the server, not set by a flag.

**Details**

- Batch requests are capped at a fixed number of writes, each path validated and de-duplicated, with size guards reporting `duplicate_path`, `batch_size` and `batch_too_large` against a 1 MiB server request limit.
- A server rejection is remembered client-side with a timestamp and an "unsupported" window, so the client stops re-attempting batch shape for a cooldown period.
- On sequential failure the result reports the index that failed and how many entries committed before it.
- Failure messages distinguish "nothing was written" from an outcome that is unknown and may have partly committed.

**Evidence**

`written one write at a time, in order (this server does not take batch writes yet, so it was not atomic)`, `this server does not take batch writes yet, so the batch was applied one write at a time and is NOT atomic`

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

### Sessions that fail to re-attach are retried, then handed to token refresh

Sessions that fail to reattach after a reconnect are retried up to five times.

**What**

After the bridge re-registers itself, any session it could not put back on the work queue is held and retried on later idle polls, up to 5 times, rather than being lost immediately.

**Details**

- On exhaustion the bridge logs a line ending `attempts; left to token refresh` and records outcome `requeue_dropped` on the same `tengu_bridge_env_reregister` event.
- Same remote switch as the re-registration path itself.

**Evidence**

`requeue_dropped`

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

### Cloud sessions accept only seven live settings changes

Only seven settings can be changed mid-run against a cloud session; others are rejected by name.

**What**

Changing settings mid-run against a cloud-hosted session is limited to model, advisorModel, effortLevel, ultracode, fastMode, viewMode and alwaysThinkingEnabled. Any other key is rejected by name.

**Details**

- The rejection lists the offending keys, truncating long names and capping the list with "(and N more)".
- A settings payload that is not an object is rejected outright.

**Evidence**

`apply_flag_settings requires settings to be an object`

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

### Cloud sessions report which host settings they ignored

A cloud session now reports which of your flags and settings keys it ignored.

**What**

The startup message a cloud-hosted session sends back now includes a `not_applied` list, so a tool driving Claude Code can show you once that a command-line flag, a settings key or a startup field was ignored.

**Details**

- Each entry has a `name` of the form `--flag`, `settings.<key>`, `managed-settings.<key>` or `initialize.<field>`, plus a `kind` of `lost`, `kept` or `preference`. `kept` means attaching to an existing session kept that session's configuration instead.
- Capped at 40 entries, with `lost` ones listed first.
- Present only on startup messages from the headless stream-json client of a cloud-hosted session, and absent in every other mode.

**Evidence**

`` `not_applied`, when present, lists what the host passed that this session does not apply ``

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

### IDE and SDK hosts are told when telemetry is off for the session

Editors and SDK hosts are told when telemetry is off so they can match.

**What**

The protocol's init/system message carries a new optional boolean, `analytics_disabled`, so an editor extension or SDK host can switch off its own direct telemetry to match the CLI. If the field is absent, the state is unknown, which is what an older CLI looks like.

**Details**

- Set when the session's privacy level requires it, when `DISABLE_TELEMETRY` or `DO_NOT_TRACK` is set in any settings tier, or when running through a third-party provider or gateway.
- Readable by anything that consumes the init/system message; nothing gates it.

**Evidence**

`analytics_disabled`

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

### Artifact wake subscriptions can be refused for a whole session

If artifact wake subscriptions are forbidden, Claude Code stops retrying for the session.

**What**

When the artifact service returns HTTP 403 for a wake subscription, Claude Code now latches that refusal for the rest of the session instead of retrying. Later publishes are not armed, auto-subscribes fail immediately with the reason `subscribe_forbidden` rather than touching the network, and only an explicit watch re-checks with the service. The latch is dropped if it was recorded with no relay active and a relay later comes up.

**Details**

- A durable state field records the refusal along with the server's message and whether a relay was active at the time; this field does not exist in 2.1.241.
- Two telemetry codes distinguish the first refusal from later suppressed attempts: `subscribe_forbidden_latched` and `subscribe_forbidden_suppressed`. Clearing the latch emits `subscribe_forbidden` as a clear event.
- User-facing copy explains the state rather than showing a generic error.
- Related in the same area: edits made through an MCP tool can now arm auto-reactions, tracked as an `mcp_write` arming source.
- No client-side flag turns this on; it is entirely driven by the server's 403 response.

**Evidence**

`"armed when this session edited the page through an MCP tool"`, `The artifact service refuses wake subscriptions from this session, for any artifact until the session ends, so retrying will not help.`, `subscribe_forbidden`

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

### Startup can wait on a remote managed-settings refresh, with a deadline

Startup can briefly wait for refreshed managed settings, with a deadline so it never hangs.

**What**

A new startup step races a fetch of remote managed settings against a consent prompt and reports one of four outcomes: refreshed, failed, timed out, or consent pending, meaning a consent dialog is up so startup does not block on the fetch. When the deadline expires it records a `deadline_expired` telemetry event.

**Details**

- Telemetry event name: `remote_managed_settings_startup_await`.
- Neither the event name nor the outcome strings exist in 2.1.241; this is a new path used only by managed and remote settings deployments.

**Evidence**

`remote_managed_settings_startup_await`

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

### Gateway credentials reworked with a TLS pin store

Gateway credential handling reuses pinned entries and gives clearer errors for bad base URLs.

**What**

With `CLAUDE_CODE_USE_GATEWAY` set, credential handling now runs in its own step before the normal path: it reuses an already-pinned entry when the token matches instead of re-pinning on every call, keeps the previously pinned URL, and reports a bad `ANTHROPIC_BASE_URL` as a typed error rather than a generic one. Two new messages explain the pin store refusing a credentials file that is a symlink or unreadable.

**Details**

- Requires `CLAUDE_CODE_USE_GATEWAY` together with `ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN`.
- The existing warning for a missing base URL or auth token is unchanged.

**Evidence**

`gateway TLS pin store refused: the credentials file is a symlink`

- Area: Auth
- Names: `CLAUDE_CODE_USE_GATEWAY`, `ANTHROPIC_BASE_URL`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Result messages report how many of your sends are still queued

Result messages now report how many of your sends were still queued.

**What**

SDK and stream-JSON result messages now carry an optional integer `queued_turn_count`, the number of sends you initiated that were still waiting in the command queue when the result was produced. It is live on this build, not gated.

**Details**

- The field is optional in both result schemas and is populated on the result message itself.
- One helper zeroes it under a single condition, so a reported zero is not always a literal empty queue.
- The name does not appear in the previous bundle.

**Evidence**

`queued_turn_count`

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

### Six more Firefox and Chromium forks recognised as browsers

Zen, LibreWolf, Waterfox, Mullvad, Floorp and Thorium are now recognised as browsers.

**What**

Browser detection and automation now treat Zen, LibreWolf, Waterfox, Mullvad Browser, Floorp and Thorium as browsers, so they are picked up the same way Firefox and Chrome are.

**Evidence**

`io.gitlab.librewolf-community.librewolf`

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

### Directory sync progress shows up in the transcript

Directory sync progress now appears inline in your transcript.

**What**

When Claude Code is syncing a directory, progress notices are now a proper transcript item rather than something the renderer had to be told about separately, so sync updates appear inline with the conversation.

**Details**

- The transcript renderer and the event filter previously accepted only polled-event items on that branch; they now also accept a `dir_sync_notice` item.
- These notices are excluded from one of the paths that collapses consecutive messages, so a sync notice does not get folded into neighbouring output.
- Sync state also gained scheduling fields for cancelling and re-running a rescan when the session goes idle (`idleRescanCancel`, `idlePassOwed`, `cancelGeneration`).

**Evidence**

`dir_sync_notice`

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

### Eval graders can inspect calls made to mock tools

Eval graders can inspect the calls that hit mock tools during a run.

**What**

A grader can now check the tool calls that hit mock stand-ins during an eval run. If no mocks applied, the grader gets an explicit reason instead of empty data. The note about plugins in an eval directory was also tightened.

**Details**

- Requires `--mocks`; without it, or when no `mocks/` directory applies to the case, the grader is told mock stand-ins were not active in that run.
- The eval directory note now always says the plugin is not loaded unless you name its directory, and states that naming a directory counts as consent to load it.
- Resolving a suite path also records the directory that consent was granted for.

**Evidence**

`no mock stand-ins were active in this run (--mocks off, or no mocks/ directory applies to this case)`

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

## Improvements

### Claude checks a checkout's git configuration before running git in it

Claude inspects a synced checkout's git config first and refuses to run git there if it looks unsafe.

**What**

Before running git in a synced directory, Claude Code now inspects that checkout's configuration and refuses when it looks unsafe, with a named reason. This blocks a repository from steering git commands run on your behalf.

**Details**

- Refusal reasons include a git executable inside the checkout, a config file inside the work tree that contributes to configuration, include chains that are too deep or too numerous, non-UTF-8 hook or filter names, reftable or borrowed object stores, and a `.git` file pointing back inside the work tree.
- It first probes the git binary to confirm it honours configuration from the environment, which requires git 2.31 or newer.
- When it does run git, hooks are pinned off by name, filter drivers are cleared, and `GIT_CONFIG` and `GIT_ATTR_SOURCE` are blanked from the environment.

**Evidence**

`[dirSync] refusing to run git in `

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

### Directory sync tracks deletions and holds the first turn until the initial sync settles

Your first turn now waits for the initial directory sync, and deletions and nested repos are reported.

**What**

Synced directories now record deletions explicitly rather than just omitting files, and your first turn waits for the initial sync to finish instead of starting against a partial tree. You also get notices about nested repositories and files that were left behind.

**Details**

- The sync journal gained deletion records, counts of deletions withheld, dependency-directory counts, a halted marker, and a block describing an upload in progress (its generation, start time, whether it was abandoned, and which writer owns it). Pending entries now track whether they have been judged and whether the peer has seen them.
- The first-turn wait reports its outcome and how long it waited as `tengu_dir_sync_first_turn_hold`.
- If the worker finds the directory has been re-homed underneath it, it publishes a halt record rather than continuing.

**Evidence**

`tengu_dir_sync_first_turn_hold`

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

### File dialogs on Windows are locked down during computer use

In Windows file dialogs Claude can only type into the File name box; other input is blocked.

**What**

Inside common Windows file dialogs, Claude can now only type into the File name box. The address bar and search band refuse typing, and right-click, Alt-modified clicks and drag-and-drop inside the dialog are blocked outright.

**Details**

- Multi-key chords must be sent one key at a time inside a file dialog; tab and newline characters in typed text are refused.
- Refusals carry the reason code file_dialog_restricted, new in this build.
- The checks only run when the platform is win32 and the host reports both the focused control and the control under the pointer; otherwise the dialog is treated as an ordinary window.

**Evidence**

`file_dialog_restricted`

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

### Plain-language reasons why a local hook is not offered to a cloud session

Local hooks held back from a cloud session now tell you in plain words why.

**What**

When your local hooks are forwarded to a cloud session, each one that is held back now says why in prose rather than being quietly dropped.

**Details**

- Reasons include: after-edit hooks stay on this machine; only command and http hook types can be forwarded; a matcher written as a pattern rather than a plain list is refused; a script living somewhere the cloud session can write on this machine is held unless pinned; duplicates and registrations past the cap are dropped; an entry marked `cloud: "skip"` is skipped.
- Standing states get their own wording too: paused, declined, disabled, unsupported, invalid and stale registrations.

**Evidence**

`only command and http hooks can run for a cloud session`

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

### Windows file-manager windows get a click-only tier, and app lists are marked as data

Explorer windows are click-only with typing forbidden, and your installed apps list is marked as data.

**What**

Explorer-style shells now sit in their own click-only tier with typing explicitly forbidden, because the address bar, Search and Run all hand text to ShellExecute. The list of installed apps is wrapped in `<installed-apps>` tags with an instruction to treat it as data only, not instructions.

**Details**

- Screenshot and zoom batches gained a `save_to_disk` option.
- Zoom regions can be given in a `normalized_0_100` coordinate mode.

**Evidence**

`<installed-apps>`

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

### Finder needs its own grant, and the Chrome tool prefix changed

Clicking Finder, desktop or Dock needs a Finder grant, and the Chrome tool prefix was renamed.

**What**

The computer-use guidance now says clicking the desktop, the Dock or a Finder window needs a Finder grant, while the menu bar does not. The Chrome MCP tool prefix mentioned in the read-tier guidance changed from `mcp__Claude_in_Chrome__*` to `mcp__claude-in-chrome__*`.

**Evidence**

`Finder is an application like any other`

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

### Hook agents on cloud-served calls cannot run destructive Bash

Hook agents judging tool calls for a cloud session can no longer run destructive shell commands.

**What**

A hook agent evaluating a tool call served for a cloud session is now refused destructive shell commands, with the reason `served_call_hook_agent_destructive`.

**Evidence**

`served_call_hook_agent_destructive`

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

### Clearer explanations when a cloud session drops or a file is left out of it

Cloud session disconnects and skipped files now name a cause and a next step.

**What**

Cloud session failures now name a cause and a next step instead of failing quietly. Disconnects distinguish the stream closing, an untrusted device, a stale session needing a fresh sign-in on this machine, and a rejected attach, and creation failures cover a session that was made but could not be bound to this machine. The separate list of local files that were not sent to a cloud session was rewritten in the same pass, so each exclusion says which file it is about and why.

**Details**

- Disconnect and creation messages carry what to do, usually re-signing in on this machine or starting a new session because the old one may have been archived.
- Creation failures say whether the unbindable session was archived afterwards.
- Session ids are truncated and hashed in these messages, so an id beginning `cse_` is reported as `session_<hash>`.
- The file exclusion list leads with settings that failed to parse, which leaves your Read permission rules unknown, and points you at `/status`.
- Git filter exclusions name LFS and git-crypt as examples.
- Read rules set to deny or ask are described as covering the specific file rather than in general terms.

**Evidence**

`Cloud session disconnected`, `a git filter (LFS, git-crypt) applies to `

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

### Computer-use guidance is now Windows-aware and separates screen takeover

Computer-use guidance now names File Explorer on Windows and says elevated programs must come back to you.

**What**

The instructions for requesting computer access change by platform. On Windows they name File Explorer and warn that elevated programs, such as Task Manager, UAC prompts and admin installers, cannot be driven at all and should be handed back to you. They also state that granting access is not the same as granting screen takeover.

**Details**

- The stated reason on Windows: Windows blocks input sent from a lower-privilege process to a higher-privilege one, so no grant can work around it.
- Screen takeover has its own consent card, raised automatically the first time a display-wide tool runs after background work.
- App-scoped tools are described as able to run in the background but unable to reach menu-bar items, hover states, context menus or canvas drags.

**Evidence**

`cannot be controlled even when granted: Windows UIPI blocks input from lower-integrity processes.`

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

### Plugin-submitted prompts are labelled in the transcript

When a plugin submits a prompt for you, the transcript names the plugin above the message.

**What**

When a plugin submits a prompt on your behalf, the transcript now prints a dimmed header above the message naming the plugin. Messages without a plugin origin render exactly as before.

**Evidence**

`Prompt from the `

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

### Browser navigation is given to the safety judge as ground truth

The browser safety judge now sees where the tab actually navigated and judges against that.

**What**

When Claude Code drives Chrome, the safety judge that reviews each browser action now receives a navigation record naming the page the tab moved from and to, and is told to treat it as fact: judge the action against the URL actually landed on, not the stated intent.

**Details**

- The judge is instructed to treat form submissions, credential entry, code execution and data exposure on an unexpected origin as suspect.
- Always part of the Chrome judge prompt; no setting turns it off.

**Evidence**

`it is ground truth that the tab this action targets is on a different site`

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

### Cloud sessions inherit more of your local configuration

Cloud sessions now carry over your system prompt, fallback model, spending cap and allowed tools.

**What**

Starting a cloud session now forwards your custom system prompt, the text appended to subagent system prompts, your fallback model, your spending cap when it is above zero, and your allowed-tools list. Disallowed tools are sent as the full set rather than one legacy entry.

**Details**

- Each field is sent only when set locally.
- The create path also carries the initial prompt when it is withheld and a home-directory seed.

**Evidence**

`append_subagent_system_prompt`

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

### Cloud sessions tell you when your default permission mode stayed local

If your default permission mode did not carry into a cloud session, you are told why.

**What**

When your settings set a default permission mode that the cloud session does not start in, a notice now explains why it was withheld, either because auto mode is off on this machine or only the repository asked for it, or because it is not a mode a cloud session can start in, and names the mode the session actually uses.

**Details**

- Shown only when a settings default mode exists and differs from the mode sent to the session.

**Evidence**

`Settings kept on this machine: the default permission mode in your settings`

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

### Cost summary can note that figures use your organization's rates

Cost summaries can note that totals were priced at your organization's configured rates.

**What**

The cost summary builds its footnotes as a list rather than one fixed line, so it can show both the warning about unrecognised models and a note that the totals were priced at your organization's configured rates. Each note appears only when its own condition holds.

**Evidence**

`"at your organization's configured rates"`

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

### Managed hook skip message now says SDK callback hooks still run

The disableAllHooks notice now says SDK callback hooks still run while configured hooks are skipped.

**What**

When the managed `disableAllHooks` setting is on, the skip notice now states that configured hooks are skipped while hooks supplied as SDK callbacks still run, instead of implying all hooks are off. The worktree-creation failure message no longer lists `disableAllHooks` as a possible cause.

**Details**

- The worktree message now attributes a non-running hook to an untrusted workspace or a matcher that did not match.
- Hook invocation also passes storage and credentials through to the hook.

**Evidence**

`"WorktreeCreate hook failed: hook is configured but did not run (workspace not trusted or matcher mismatch)"`

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

### Plugin commands always run immediately

Plugin and command entries always run immediately now; the flag that gated it is gone.

**What**

The `tengu_immediate_model_command` flag that decided whether plugin and command entries ran without waiting is gone, and the roughly four descriptors that consulted it now hard-code immediate execution. What was dark-launched behind the flag is now the behaviour for everyone.

**Evidence**

`immediate: !0,`

- Flag `tengu_immediate_model_command`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.242; this account: on, anonymous baseline: on, compiled default: not a boolean we can read)
- Area: Plugins
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Repeated permission-classifier denials now abort a headless run

Repeated permission-classifier denials now stop a headless run instead of looping forever.

**What**

Auto mode counts how many times the permission classifier denies an action, both consecutively and in total for the session. Crossing the limit reports both counts and, in headless runs where permission prompts are suppressed, throws and stops the agent rather than looping. In interactive use the counters reset and the run carries on.

**Details**

- Behaviour splits on whether the session avoids permission prompts, which is what headless runs do.
- The abort surfaces as an error naming the cause rather than a silent stop.

**Evidence**

`Agent aborted: too many classifier denials in headless mode`

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

### Git commands now ignore repository-supplied replace refs and grafts

A repository can no longer use replace refs or grafts to change what history Claude Code sees.

**What**

Every git subprocess Claude Code runs is now given `GIT_NO_REPLACE_OBJECTS=1` and `GIT_GRAFT_FILE=/dev/null`, so a repository cannot use replace refs or a graft file to change what history Claude Code sees. This sits alongside the existing scrubbing of hook environment variables.

**Details**

- Applied unconditionally on the git environment path, with no opt-out.
- The shallow-repository probe gained a matching hardened path.

**Evidence**

`GIT_NO_REPLACE_OBJECTS`

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

### Extra usage tracked as its own rate-limit kind

Extra usage you bought is tracked separately, so ending a session for it no longer looks like hitting a weekly cap.

**What**

Usage bought on top of your plan is now a distinct rate-limit kind alongside the five-hour, seven-day and other windows, and when a session ends or resets for that reason it is treated like a user-initiated or account-switch reset rather than like hitting a weekly or budget ceiling.

**Details**

- Affects both the rate-limit display and the reset-reason handling.

**Evidence**

`"extra_usage"`

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

### Rules for naming claude.ai connectors in an Artifact

Claude now names claude.ai connectors by display name in Artifacts, and says connectors with odd characters must be renamed first.

**What**

When your session has claude.ai connectors attached, Claude now gets explicit rules for referring to them in an Artifact: use the connector's display name, "never the id or any `mcp__` segment", "because viewers resolve connectors by name only". Connectors with no name are described to you by the tools they offer, and connectors whose names contain anything beyond letters, digits, spaces and `. _ ( ) -` "cannot be declared at all until renamed" in claude.ai under Settings then Connectors.

**Details**

- The connector support itself already existed; this is new guidance that changes how Claude refers to and refuses connectors.

**Evidence**

`because viewers resolve connectors by name only`

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

### Artifact comments refuse to run on an org service key

On an org service key, Claude now says Artifact comments are unavailable instead of writing replies into the page.

**What**

A session authenticated with an organization service key rather than a personal login cannot read or post Artifact comments. Claude is now told to say so plainly instead of writing its replies into the page body.

**Details**

- Triggered by the kind of credential the session holds, not by any flag.

**Evidence**

`comments require a user-scoped credential`

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

### Teleport uploads refuse a tampered git directory on device sessions

On device sessions, uploading a repo with a tampered git directory now fails outright rather than being downgraded.

**What**

When uploading your repository to a cloud session, Claude Code now checks once whether this is a device session and, if so, builds the upload in hardened mode. In that mode a misplaced or tampered git directory fails the upload outright instead of being downgraded to a softer result, and is treated the same way as uncommitted credentials.

**Details**

- The hardened flag defaults to the device-session check result, and defaults off on Windows.

**Evidence**

`git_dir_tampered`

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

### Built-in hooks respect disabled and managed-only settings

Built-in hooks now honour hooks-disabled and managed-only settings instead of loading regardless.

**What**

Loading of Claude Code's built-in hooks now takes account of whether hooks are disabled, whether only managed hooks are allowed, and whether everything is switched off, can be skipped entirely, and reports the resulting live set so other parts of the app know which built-in hooks are in play.

**Evidence**

`notifyBuiltinHooksLoaded`

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

### Plugin render hooks may only produce five element types

Plugin UI hooks can only draw five element types; anything else is reported as a hook error.

**What**

A plugin hook that draws UI can build elements only from Box, Text, div, span and b, plus lowercase `box` and `text` aliases. Anything else throws inside the plugin's sandbox, so Claude Code reports it as a hook error rather than trying to render it.

**Details**

- Keys, refs and props whose value is null are stripped from the element before it is frozen.
- This applies to every render hook on this build; there is no setting to widen the tag set.

**Evidence**

`a render hook draws those and what next(e) returned`

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

### Plugin symlinks that escape the plugin directory are refused with a reason

Plugin symlinks pointing outside the plugin directory are refused, each case with its own message.

**What**

When loading a plugin or skill, Claude Code now checks each part of a symlink's target and refuses links that leave the plugin's own directory tree, route above it, point back at the plugin directory itself, or land in repository metadata. Each case gets its own message.

**Details**

- Eval case discovery separately refuses `mocks/` as a case directory name.

**Evidence**

`is a symlink that leaves the plugin tree`

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

### Stopping artifact background activity also drops remembered join approvals

Stopping an artifact's live rooms also drops remembered rejoin approvals and reports both.

**What**

Disarming an artifact's live rooms now reports both the rooms left and any remembered approvals to rejoin them, and tells the model not to republish in order to get back in unless you ask.

**Details**

- Previously only the rooms left were reported, so an approval could survive the stop.

**Evidence**

`do not republish to rejoin unless the user asks.`

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

### Windows shell surfaces are no longer grantable apps for computer use

On Windows the Start menu, search and Explorer shell can no longer be granted to Claude as apps.

**What**

On Windows, the Start menu, search UI and Explorer shell are now treated as OS surfaces rather than applications Claude can be granted control of.

**Details**

- Excluded process names: startmenuexperiencehost.exe, shellexperiencehost.exe, searchui.exe, searchapp.exe, searchhost.exe.
- Also excludes explorer.exe and anything under systemapps\, resolved from the WINDIR environment variable.
- Windows only; on other platforms nothing changes.

**Evidence**

`startmenuexperiencehost.exe`

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

### An Artifact version counts as seen only after the whole file is read back

An artifact version counts as seen only when the whole file came back unchanged.

**What**

When an Artifact's HTML is handed to Claude to read, Claude Code now tracks which lines came back along with the file's size, modification time and hash, and marks the version as observed only if the entire file was returned and the bytes still match.

**Details**

- Mismatches are recorded as `file_changed`, `file_unreadable`, `superseded` or `seed_not_persisted`.
- Cases that cannot be checked either way fall back to a held, unverifiable state rather than counting as read.

**Evidence**

`artifact_handover_read`

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

### Artifact type instructions are treated as untrusted text

Instructions bundled with an artifact type are wrapped as untrusted text that cannot widen permissions.

**What**

Instructions that come with an Artifact type are now wrapped in a preamble and a trailing warning saying they cannot grant permissions or widen the task, since anyone who can publish to the Artifact could have written them.

**Details**

- Two preambles exist: one for a trusted Artifact type and one for the case where the instructions could have come from any publisher.
- The trailing warning rules out fetching or publishing elsewhere, putting local files or credentials into the Artifact, and editing permission settings, CLAUDE.md or config on the instructions' say-so.
- Always applied as part of assembling the Artifact system prompt.

**Evidence**

`artifact-type-instructions`

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

### Administrator policy helper stands down when a remote settings payload supersedes it

A local admin policy helper shuts itself down when remote settings supersede the policy it came from.

**What**

If remote settings arrive that shadow the local MDM or file policy a policy helper was read from, the helper is now retired: its refresh timer stops, its state is cleared, and the settings tier change is announced and recorded in telemetry.

**Details**

- Runs when a remote payload lands and the helper was not itself armed from remote settings.
- Telemetry goes under the policy-helper settings event; both the message and this outcome value are new.

**Evidence**

`policyHelper: OS-admin helper pass retired; the remote payload that landed shadows the MDM/file policy it was read from`

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

### Directory-sync overlay always applies when one exists

The directory-sync overlay always applies now; the internal flag that could skip it is gone.

**What**

Syncing a directory to a cloud session previously checked an internal enable flag and could skip the overlay, warning that it was disabled. That flag and its skip path are gone: if an overlay exists it is applied and the sync start event fires.

**Details**

- The old `dir_sync_overlay_disabled` warning and its `{status:"disabled"}` result no longer occur.
- The sync request now carries a description of the overlay (sha256 and size) and an upload-only marker, set when your directory-sync consent is upload-only.

**Evidence**

`tengu_dir_sync_overlay_start`

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

### Session spawn aborts on host-specific write paths committed to a repo

A committed settings file granting host-specific write paths blocks session startup with instructions.

**What**

If a settings file checked into the repository grants write access to a host-specific path, the runner now refuses to start the session and tells the operator to move that entry into their user-level `settings.json`.

**Details**

- A hard refusal with no override.

**Evidence**

`Host-specific write-scope entries belong in the operator's user-level settings.json`

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

### Browser tools reached over a remote device get the same handling as local ones

Browser tools driven on another device now behave like local browser tools.

**What**

The prefix list used to special-case preview and browser tools now also matches browser tools proxied through the remote-devices connector, so driving a browser on another device behaves like driving one locally.

**Evidence**

`mcp__remote-devices__Claude_Browser__`

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

### Autonomous loop ticks can mark themselves as no-progress

Autonomous loop ticks can mark themselves as no-progress, collapsing repeated updates into one line.

**What**

The autonomous loop prompt now documents a per-tick `noop` boolean, and consecutive ticks marked `noop: true` collapse into one line in the terminal instead of printing repeated no-progress updates. The same wording was added to two prompt variants.

**Evidence**

`Consecutive \`noop: true\` ticks collapse in the terminal.`

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

### Loop ticks must report whether anything happened

Self-scheduled loops that do nothing now collapse quietly instead of scrolling filler past you.

**What**

A self-scheduled loop tick now has to tell the scheduler whether it changed anything, by passing a `noop` flag set true for a tick that did nothing. Quiet holds collapse in your terminal instead of scrolling past.

**Details**

- Applies to the three continuation prompts used by self-scheduled (non-cron) loops; cron-driven loops are unchanged.
- The scheduling tool already documented the flag: "Consecutive `noop: true` ticks are collapsed in the user's terminal view and tracked as a streak."

**Evidence**

`and \`noop\` set to \`true\` if this tick changed nothing (or \`false\` if it did)`

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

### Opus 5 disabled-thinking advice merged into one instruction

Advice for running Opus 5 without thinking is now a single combined instruction instead of two.

**What**

The guidance for running Opus 5 with thinking turned off previously gave separate fixes for two problems: a tool call written out as plain text that silently never runs, and internal thinking tags leaking into the answer. They are now one combined instruction.

**Details**

- The combined rule allows a preamble sentence, tells the model to say so when no available tool fits the request, and forbids internal XML tags in general rather than naming a specific tag.
- The existing rule against naming thinking tags in the prompt is kept and restated in the checklist.

**Evidence**

`If no tool can express what the user asked for, say so instead of guessing.`

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

### Clearer lower-priority mode messages

Lower-priority mode messages now distinguish starting from resuming, explain the cooldown, and say how to turn it back on.

**What**

Lower-priority mode now tells you different things when it starts fresh versus when it resumes ("Lower-priority mode is back on until your limit resets at ..."), explains the cooldown that follows waiting too long for spare capacity, and the off message now says how to turn it back on.

**Details**

- Availability is unchanged; only the wording and the tracked reset time are new.

**Evidence**

`Lower-priority mode is off. New messages wait for your usage limit as usual`

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

### Cloud sessions warn when the command-line prompt never arrived

If your command-line prompt never reached a cloud session, you now get told to send it again.

**What**

A remote session holds back the prompt you passed on the command line until sync is ready. If that send then fails, you now get a warning saying it "wasn't delivered ... please send it again", and a separate wording when the send could not be confirmed to have reached the cloud session, rather than the prompt disappearing quietly.

**Details**

- A related callback warns once per undelivered response instead of repeating.
- Permission modes seeded onto the session when attaching are reported through telemetry `tengu_remote_permission_mode_adopted`.

**Evidence**

`It wasn't delivered to the cloud session; please send it again.`

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

### Slash commands load on demand instead of at startup

Built-in slash commands load when you invoke them, so startup does less work.

**What**

Each built-in slash command is now a separate chunk fetched when you invoke it, rather than code loaded as part of the main bundle, which moves that work off startup.

**Details**

- The registry entries changed from wrappers around already-loaded module initializers to real dynamic imports, one chunk per command, covering /config, /mcp, /skills, /install-github-app and the rest.
- The list of available command names still comes from the same map's keys, so nothing changes about which commands exist or how they are discovered.
- The conditional entries for background/daemon/stop and workflows are unchanged.

**Evidence**

`"install-github-app": () => import(`

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

### Odd git checkouts no longer block a cloud session upload

Unusual git checkouts fall back to the older upload path instead of blocking your cloud session.

**What**

Cloud sessions could refuse to start on checkouts the new hardened upload path cannot handle. Those layouts now fall back to the previous upload method for that session, with a warning, instead of failing.

**Details**

- Layouts classified as unsupported: a `.git` file instead of a directory, a separate work tree, included config, many includes, an old git version, borrowed objects, reftable, and partial clones on old git.
- Reported as `fallback_eligible` in `tengu_ccr_bundle_upload`.
- The warning also notes the legacy path uploads uncommitted files with credential-like names that the new path holds back.
- When hardening is explicitly required for device sessions, the old hard failure still applies.

**Evidence**

`the new upload path does not support that yet, so the working tree is being uploaded the previous way for this session.`

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

### Repo size for upload is now measured by live objects, not pack files on disk

Repos with lots of dead history are no longer squashed or rejected based on pack file size.

**What**

Repos with large amounts of dead history in their pack files were being downgraded to a squashed snapshot or rejected as too big. The uploader now measures the bytes actually reachable from your branches and picks the bundle size tier from that.

**Details**

- Reads `count-objects -v` first; if packs look far larger than the cap, runs a time-boxed `rev-list --disk-usage --objects --missing=allow-any --all`.
- Skipped for partial clones, detected by sniffing `extensions.partialclone` and `remote.*.promisor` / `partialclonefilter`.
- Runs on the hardened upload path; no flag to set.

**Evidence**

`--missing=allow-any`

- Area: Cloud Sessions
- Names: `--disk-usage`, `--objects`
- Tier: You'll notice
- Useful: 3/5
- Signal: 2/5

### Read tool returns PDF pages as structured entries with per-page errors

One unreadable PDF page no longer costs you the rest of the document.

**What**

Reading a PDF now returns a first page plus a list of page images, each with its own base64 data, media type and error field, so one unreadable page no longer costs you the rest of the document.

**Details**

- A page that failed carries an `error` string explaining why and empty base64; the error is set only when base64 is empty.
- The image bytes are transient and are not persisted on the recorded tool result.
- Counters for `imagesOmitted`, `imagePagesFailed`, `imagePagesFailedOmitted` and `documentsOmitted` are surfaced as notes.

**Evidence**

`Why the page could not be processed as an image; set only when base64 is empty`

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

### Crash reports hold back messages from errors thrown outside Claude Code

Crash reports replace messages from errors thrown entirely outside Claude Code, so your data stays out.

**What**

When every frame of a crash comes from somewhere other than Claude Code's own code (native frames, dependencies, Node or Bun internals) and the error carries no code, the message is replaced with a fixed phrase naming the kind of error, so text that might contain your data is not sent.

**Details**

- Applies always in the error report builder; stack frames, the fingerprint and the tags block (platform, OS release, entry point, model, session kind, renderer mode) are unchanged.

**Evidence**

`thrown outside the Claude Code bundle`

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

### MCP servers managed by your organization are labelled

MCP servers your organization provisioned are now labelled as managed in the list and detail views.

**What**

MCP connections provisioned by your organization are now marked in the UI: the detail view shows a "Managed:" row reading "by your organization", and the list line gets a ` · managed` suffix.

**Details**

- Shown only when the connection goes through the claude.ai proxy transport, is in the `claudeai` scope, and its config carries the enterprise-managed marker.
- Not behind a feature flag; it is purely a condition on the server's own configuration.

**Evidence**

`by your organization`

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

### Cloud-to-local file sync distinguishes Claude's earlier versions from your edits

Syncing down from the cloud now tells your own edits apart from earlier versions Claude wrote.

**What**

When syncing files down from a cloud session, replacement notes now tell apart a local copy that is an earlier version Claude wrote from your own uncommitted edits, and deletions are accounted for in detail.

**Details**

- New counters reported by the sync worker include deletes considered, files moved to trash, files kept because they changed, tombstone mismatches, refused trash operations, deletions withheld remotely, deletions capped, and earlier cloud versions kept.
- The worker also logs when authentication recovers after a run was refused.

**Evidence**

`kept_earlier_cloud_versions`

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

### Rejected WebSocket upgrades now say why, including Cloudflare blocks

Refused WebSocket connections now report the status and whether Cloudflare blocked them.

**What**

When a WebSocket connection is refused before it opens, Claude Code records the HTTP status and whether Cloudflare mitigated the request, logs the Cloudflare ray id, writes a short bracketed note into the running task's output, and closes the socket instead of failing silently.

**Details**

- Triggered by the underlying client's unexpected-response event, so it covers rejections that never became a WebSocket.

**Evidence**

`[callWs] upgrade rejected: status=`

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

### Lower-priority continuation offer is shorter and shows remaining allowance

The lower-priority continuation offer is shorter and shows how much allowance is left.

**What**

The notice and status line offering to continue at lower priority were trimmed, and a line showing how much allowance is left was added.

**Details**

- a telemetry event fires when the offer is held back during a cool-off period, carrying the experiment arm, the reason, whether the client had it enabled, and the config version
- both the event and the offer only apply to sessions in the treatment arm of the low-priority offer experiment

**Evidence**

`"tengu_lowpri_offer_withheld"`

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

### Proxied HTTPS connections verify the original hostname

Connections through an HTTPS proxy now verify the certificate against the host you asked for.

**What**

Connections made through an HTTPS proxy now check the server's certificate against the host you actually asked for, and for non-literal hostnames Claude Code resolves the address itself while still sending the original name for SNI and verification. Two new failure reasons cover a credentials file holding the gateway's TLS pin being a symlink or unreadable.

**Details**

- symlinked credential files are not followed
- applies to all proxied connections, with no setting to opt out

**Evidence**

`"the credentials file that keeps the gateway's TLS pin is a symlink, which is not followed"`

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

### Claude Code's git commands no longer recurse into submodules

Git commands Claude Code runs stay in the outer repo and no longer recurse into submodules.

**What**

Every git command Claude Code runs itself now passes `submodule.recurse=false`, alongside the existing `safe.bareRepository=explicit`, so operations in a repo with submodules stay in the outer repo.

**Evidence**

`submodule.recurse=false`

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

### kubeconfig files treated as sensitive

kubeconfig files now get the same protection as other credential files.

**What**

`kubeconfig.yaml`, `kubeconfig.yml` and `kubeconfig.json` were added to the existing list of credential-bearing filenames, so they get the same protection as the other secrets already on it.

**Evidence**

`kubeconfig.yaml`

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

### Resuming a session restores Artifact watches up to a cap

Resuming re-arms your recorded Artifact watches up to the session limit and names any it couldn't restore.

**What**

When you resume a session, Claude Code now re-arms the Artifact watches it had recorded instead of giving up as soon as one live watch exists. It restores as many as the per-session limit allows and tells you which ones it could not, either because the limit was reached or because the watch could not be proven still valid.

**Details**

- Cap message: "this session can watch at most <N> Artifacts at once. Ask Claude to stop watching one, then publish one of these again, to turn its replies back on."
- The other case asks you to have Claude watch the Artifact again or publish it again.
- No flag gates this.

**Evidence**

`watch_cap`

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

### Document artifacts get a new look: wider text column, black Save button

Document artifacts published from now on get a wider text column, smaller phone text and a black Save button.

**What**

The document skill's page template was restyled. Text now sets at 16px on a 42em measure instead of 17px on 34em, with a new phone step at 480px dropping to 15px. Save becomes a black primary button and Discard a raised surface with a hairline. Documents you publish after this build get the new look; existing ones are edited in place and unchanged.

**Details**

- New colour tokens drive the chrome: `--cds-control-text`, `--cds-fill-primary`, `--cds-text-on-primary` and three shadow steps. The old clay colour is demoted to drawing the callout rule only.
- The toolbar wraps into groups, hides separators under 520px, hides the word count under 800px, truncates a long status readout, animates the style-menu chevron and list, and is no longer text-selectable.
- Reachable by everyone on this build; it is the shipped template.

**Evidence**

`--cds-fill-primary: #191915; /* the black button: Save */`

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

### Pre-warmed cloud sessions accept two named branches instead of rejecting all revisions

Pre-warmed cloud sessions map a pinned revision to a staging or warm branch instead of refusing it.

**What**

A session that pins a specific revision used to be refused outright. The revision is now mapped to a branch, defaulting to `staging` when none is given and `warm` for the warm branch, and anything else is refused with the offending reference echoed back, cut off at 80 characters.

**Evidence**

`session named a revision the standby does not prefetch`

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

### Fetching from your own checkout stops retrying when the remote never responds

Fetching from your own checkout gives up quickly when the remote sends nothing at all.

**What**

When Claude fetches from a bring-your-own-checkout remote and every retry attempt saw no progress and no activity from the remote, it now stops retrying and fails fast so the session can be reassigned, instead of burning the full retry budget.

**Details**

- Only applies where the retry path is enabled for the session.
- The failure is tagged in telemetry with a giveup reason so silent remotes can be distinguished from other fetch failures.

**Evidence**

`giveup: "mute"`

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

### Attaching to a cloud session fails fast on a bad session id

Attaching to a cloud session with an unknown or malformed id is refused immediately with a message.

**What**

Attaching to a remote session used to log a failed lookup and carry on over the websocket anyway. An unknown session id or a malformed one is now refused outright with a message saying so; other lookup errors still fall through to the websocket as before.

**Details**

- The refusal records a `tengu_remote_attach_session_rejected` event carrying the reason.

**Evidence**

`cloud attach refused: the id names no session of this account, or is malformed`

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

### Workload identity token refresh can proceed without the cross-process lock

Workload identity token refresh can now go ahead without the cross-process lock rather than failing.

**What**

The lock that serializes OAuth refreshes for workload identity federation now has two modes. Fail-closed retries acquisition 5 times; fail-open retries 15 and, if it still cannot get the lock, refreshes anyway without cross-process serialization rather than failing the refresh.

**Details**

- The fail-open path logs a debug line explaining that it proceeded unlocked.
- The existing lock acquire and release telemetry now records which mode was in use.
- The mode is chosen per call site, not by a user setting.
- Workload identity client state (the credentials promise, token cache, resolved base URL and failed access tokens) moved from module globals into a resettable object.

**Evidence**

`wif: credentials lock unavailable at `

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

### Window screenshots no longer trigger spurious "take a new screenshot" warnings

Window screenshots no longer produce bogus "take a new screenshot" warnings before clicks.

**What**

Before clicking, Claude normally re-captures a crop and compares it against the last screenshot, warning if the screen changed. Captures that carry the window frame size now skip that check outright, so the warning is never produced for them.

**Details**

- Screenshots from the macOS app-scoped tools can now report the window's frame size separately from the captured image size, and percentage-to-pixel coordinate maths prefers the frame size when present.
- No flag controls this; the check is skipped whenever the reference screenshot has a frame size.
- The frame-size field does not exist in the previous build.

**Evidence**

`Screen content at the target location changed since the last screenshot. Take a new screenshot before clicking.`

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

### Clicks outside every display are refused instead of dispatched

Clicks landing outside every display are refused with a prompt to take a fresh screenshot.

**What**

Before a click or cursor move is hit-tested, its point is checked against the list of displays. A point that lands on no display is refused, with an instruction to take a fresh screenshot or move the mouse first, rather than being sent to the OS.

**Details**

- Refusals use the reason code hit_test_off_display.
- If listing displays fails or reports zero displays, the check is skipped and the previous behaviour applies, logging "[computer-use] off-display backstop skipped".

**Evidence**

`hit_test_off_display`

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

### Uploaded git bundles are checked against your own object store

Uploaded git bundles are verified against your own checkout before being sent.

**What**

After building a bundle, Claude Code now confirms every commit it references really exists in this checkout, so a bundle assembled against some other object store is rejected rather than uploaded.

**Details**

- Bundle heads are piped through `cat-file --batch-check`; a mismatch fails with reason `git_dir_tampered`.
- Companion checks cover refs that were missing, ambiguous, or changed while the bundle was being built, each with its own remediation text.
- Only on the hardened upload path; skipped when the legacy path is in use.

**Evidence**

`at commits this checkout's own object store does not hold: another object store stood in while the bundle was made.`

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

### The bridge heartbeats sooner and while partly busy

The session bridge heartbeats sooner and while busy, so the server is less likely to drop it.

**What**

The bridge now sends a heartbeat immediately after accepting a session, and keeps heartbeating when sessions are running but it still has spare capacity, so the server is less likely to consider it stale.

**Details**

- The one-shot heartbeat reports as `bridge_work_heartbeat`, or `initial_failed` on failure.
- Below full capacity it heartbeats every `non_exclusive_heartbeat_interval_ms` from the server's bridge config, or every 60000 ms once a first heartbeat has been sent and the server-supplied interval is 0.
- Previously heartbeats only ran while at the maximum session count.

**Evidence**

`bridge_work_heartbeat`

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

### GitHub CLI login is detected with `gh auth token`

GitHub CLI sign-in is checked with a quick local command instead of a silent network call.

**What**

Claude Code now checks whether your `gh` is signed in by running `gh auth token` with a 5 second timeout, classifying the result as not installed, authenticated, not authenticated or unknown. An older `gh` without that subcommand reports unknown instead of quietly making a network call.

**Details**

- Parsing `gh auth status` is only used when the caller passes `allowNetworkFallbackForOldGh`.

**Evidence**

`this GitHub CLI version has no `gh auth token``

- Area: Elsewhere
- Names: `gh auth token`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Clearer errors when a page requests an unavailable MCP server

A page asking for an unavailable MCP server now gets one of three specific rejections.

**What**

The single "reserved server name" rejection has been split into three distinct outcomes with separate counts: an internal host server name, an opaque server id, and a malformed manifest.

**Details**

- The error text now states that built-in Claude app servers are never exposed to pages as host servers.
- It lists the connectors seen during the session so you can pick a valid `server` value.

**Evidence**

`internal_host_server_name`

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

### Directory sync explains why a file could not be written locally

Sync tells you why a pulled file could not be written, including case- or Unicode-only clashes.

**What**

Failures while writing pulled files into your working copy are now reported as refusals with a reason instead of thrown as raw errors, and the summary tells apart a genuine name clash from two files differing only by letter case or Unicode form, listing the paths involved.

**Details**

- Recognised reasons cover a missing anchor directory, a destination that refused the write, a file landing outside the sync root, a parent path escape, a parent that is not a directory, and a path that resolves outside the sync root.

**Evidence**

`WORKING_RESOLVES_OUTSIDE`

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

### Interrupt behaviour for held sends is now written down

The control protocol now spells out that interrupts keep held messages queued unless you cancel them.

**What**

The control-protocol schema now explains what happens to a message a client is still holding back: a plain interrupt leaves it held and lists it under `still_queued`, while cancelling queued messages withdraws it and reports it under `cancelled`.

**Details**

- The same message can also be withdrawn on its own with `cancel_async_message`.
- Today the only such hold is a message waiting for the session to take the initial upload from that machine.
- A hosted-session driver follows a cancellation with a `command_lifecycle` frame marked `cancelled` when its own sweep cancels a send it had already delivered.
- The behaviour is advertised to clients through the `interrupt_cancel_queued_v1` capability.

**Evidence**

`behind a send gate (today: waiting for the session to take the initial upload from that machine)`

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

### App risk classification recognises Windows apps

App risk classification now recognises Windows executables and package ids, not just macOS names.

**What**

The classifier that sorts a running application into browser, terminal, trading or shell previously only knew macOS-style display names. It now also matches Windows executable names and package identifiers.

**Details**

- Browsers: Edge, Firefox and Arc package identifiers plus `mullvadbrowser.exe`, `floorp.exe`, `comet.exe`.
- Terminals and editors: Windows Terminal, PowerShell, `wezterm-gui.exe`, `windsurf.exe`, `rider64.exe`.
- Shell surfaces such as `startmenuexperiencehost.exe`.
- A finance bucket covering `thinkorswim.exe`, `ledger live.exe`, `trezor suite.exe`.
- Data only; no flag, and it applies wherever app classification already runs.

**Evidence**

`mullvadbrowser.exe`

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

### Plugin install flags when new tools need a refresh to take effect

Plugin installs now flag "installed but not applied" when language-server tools change too.

**What**

After installing plugins, Claude Code checks whether the change alters the available tools, now including language-server tools as well as added and removed MCP servers. If so, the install message is annotated with "installed but not applied" and the app is marked as needing a refresh.

**Details**

- The bookkeeping that records what was warned about or forced moved into a finally block, so it is always written even if the check fails.

**Evidence**

`installed but not applied`

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

### Remote settings refresh after onboarding now says what happened

The post-onboarding org settings refresh now logs each outcome instead of swallowing errors.

**What**

The refresh of organization settings that runs after onboarding previously fired and swallowed any error. It now logs a distinct line for each outcome, including a warning that org settings, company announcements included, may only apply later in the session, and a note that a consent dialog will open once the session view loads.

**Details**

- Outcomes distinguished: refreshed, consent pending, timed out, failed.

**Evidence**

`Remote settings: refresh still in flight at REPL mount — org settings (company announcements included) may apply later this session`

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

### Working-tree upload refuses Windows short names and Read-rule paths

Uploads refuse Windows short-name paths, and credential refusals can now cite your Read rules.

**What**

Before uploading your working tree to a cloud session, Claude Code now refuses if uncommitted changes include paths shaped like Windows 8.3 short names, since such a name can resolve to a different file than it spells. The credentials-and-keys refusal can now also cite paths covered by a Read rule in your settings.

**Details**

- An oversized starting file set can now be refused outright rather than quietly falling back to uploading only a bundle.

**Evidence**

`which can open a different file than they name`

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

### Half-open cross-session connections are dropped on a deadline

Cross-session connections that never send a full line are closed on a deadline.

**What**

The local socket that carries messages between sessions now closes any connection that fails to send a complete line within a configured first-line deadline, so a stalled or probing peer no longer holds a slot open.

**Details**

- Each connection arms a timer on accept, cleared on close or error; the timer does not keep the process alive.
- The drop is logged, and the first one in a session reports a cross-session inbox auth telemetry value.

**Evidence**

`[uds-messaging] Closing a connection that sent no complete line within `

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

### Wider detection of tampered git directories

More forms of tampered or redirected git state are detected, each with a specific reason.

**What**

Checking a repository now reports a specific reason when its git directory looks tampered with, rather than only catching one narrow case, so more forms of redirected or borrowed git state are caught.

**Details**

- Reasons reported include an old-format git directory, a redirected common directory, reftable storage, borrowed objects, a suspicious git directory and too many include directives.
- Object-store stamps are recorded as part of the check.

**Evidence**

`misplaced: "borrowed_objects"`

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

### Result output documents its pending-user-turn count

The result message documents exactly which queued sends its pending count includes.

**What**

The result message field counting queued sends now explains itself: it counts only your own sends still waiting when the result was produced, queued sends may merge together, entries the system queued are not counted, and the field is missing on results from a fatal startup failure.

**Evidence**

`User-initiated sends still waiting in the command queue when this result was produced.`

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

### GitHub CLI checks tell an old `gh` apart from a broken one

An outdated GitHub CLI no longer looks like a missing or signed-out one.

**What**

Checking whether your GitHub CLI login can be shared now reports two outcomes it previously folded into not-installed or not-authenticated: `gh_too_old`, meaning `gh` is logged in but lacks the auth-token command, and `gh_check_failed`, meaning the check itself errored. Remote and web setup surface each with its own message, so an outdated CLI no longer looks like a missing or signed-out one.

**Details**

- The status check is version-aware and falls back to a network check on older `gh` versions that cannot report their capability directly.
- `gh_too_old` shows "GitHub CLI is logged in, but this version is too old to share its login", names 2.17.0 or newer as the requirement for `gh auth token`, and offers the web sign-in path instead.
- `gh_check_failed` reports that the status check failed and appends the trimmed error text.
- Both outcomes are reported through the existing `tengu_remote_setup_result` event.
- A background probe of the GitHub connection times out after 3 seconds and reports to a new `api_github_connection_status` event, distinguishing connected, not connected and unknown; the foreground path allows 10 seconds.

**Evidence**

`api_github_connection_status`, `GitHub CLI is logged in, but this version is too old to share its login`

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

### Cloud gateway refuses a pinned certificate stored in a symlinked file

A symlinked TLS pin file makes the cloud gateway refuse outright rather than retry.

**What**

If the credentials file holding the cloud gateway's TLS pin is a symlink, the connection is refused outright and not retried. An unreadable pin is now reported as its own separate failure.

**Details**

- Two distinct error kinds: `gateway_pin_refused` for the symlink case, `gateway_pin_unreadable` for a pin that cannot be read.
- The symlink refusal suppresses retry; nothing turns this check off.

**Evidence**

`Cloud gateway TLS pin is in a symlinked credentials file`

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

### Sandboxed sessions are told network traffic goes through a filtering proxy

In a proxied sandbox Claude is told to try requests and read errors rather than guess reachability.

**What**

When a sandbox is configured with an egress proxy, the model is now told to just attempt a request and read the error rather than guess what is reachable, and pointed at the sandbox violations block for details.

**Details**

- Only added when the proxy configuration is present and non-empty; sandboxes without one see the previous preamble.

**Evidence**

`Network egress goes through a filtering proxy.`

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

### macOS permission denial now tells Claude to stop retrying

A refused macOS permission now tells Claude to stop retrying and ask you to grant it.

**What**

When a macOS access request is refused, the message Claude receives no longer just says a permission panel was shown. It instructs Claude not to retry during the same turn and to ask you to grant permissions in the Claude desktop app on the machine it is running on.

**Evidence**

`Do not retry in this turn.`

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

### Remote Control conflict notice explains the cross-machine limit

The Remote Control conflict notice now explains that sessions on other machines are out of reach.

**What**

The notice saying another Claude Code on this machine already holds Remote Control now adds that this terminal cannot see your sessions on other machines and they cannot reach it.

**Details**

- the extra clause appears only when cross-session messaging is enabled for the session

**Evidence**

`so this terminal can't see your sessions on other machines and they can't reach it`

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

### Artifact document paths capped at 15 path segments, down from 31

Artifact collection paths are capped at 15 segments, down from 31, so deeper ones are rejected.

**What**

Every check and description of artifact document paths now allows at most 15 slash-separated segments in a collection path. Collections nested deeper than that, which were previously accepted up to 31, are rejected.

**Details**

- each segment may use letters, digits and `_ - . ~ : @ +`; `.` and `..` stay reserved; the document id is still a single segment
- the note listing saved documents now tells Claude that a `~` in a document id is written as `@` on disk

**Evidence**

`'collection must be a path of 1-15 "/"-separated segments and doc_id one segment (letters, digits, _ - . ~ : @ + per segment; "." and ".." reserved)'`

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

### Computer-use app tools: torrent clients blocked, screenshot scale factor, shared allowlist wording

Torrent clients are now refused for computer use, and allowlist wording is shared across click actions.

**What**

The list of application bundle IDs Claude Code refuses to drive gained torrent clients such as qBittorrent. Screenshots gained a scale factor whose description stresses that coordinates stay in the full-resolution frame, and the sentence about the frontmost application needing to be in the session allowlist is now one shared fragment reused across click, double-click, triple-click, middle-click, key, scroll, drag and move.

**Details**

- app permissioning moved from a flat list of allowed apps to a grants object, queried through `isEmpty()` and `captureAllowedBundleIds`
- which of these tools you see still depends on the computer-use surface being available to your session

**Evidence**

`"org.qbittorrent.qBittorrent"`

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

### Remote Control cannot be turned on from inside a remote session

You cannot turn on Remote Control from inside a remote session; it is refused with a message.

**What**

Enabling Remote Control while you are already connected over a remote transport is now refused with a message saying so, instead of appearing to proceed.

**Evidence**

`"Remote Control cannot be enabled from inside a remote session"`

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

### Directory sync separates timeouts from failures and reports far more outcomes

A sync that runs out of time now reports as timed out rather than failed.

**What**

A sync request that runs out of time now reports `timed_out` rather than `failed`, so a slow link is no longer indistinguishable from a rejected upload. Pull results can also report an outright refusal.

**Details**

- The sync summary gained counters for upload timeouts, slow-but-landed uploads, and a set of deleted-file buckets.
- Stat records now carry device and inode identity, so a file replaced in place can be told apart from one edited.
- A read-only sync scope that still has unsaved paths reports them as `unsavedReadOnlyPaths` and emits the telemetry event `tengu_memory_sync_ro_unsaved`.

**Evidence**

`"tengu_memory_sync_ro_unsaved"`

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

### PDF reads inside REPL results are capped with an explicit note

Too many PDFs in one REPL result get dropped with a note telling Claude to read fewer.

**What**

When Read calls made inside a REPL produce more PDFs than the result can carry, the extras are dropped and replaced by a note that states the cap and tells Claude to read fewer PDFs per call, so the model can correct itself instead of silently losing documents.

**Details**

- Parallel counters were added for omitted images and for image pages that failed to render.

**Evidence**

`"type": "text"`

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

### Computer-use server at 0.2.0, with a clear error when nothing is granted

Calling a computer-use tool before granting any app now returns a clear request-access error.

**What**

The built-in computer-use server now reports version 0.2.0, up from 0.1.3. Calling a tool before any app has been granted returns a specific error telling the model to request access first, instead of failing opaquely.

**Details**

- The accepted action list grew from `request_access` and `list_granted_applications` to also include `request_teach_access` and `list_apps`, both unconditionally.
- Screenshot config gained `adaptiveResolution` and `saveToDisk`, both defaulting to false.
- Click coordinates are computed against the window frame size when the capture supplies one.

**Evidence**

`No applications are granted for this session. Call request_access first.`

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

### Artifact publish recognises removal-only calls and explains duplicate paths

Artifact updates that only remove files now count as a publish and go through.

**What**

A call now counts as a publish when it only removes files or only sets live files, not just when it lists files to upload, so removal-only updates go through.

**Details**

- The duplicate-path error was rewritten from a terse note into an instruction naming the repeated path.
- Publish responses carry a marker for an artifact that is gone and a flag for a 403 caused by the artifact belonging to another organisation.

**Evidence**

`is listed more than once in`

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

### Egress-proxy refusals on artifact fetches are named as such

An artifact fetch blocked by your corporate proxy now says so instead of looking like a service failure.

**What**

An artifact content fetch blocked by a corporate egress proxy now reports its own reason code and an error naming the proxy's CONNECT status, rather than a generic relay failure, so a network policy problem is distinguishable from a service problem.

**Details**

- A separate message explains that asset reads work only locally or from a cloud session with the gateway relay enabled.

**Evidence**

`artifact content fetch refused by the environment's egress proxy`

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

### Clearer wording for feedback failures, permission denials and upgrades

Feedback and permission messages were rewritten, including a denial explaining don't-ask mode blocked a tool.

**What**

Several messages were rewritten into plain sentences, including "Draft too large. Shorten the details and try once more." and "Couldn't send feedback. The draft is still queued. Try again later." A new denial message explains that a tool was blocked because don't-ask mode is on, a line was added about plugins sharing usage, and the Max upgrade link now carries campaign attribution.

**Evidence**

`Draft too large. Shorten the details and try once more.`

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

### Angle brackets rejected in display names and paths

Angle brackets are now rejected in display names and paths alongside other unsafe characters.

**What**

The filter that rejects unsafe characters in labels now also rejects `<` and `>` and their fullwidth equivalents, on top of the control characters, brackets, quotes and backslashes it already caught. A separate URL matcher was tightened to stop absorbing angle brackets and the ellipsis character.

**Evidence**

`\uFF3B\uFF3D\uFF02\uFF1C\uFF1E`

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

### More browsers and torrent clients recognised

More browsers and torrent clients are recognised, and shell surfaces are treated like terminals.

**What**

Detection now knows Zen Browser, LibreWolf, Waterfox, Mullvad Browser, Floorp and Atlas Browser, and a neighbouring classification list gained torrent, Transmission, Deluge, BiglyBT, Tixati and FrostWire. Click and coordinate mapping now treats a `"shell"` surface the same as a terminal.

**Evidence**

`mullvad browser`

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

### Malformed MCP capability declarations on published artifacts explain the fix

Bad MCP capability declarations on a published artifact now come with a diagnosis and a fix.

**What**

Publishing an artifact that declares MCP capabilities incorrectly now gets a specific diagnosis and remedy for each case instead of a generic rejection.

**Details**

- Covers: the declaration not being an object, no servers listed, no tools listed, a connector id used where a server name belongs, a tool name in the server slot, and a name that cannot be declared until it is renamed in claude.ai.

**Evidence**

` is a connector id, which no viewer can resolve`

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

### Bundling for a cloud session reports whether your Read rules were applied

Before packaging your checkout, you are told if broken settings mean only committed files go up.

**What**

Before packaging your checkout for a cloud session, Claude Code reads the rules that withhold files from upload and records how many applied versus how many could not be read. If a settings file has errors you are told that uncommitted contents stay local and the cloud session will start from what is committed.

**Details**

- Reported under `tengu_teleport_bundle_read_rules`.
- If the repository layout is not supported by the newer packaging path, an older capture method retries the upload.

**Evidence**

`tengu_teleport_bundle_read_rules`

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

### OpenAI Atlas detected as a browser

OpenAI Atlas is now recognised as a browser on macOS.

**What**

Browser detection on macOS now recognises OpenAI Atlas, joining Perplexity Comet and Arc's Dia.

**Details**

- Added to the macOS bundle-identifier list; other platforms use a different detection path and are unaffected.

**Evidence**

`com.openai.atlas`

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

### Artifact comment monitors say exactly who holds them

If resuming can't take back Artifact comment replies, you now get a specific reason and advice instead of one vague message.

**What**

When you resume a session and it cannot take back automatic replies to comments on an Artifact, the single old "held by a background session" state is replaced by three specific reasons, each with concrete advice.

**Details**

- `held_by_live_session`: "this conversation is also open in …", followed by advice to close those sessions or stop the agent.
- `held_by_job`: "they were last turned on from a background agent of this conversation, which keeps them while it runs (see `claude agents`)".
- `other_org`: "this Artifact is in another of your organizations. Run /login and sign in to that organization".
- Telemetry on `artifact_live_subscribe` reports matching reasons `resume_held_live`, `resume_held_job` and `resume_holder_unknown` plus holder counts.
- Reachable by anyone resuming a session that had a live Artifact; no flag.

**Evidence**

`held_by_live_session`

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

### Subscription failures now say what the server said

Failed file subscriptions now include the server's own explanation alongside the reason code.

**What**

When Claude cannot subscribe to or watch a file for updates, the message it sees now includes the server's own explanation and detail alongside the reason code, and the "no subscription" note prints the reason in parentheses next to the URL. Publishing and reading still work in that state.

**Evidence**

`could not register one; publishing and reading still work.`

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

### Artifact path depth limit halved

Artifact names can now hold at most 14 slash-separated path segments, down from 30.

**What**

Slash-separated artifact names may now contain at most 14 extra path segments, down from 30. Deeper names are rejected as invalid.

**Evidence**

`{0,14}`

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

### Plugin eval docs: scores from untrusted suites are advisory

Eval docs now warn that scores from untrusted suites granted Bash should be treated as advisory.

**What**

The eval sandbox documentation now warns that the harness judges a run from the child process's output and files, which a shell-granted process running as you can also reach. Scores from an untrusted suite run with `--allow-tools Bash`, or any other grant that can execute code, should be treated as advisory unless the run had operating-system-level isolation.

**Details**

- Text only; the sandbox itself behaves as before.

**Evidence**

`treat scores from an untrusted suite run`

- Area: Elsewhere
- Names: `--allow-tools Bash`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Corrupt prompt-history lines are skipped rather than breaking history

One corrupt line in your prompt history no longer breaks history loading.

**What**

Each saved prompt-history line is checked against a schema; bad lines are dropped with a log entry naming the field and problem instead of derailing history loading.

**Details**

- Logs "Skipping malformed history line" and classifies each failure as a shape rejection or a parse failure.
- Pasted-content attachments that had to be dropped from an otherwise valid line are counted.

**Evidence**

`Skipping malformed history line`

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

### Plugin sync records each plugin's id in a sidecar file

Plugin sync writes a small sidecar file recording each plugin's id.

**What**

Syncing plugins writes a small file next to the synced directory recording the plugin's id, skipping the write when it already matches. If something other than a file sits at that path, a directory there is moved to trash first.

**Details**

- New telemetry for an invalid id, a failed write and a failed trash move.
- The synced-plugin record now also stores the plugin id, the installed version and the requested version.
- Runs only after the existing guard verification passes.

**Evidence**

`plugins_sync_sidecar_write_failed`

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

### Scheduled task runs carry a task and a per-run identifier

Each scheduled task run now carries its own id, so a run can be traced back to its schedule.

**What**

Each scheduled-task firing now passes the task and a fresh per-run id through to the query it starts, so a run can be tied back to the schedule that triggered it.

**Details**

- The `/loop` branch no longer consults the separate loop-enabled check before rewriting the queue, and the "Claude resuming /loop wakeup" message variant is gone from this path; the scheduled-task variant remains.

**Evidence**

`Running scheduled task (`

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

### Voice capture loads its native module only when used

Voice input's native module loads only when you use it, so startup is lighter.

**What**

The audio capture native module is now loaded on first use instead of at startup, so runs that never use voice input do not pay for it. Load time is logged.

**Details**

- The load is cached after the first request and checks native audio availability before use.

**Evidence**

`[voice] audio-capture-napi loaded in `

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

### Update manifest fetches retry when the connection drops

Update checks retry when the connection drops instead of failing the whole check.

**What**

The native updater's manifest request, which uses a 10 second timeout and expects JSON, now retries on a dropped connection up to the shared retry limit with a 1 second pause between attempts, instead of failing the update check outright.

**Details**

- The outcome reports whether a manifest retry happened, alongside the existing flag for the download's own drop-retry.
- Unconditional in the native updater; no setting involved.

**Evidence**

`Manifest fetch connection dropped on attempt `

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

### Auto-update checks are throttled within a long-running process

Long-running sessions stop making redundant auto-update checks; there is no flag for it.

**What**

An auto-update check is now skipped if one ran recently, so a long-lived session stops making redundant checks. The skip is logged with how many seconds ago the last check ran. There is no flag.

**Details**

- Updater state (time of the last auto-update check, an update-restore failure marker, and minted preserved inodes) moved from module-level globals into one per-process object.
- The self-heal paths that restore a missing `claude` binary from a preserved `<exe>.old.<timestamp>` copy now receive that state explicitly instead of reading globals.
- The npm install and uninstall shell-outs now resolve the home directory through a helper rather than calling `os.homedir()` directly.

**Evidence**

`auto-update check throttled (last check `

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

### Bundled skill prompts, templates and docs converted to plain ASCII

Bundled skills and docs are now pure ASCII, so old searches for special characters find nothing.

**What**

Every character outside ASCII was stripped from the skills that ship with Claude Code, covering both the artifact skills (design, diagramming, dashboard, data-table, explainer, report) with their HTML templates and the whole Claude API skill documentation set. Wording and guidance are unchanged, so generated artifacts and skill behaviour should look the same, but any script or search that matched the old characters in these files will now come up empty.

**Details**

- Em dashes become `-`, ellipses become `...`, arrows become `->`, and the greater-or-equal and less-or-equal signs become `>=` and `<=`.
- Curly quotes and triangle glyphs are gone from the artifact skill prose and templates.
- HTML in the artifact templates uses named entities instead of literal characters, and non-ASCII inside embedded JSON or JavaScript is written as an escape sequence so it still parses back to the original character.
- Emoji callouts in the API skill documentation are replaced by the plain words `Warning:`, `Tip:` and `Note:`.
- The API skill pass covers the skill index, the per-language guides, the shared reference documents and the managed-agents pages.

**Evidence**

`<input class="filter" id="dt-filter" type="search" placeholder="Filter rows&hellip;" autocomplete="off">`, `Warning: **There is no inline agent config.**`

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

### Bridge shutdown tells the server to stop work earlier

On shutdown the bridge asks the server to stop sessions before force-killing them.

**What**

On shutdown the bridge now asks the server to stop each tracked session before waiting for those sessions to exit, rather than after force-killing them, and awaits the results at the end.

**Details**

- The stop calls carry the current environment secret, including one obtained by re-registration.
- Failures are labelled for telemetry under `bridge_work_stop`: `env_gone` when the resource is already gone, `shutdown_403` for a 403, `shutdown_failed` otherwise.

**Evidence**

`shutdown_403`

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

### Chrome connector repairs its own launcher permissions

The Chrome launcher script is quoted properly and gets its executable permission repaired.

**What**

The small script Chrome uses to launch Claude is now written from a proper argument list, quoted for the platform (`%` doubled on Windows), and when the script already exists with the right contents its executable permission is reset to 0755 instead of being left broken.

**Details**

- If the permission repair fails, it logs "[Claude in Chrome] Could not repair wrapper exec bit" rather than silently handing back a script that will not run.

**Evidence**

`[Claude in Chrome] Could not repair wrapper exec bit: `

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

### Plugin downloads write straight into the cache

Plugin downloads stream straight into the cache and are hashed on the way in.

**What**

Files a marketplace plugin pulls down are now streamed directly into the cache and hashed on the way in, instead of first landing in a temporary staging directory. The expected checksum is still verified at the end, and a download that is too large fails rather than filling the cache.

**Details**

- The write refuses to overwrite an existing entry and uses file mode 384 (owner read/write only) with a size cap.
- If two sessions fetch the same asset at once, the loser re-checks the digest of what is already there and treats it as a cache hit.
- Storage failures are reported as `asset_transient` or `asset_too_large`.

**Evidence**

`asset-cache`

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

### A malformed pasted item no longer discards a history line

A single bad pasted-content record no longer throws away the whole history entry.

**What**

Command history lines are now validated against a schema, and a single bad pasted-content record is dropped instead of throwing away the whole history entry.

**Details**

- Validated fields: display text, pasted contents, timestamp, project, and an optional session ID.
- Dropping a bad paste logs "Dropping a malformed paste record from a history line".
- Load reporting now separates `history_load_parse_failed`, `history_load_shape_rejected` and `history_load_paste_record_dropped`, each with its own boolean field, alongside the existing `history_load_read_interrupted`.

**Evidence**

`history_load_shape_rejected`

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

### Artifact operations say when they did nothing

Artifact upload, read, delete and edit now say when there was nothing to do.

**What**

Upload, read, delete and edit paths for artifacts previously returned silently when there was nothing to act on, and now return an explicit result: "Nothing was uploaded", "Nothing was read", "Nothing was deleted" or "Nothing was read or changed".

**Details**

- Live-file publishing attaches a `liveFilesOn` telemetry flag, but only when the accompanying `liveFilesGate` value is true.
- File entries in this path changed from a single value to a pair holding the original path and the redirected one.

**Evidence**

`Nothing was read or changed`

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

### Plugin command failures name the command that failed

Each plugin command now has its own failure message instead of one generic line.

**What**

Install, uninstall, enable, disable, disable all, update and prune each now have their own failure message when the cause cannot be classified, replacing one generic line. Agent definitions with a name starting with a hyphen are now rejected.

**Evidence**

`claude plugin disable --all failed with an unclassified error`

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

### Menus can drop the number column, and the vim indicator no longer crowds the hint

Menus can hide the number beside each option, and vim mode no longer crowds the input hint line.

**What**

Selection menus can now be rendered without the numeric shortcut beside each option, and the input footer suppresses its dense hint text when the vim mode indicator is showing, so the two no longer fight for the same line.

**Details**

- The menu behaviour is chosen per menu, not by a setting or flag.

**Evidence**

`hideIndexes: ea`

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

### Handed-off subscriptions now say "yielded" instead of "interrupted"

Handing an Artifact subscription to another session now reads as "yielded" instead of looking like a failure.

**What**

When a live Artifact subscription is handed to another session, its stopped watch is now reported as "yielded" rather than "interrupt", "swept" or "killed", so a deliberate hand-off no longer reads as a failure.

**Details**

- The hand-off path also emits counters for each way it can go wrong: malformed frame, unvettable target, unverified requester, table full, handler threw, stale request, ambiguous answer send, undelivered answer, and two pid-mismatch cases, plus a count of successful hand-backs.

**Evidence**

`yield_handed_back`

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

### artifact-pr-review skill text uses hyphens instead of em dashes

The bundled PR review skill uses plain hyphens now; nothing about its behaviour changed.

**What**

Both variants of the bundled PR review skill now spell em dashes as plain hyphens, including in the description the model matches against when picking the skill. No procedural content changed.

**Evidence**

`Create a PR review artifact - a structured review briefing for a GitHub pull request`

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

### Feedback dialog drops the "Drafted by Claude" line

The feedback dialog dropped its drafted-by-Claude line and has a shorter success message.

**What**

The text under the feedback form now starts at "We may use these reports to debug related issues and improve Claude Code." and keeps the sentence pointing at `/config` to opt out. The success message is now "Feedback sent (receipt …). Thanks!".

**Evidence**

`We may use these reports to debug related issues and improve Claude Code. Turn off Claude-drafted feedback anytime in /config.`

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

### Em dashes removed from user-facing lines

Several visible messages were reworded to drop em dashes, and more quote characters are stripped.

**What**

A copy pass rewrote several visible strings to use parentheses or sentence breaks instead of em dashes. The transcript badge now reads "transcript expired (report only)", and the feedback confirmation reads "Feedback sent (receipt ...)". The drafted-feedback off notice, the feedback preview footer and the blank-title warning were reworded the same way. A new sanitiser also strips a set of angle-quote and guillemet characters.

**Evidence**

`"Claude-drafted feedback is off. Turn back on in /config"`, `transcript expired (report only)`

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

### Identifier validators capped and invisible-character stripping widened

Identifier names are capped at 64 characters and more invisible characters are stripped.

**What**

Two identifier checks that accepted names of any length now cap them at 64 characters, the line-start matcher recognises the U+001C to U+001E separator characters, and the routine that strips invisible characters was replaced with a broader Unicode class match.

**Evidence**

`/^[A-Z][a-zA-Z]{0,63}$/`

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

### Skill prose rewritten with ASCII punctuation

Several skill texts swapped fancy punctuation for plain ASCII; nothing behaves differently for you.

**What**

The design-sync, doc, whiteboard and workshop skill bodies now use ASCII punctuation in the text fed to the model: em dashes become `-`, arrows become `->`, `<=` replaces the math symbol, `...` replaces the ellipsis, and the doc skill's save hint says `Ctrl/Cmd+S` instead of the command glyph.

**Details**

- The count of escaped em dashes across the bundle falls from 9,495 to 7,326, and most survivors sit in the doc template's CSS comments, so this is a pass over skill markdown rather than a bundle-wide sweep.
- No instruction changes meaning.

**Evidence**

`the toolbar shows unsaved changes until they click **Save** (or press Ctrl/Cmd+S)`

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

## Bug Fixes

### Turn duration is now printed when background swarm tasks finish

You now see the turn's elapsed time once the last background swarm task finishes.

**What**

If a turn ended while swarm tasks were still running, the elapsed-time line was recorded but never shown. It is now emitted once the last task stops running, with the deferred elapsed time and budget info.

**Details**

- The controller watches the task store and unsubscribes after appending the message.
- Affects any turn that ends with tasks still running.

**Evidence**

`hasDeferredSwarmDuration`

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

### Hooks running for another machine's session no longer point at a missing transcript

Hooks for a session running on another machine are told there is no local transcript.

**What**

A hook invoked on behalf of a session running elsewhere is now told there is no local conversation transcript to read, instead of being handed a transcript path that does not exist on that machine.

**Evidence**

`This call is being served for another machine's session; there is no local conversation transcript to read.`

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

### Tool calls with a late host argument are rejected instead of run

Local tool calls that get a host argument too late now fail instead of running.

**What**

Both the streaming and the direct tool-execution paths now check, before running a locally-executed tool, whether it was handed a host argument that arrived too late, and fail the call with a dedicated error rather than executing it.

**Evidence**

`late host argument on a local tool call`

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

### Artifact read-back failures no longer feed server payloads to the model

Failed artifact read-backs now give a short error instead of dumping server output into context.

**What**

When reading an artifact back fails or returns malformed data, the returned error is now just `read-back HTTP <status>` or `malformed read-back body`. The full response body and parser message go to the debug log instead of into the model's context.

**Evidence**

`malformed read-back body`

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

### File-history restore rejects invalid session ids

File-history restore now rejects bad session ids instead of copying backups anywhere.

**What**

Restoring from file history now validates the target session id before copying backups. An invalid id logs a warning and aborts the copy, so a crafted id cannot steer backup files into a directory of someone else's choosing.

**Evidence**

`"FileHistory: refusing to copy backups on restore (invalid target session id)"`

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

### Automated bridge replies no longer raise notices

Replies to requests Claude Code sent on its own no longer pop up as notices.

**What**

Incoming bridge events that answer a request Claude Code sent automatically are now dropped quietly with a debug log rather than surfacing a notice. Outbound request ids are recorded with a flag marking them as automated, and the drop is counted under its own telemetry name ending in `_automated_reply`.

**Evidence**

`"bridge_event_attestation"`

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

### Bash background runs honour the caller's constraint, stderr matching normalised

Valid foreground bash calls are no longer wrongly rejected, and stderr matching is tidier.

**What**

Input validation now checks whether the calling request permits or requires a background run before objecting to a foreground one, so a valid foreground call is no longer rejected. Standard error is trimmed and stripped before being matched against patterns, and tool results can carry appended trailing text.

**Evidence**

`"tool_host_result_lines"`

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

### A dirty submodule no longer makes a directory look changed

A modified submodule no longer makes your working tree look dirty for directory sync.

**What**

The two git checks used to decide whether a working tree has uncommitted changes for directory sync now ignore submodules entirely, so an out-of-date or modified submodule no longer marks the tree dirty.

**Details**

- `--ignore-submodules=all` is added unconditionally to both the quiet `diff` check and the `diff-files --name-only -z` listing.

**Evidence**

`--ignore-submodules=all`

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

### Stricter file path check on workflow events

Workflow event file paths must now point at a single item directly inside the store root.

**What**

A file path supplied with a workflow event must now name a single object directly under the store root, rather than merely start with the root path and avoid `..`. The error message says so.

**Evidence**

`filestore_path must name one object directly under `

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

### Bridge worktrees now use the same storage backend as the session

Remote session worktrees now store their state in the same place as the session itself.

**What**

Worktree creation and cleanup in remote sessions were made without the session's storage-format setting, so the two could disagree about where state lived. All four call sites now pass it.

**Details**

- Covers creation, normal cleanup, forced cleanup after a failed spawn, and cleanup at shutdown.

**Evidence**

`storageV5: e.storageV5`

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

### Malformed diagnostics in a replayed attachment are dropped instead of trusted

Resumed sessions discard malformed editor diagnostics instead of passing them along.

**What**

When a resumed session replays editor diagnostics, a payload that is not an array, or entries missing a string message or a numeric range start, are now discarded rather than passed through.

**Details**

- The counts are logged, in the form "Dropped N malformed file(s) and M malformed diagnostic(s)".

**Evidence**

`Dropped a `

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

### Proxy denial details no longer reach the model

When a proxy blocks a fetch, only the connect status reaches the model; details go to debug logs.

**What**

When a network egress proxy blocks an artifact content or asset fetch, the proxy's marker and status detail now go to the debug log, and the message returned to the model keeps only the connect status.

**Details**

- Both artifact fetch paths also gained a branch reporting that the relay is unavailable.

**Evidence**

`[artifact] asset fetch: egress proxy denied`

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

### Timestamps lowercase AM/PM again

Timestamps show lowercase am/pm again on newer systems.

**What**

Recent system libraries emit a narrow no-break space (U+202F) before AM/PM rather than an ordinary space, which left some timestamps showing uppercase "AM"/"PM". Three formatters now accept either character, and one of them also gained the case-insensitive matching it was missing.

**Evidence**

`/[ \u202f]([AP]M)/i`

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

## In Development

### Cloud sessions can be seeded from a bundle of your local commits

A cloud session can start from your local commits instead of always pulling the remote copy.

**What**

When your checkout agrees with the pinned remote commit, a cloud session can now be started from a git bundle of your local HEAD instead of refusing and falling back to the remote's copy. What decides whether this is offered at all is set outside the code paths visible here, and it degrades cleanly to the old seeding when unavailable.

**Details**

- The seed carries the pinned commit, your local head commit, the agreed tree and a digest of the bundle.
- The planner is loaded on demand; if that load fails the plan is reported unavailable and normal seeding proceeds.
- New refusal reasons include pull not supported, the head having moved during planning, and the file inventory being refused.
- Tracked files are written through a temporary file created exclusively, with the parent directory re-verified as being under the sync root.
- Emits `tengu_ccr_overlay_bundle`.

**Evidence**

`tengu_ccr_overlay_bundle`, `planOverlay`

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

### Only a fixed list of settings is allowed to travel to a cloud session

Only a short allowlist of your settings is sent to a cloud session; the rest stays local.

**What**

When local settings are forwarded to a cloud session, everything outside an allowlist is withheld: language, outputStyle, attribution, includeCoAuthoredBy, includeGitInstructions, alwaysThinkingEnabled, showThinkingSummaries and permissions are the only keys that travel, with attribution narrowed further to commit, pr, sessionUrl and commitTrailers.

**Details**

- Any other key makes the bundle refused with the reason not_portable_key; exceeding the key-count or key-length caps gives over_bounds.
- The receiving side counts what was left out and reports it as withheldKeys / settingsKeysWithheld.
- Other send-side refusals: settings_unreadable, nothing_to_send, schema_rejected and settings_too_large.

**Evidence**

`includeGitInstructions`

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

### Permission rules are screened before they leave the machine

Permission rules that reach outside the project or only fit your device are not forwarded to the cloud.

**What**

Each permission rule forwarded to a cloud session is classified first. Rules that reach outside the project are dropped as rooted, rules that only make sense on your own device are dropped as device, malformed rules as invalid, and rules past the count or length caps as over_cap. The rest are kept.

**Details**

- Screening walks through wrapper commands (sudo, doas, env, timeout, xargs and others) to find the real command underneath.
- It knows a large table of network-capable commands including curl, wget, ssh, psql, mongosh and invoke-webrequest.
- It also flags arguments that bind a server to any address, such as --host, --bind or HOST= with values like 0.0.0.0, :: or localhost.
- Counts of what was dropped are reported as droppedRooted, droppedDevice, droppedInvalid, droppedOverCap and droppedGuarded.

**Evidence**

`keep_covers_device_tools`

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

### Rebuilt cloud environments get back deny and ask rules only

If a cloud environment is rebuilt, only your deny and ask rules come back, not allows.

**What**

If a cloud environment is recreated mid-session, a recovery path re-fetches the forwarded settings and merges only the deny and ask permission rules back into the local settings file. CLAUDE.md, preferences and allow rules are deliberately not restored.

**Details**

- On success the user sees a notice beginning "Restored " together with the caveat that the other content is not restored in this environment.
- On failure a warning names one of ten reasons: ready_unreadable, rules_unverifiable, settings_refused, auth_failed, pack_absent, pack_unreadable, generation_mismatch, request_failed, settings_unusable, nothing_applied.
- If the forwarding feature is off, recovery stops with the outcome gate_closed or lane_unavailable.

**Evidence**

`the deny and ask rules your machine sent are not restored here`

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

### Workflow tasks can relay the triggering request in a labelled frame

Workflow runs now label whether a person or a schedule triggered them, and quote the request.

**What**

A workflow run now classifies what set it off. A scheduled or external trigger gets an "automated trigger" banner telling the model it cannot claim user approval, while a run a person decided relays that person's request verbatim and indented, with optional preceding assistant context, and control characters and backticks stripped.

**Details**

- `CLAUDE_CODE_WORKFLOW_PROMPT_PROVENANCE` forces the behaviour; otherwise it depends on the `tengu_bubbly_harbor` check, which is treated as off if the check fails.

**Evidence**

`[Workflow harness \u2014 automated trigger]`

- Area: Workflows
- Names: `CLAUDE_CODE_WORKFLOW_PROMPT_PROVENANCE`
- Tier: You'll notice
- Useful: 3/5
- Signal: 3/5

### Chrome navigation classifier for auto mode moved to remote config

An env var for the Chrome navigation classifier is gone; a server setting now controls it.

**What**

The env variable `CLAUDE_PREVIEW_CLASSIFIER_FLOOR` is gone. The classifier that decides Chrome navigation in auto mode is now switched by `chromeNavigationClassifierEnabled`, read from a server-supplied auto-mode config and requiring an exact boolean true.

**Details**

- The config object falls back to empty, so with no server value the classifier stays off.
- Nothing in this build sets the key locally; it is decided by server configuration.

**Evidence**

`chromeNavigationClassifierEnabled`

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

### Rate-limit banner can show a projected reset time for one experiment arm

Some accounts now see an estimated reset time on the rate-limit banner; others see none.

**What**

When you are rate limited and no known reset time is available, the banner computes a projected time, but only for accounts whose low-priority-offer experiment arm is set to "treatment". Everyone in the control arm sees the previous behaviour, with no time shown.

**Details**

- The projected value is used only when neither an active rate-limit phase nor a known reset timestamp supplies one.
- The arm value arrives from server-side configuration, so nothing in this build decides who is in it.

**Evidence**

`lowPriorityOffer`

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

### Cloud sessions can be sent hook templates before registration

Cloud sessions can be handed a hook script up front, with a checksum so stale uploads get refused.

**What**

The protocol between your machine and a cloud session gained an upload step that sends a hook script body (base64, at most 360,000 characters) with a SHA-256 the cloud side checks against its own table, optionally tagged with the worker generation so a stale upload is refused.

**Details**

- Registration replies now also carry which hook ids were ignored, which templates are held, and an "away since" timestamp set when this machine had been treated as absent after two forwarded hook runs went unanswered.
- Documented refusals: hook forwarding disabled, hook forwarding not ready, stale worker generation, invalid upload, template refused.

**Evidence**

`@internal Success payload answering upload_device_hook_template.`

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

### Two hook templates are allowed to run in the cloud by default

Two named hook templates default to running inside the cloud session rather than on your machine.

**What**

Instead of calling back to your machine, a matching hook can be uploaded and run inside the cloud session. When the caller names no list, the ids permitted to do that default to `gh-api-readonly` and `ruff-autofix`; anything else falls back to running on your machine with a notice.

**Details**

- A digest mismatch, a legacy copy, a matcher that does not match, or two consecutive "awaiting upload" results each demote a template back to local execution.
- Behind the same device-hooks gate that is off in this build.

**Evidence**

`is not run in the cloud in this version; it runs on this machine instead.`

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

### Hosts must declare which dialogs they can show

Embedding programs declare which dialogs they can show; unsupported ones are left to other clients.

**What**

A program embedding the cloud headless client lists the dialog kinds it can display in its `initialize` message, under `supportedDialogKinds`. Requests for an undeclared kind are not forwarded; the client prints a notice and leaves the dialog to another attached client or to the session's dialog timeout.

**Details**

- Permission requests are always forwarded regardless of what the host declared.
- Skipped dialogs are counted as `dialogs_not_declared` at end of run.
- The field is added to the honoured initialize options unconditionally.

**Evidence**

`its initialize did not declare the kind`

- Area: Cloud Sessions
- Names: `supportedDialogKinds`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5

### Each per-app tool now needs a specific permission tier

Per-app tools each require a permission tier, so a low grant lets Claude look but not act.

**What**

The existing read, click and full permission tiers are mapped onto the individual per-app tools, so a low-tier grant permits looking but not acting. Calls below the required tier come back as tier_insufficient with guidance to request a higher tier.

**Details**

- Read tier: app_list_windows, app_screenshot, app_ax_find, app_batch, app_bring_to_current_space.
- Click tier: app_click, app_scroll.
- Full tier: app_drag, app_type, app_key, app_menu.
- app_menu additionally refuses Services submenu items and Apple-menu system actions; app_key refuses key combinations that would touch the system clipboard.

**Evidence**

`tier_insufficient`

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

### Plugin-registered tools are served over a local MCP server

Tools a plugin registers reach the model through a local loopback MCP server.

**What**

Tools a plugin registers are exposed by starting an HTTP server bound to 127.0.0.1 on a random path, which is then registered as an MCP server scoped to that plugin. This is how plugin tools reach the model.

**Details**

- Calling a tool that no handler answered returns an error result telling the plugin author to add a tool.call handler for the tool's full name, in the form mcp__<plugin>__<tool>.
- The port is chosen at runtime and logged.
- Behind the tengu_plugin_hooks_modules rollout flag.

**Evidence**

`loopback MCP server listening on 127.0.0.1:`

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

### Plugin model calls are capped by an allowlist and a session budget

Plugin-triggered model calls are limited to allowed models and a per-session token budget.

**What**

When a plugin asks Claude Code to run a model completion, the request is checked against the organization's allowed models and a per-session token budget, so a misbehaving plugin cannot spend a session's capacity.

**Details**

- The requested maximum token count must be a whole number within a cap.
- An estimated cost is reserved before the request and reconciled against actual usage afterwards.
- Passing a fraction of the budget produces a warning log; passing the budget makes the call throw.
- Behind the tengu_plugin_hooks_modules rollout flag.

**Evidence**

`$.model.complete: the session's model budget for this plugin is spent`

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

### Plugin hook modules run in an isolated sandbox with time and size budgets

Plugin hook modules run in a locked-down sandbox with time and size limits.

**What**

When the plugin hook-module feature is enabled, each module runs in its own JavaScript context that cannot generate code, cannot import outside its own plugin folder, and is stopped if it overruns.

**Details**

- Console output is routed into the hook chain report; setTimeout and setInterval are shimmed and cancelled when the plugin unloads.
- Imports are limited to relative paths inside the plugin directory plus one synthetic types module; TypeScript and TSX sources are transpiled with Bun.
- Handlers get a budget that can be paused and resumed, and an abort grace period after which the hook is reported as a runaway.
- Matcher copying is capped by node count and nesting depth; values passed across the boundary are deep-frozen or cloned.
- Same off-by-default gate as the capability object; nothing here can be switched separately.

**Evidence**

`core table: not an operation`

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

### App locks block whole-screen actions, and a remote switch-off releases them

While Claude controls one app, whole-screen actions are refused until it releases the app.

**What**

While the new per-app control holds a lock on an application, whole-screen computer-use actions are refused with a message telling the model to release the app first. Whether any of this is available is decided by the host program at runtime, not by a setting visible in the build.

**Details**

- The tool router branches on the app-scoped tools, including app_release and the new request_full_control and release_full_control.
- If the host provides app-scoped control but its enabled check returns false, any app-scoped call other than release drops the app lock, clears the stored screenshot and returns a feature_disabled error.
- Sessions holding a lock report it in text beginning "This session is currently controlling ".

**Evidence**

`This session is currently controlling `

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

### Subagent results can carry a provenance frame that skips the hand-off review

Subagent results can be tagged to show which parts Claude Code wrote versus the model.

**What**

Results handed back from a subagent can be wrapped in bookkeeping that marks which blocks Claude Code itself wrote versus what the model produced, so a hook rewriting the text can be told apart. When that framing covers the whole delivery, the classifier review of the hand-off is skipped.

**Details**

- Framing is keyed off `tengu_melodic_wolf`, which falls back to off in this build.
- Skipping the review requires `classifyHandoff` in `tengu_auto_mode_config` to be false, and is logged as `skipped_framed_handback` or `skipped_enveloped_handback` under `tengu_auto_mode_decision`.

**Evidence**

`skipped_framed_handback`

- Flag `tengu_melodic_wolf`: Not enough to say (read for one account on one subscription tier against v2.1.242; this account: no value returned, anonymous baseline: no value returned, compiled default: on)
- Flag `tengu_auto_mode_config`: Not enough to say (read for one account on one subscription tier against v2.1.242; this account: no value returned, anonymous baseline: no value returned, compiled default: not a boolean we can read)
- Area: Subagents
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Cloud sessions can report git worktree state on handoff

Handing a session to the cloud can include a snapshot of your checkout, off by default.

**What**

When handing a session to the cloud, Claude Code can now collect and send a snapshot of your checkout alongside the current branch list. It is off unless the `tengu_ccr_handoff_metadata` flag is enabled for you, and it only runs when the repo matches the remote the session is bound to.

**Details**

- The snapshot covers head commit, unpushed commit count, dirty working tree, whether a git operation is mid-flight, whether an upstream exists, and whether the repo uses submodules or LFS.
- Logged locally as `[remote-bridge] worktree_state →`.
- Fallback when the flag is absent is false, so nothing is collected.

**Evidence**

`tengu_ccr_handoff_metadata`

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

### Console profile sign-in validates the token before saving it

The profile sign-in checks a token before saving it and names each failure separately.

**What**

The same new Console profile flow checks the result of a sign-in before storing anything, and explains each failure separately rather than failing generically.

**Details**

- Rejects a sign-in with no inference access ("The organization didn't grant inference access to this sign-in, so Claude Code can't use it."), with no refresh token, or with a non-finite expiry.
- Replacing an existing claude.ai login clears that record.
- A credential file that is a symlink is refused with its own explanation.

**Evidence**

`The organization didn't grant inference access to this sign-in, so Claude Code can't use it.`

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

### Cloud-supplied hook templates run as vetted python3 scripts

Cloud-supplied hook scripts run through a vetted python3 wrapper that rechecks their checksum.

**What**

A new runner executes hook scripts supplied by the cloud session. It picks the first absolute interpreter it can vet from a fixed list, writes the script to a per-process directory, and runs it through a wrapper that re-reads the file, enforces a size cap and re-checks its SHA-256 before executing. A mismatch exits with code 86 and is reported as an integrity failure.

**Details**

- The spawn clears environment variables matching `LD_`, `DYLD_`, `GCONV_PATH`, `PYTHON` and sets only `CLAUDE_PROJECT_DIR`. It runs detached in its own process group, which is killed on timeout or abort.
- Registration and upload require two account flags together, `tengu_violin_wood` and `tengu_violin_amati`. If neither has been served yet the request answers "hook_forwarding_not_ready: feature flags not yet available; retry".
- Policy settings that restrict hooks to plugins, disable customization, allow only managed hooks, or disable hooks entirely clear the registry first.
- None of these strings exist in 2.1.241.

**Evidence**

`device hook template integrity check failed: `

- Flag `tengu_violin_wood`: Off in both readings (read for one account on one subscription tier against v2.1.242; this account: off, anonymous baseline: off, compiled default: on)
- Flag `tengu_violin_amati`: Off in both readings (read for one account on one subscription tier against v2.1.242; this account: off, anonymous baseline: off, compiled default: not a boolean we can read)
- Area: Hooks
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Artifact live-files wording and a distinct gateway refusal

Artifact wording swaps for live-files variants, and gateway reads now say when they are not enabled.

**What**

Artifact result suffixes, the explanation of the `path` argument and publish-change detection now have alternate live-files wordings picked at runtime, and reads through the session gateway can fail with an explicit "not enabled for this session" message rather than a generic transport error. Creating an Artifact from a type is refused in cloud sessions.

**Details**

- The live-files wording is chosen by a runtime capability check derived from the tool's shape rather than a named flag, so which variant you see depends on what the session reports rather than on any setting in this build.

**Evidence**

`"artifact reads through the session gateway are not enabled for this session"`

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

### Third way of transferring a repository to a cloud session

There is a third way to get your repo into a cloud session, an overlay pinned to your head commit.

**What**

The code that decides how to get your repository into a cloud session now has a third option alongside sending a bundle and pointing at a remote reference: an overlay pinned to your local head commit. Whether it is offered is fed in as an input from elsewhere.

**Details**

- The decision now also takes a maximum file count for folder-based transfer.

**Evidence**

`case "overlay":`

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

### Eval run analysis refuses to grade a run that bypassed its mocks

Eval grading now refuses to score a run whose tool calls bypassed the mock servers.

**What**

The code that reads an eval run's transcript was rewritten. It now verifies each stand-in server connected under the expected name and matched a nonce-marked call log, aborts on an expectation violation, and marks the run ungraded when the transcript shows more tool calls than the stand-ins recorded.

**Details**

- Per-tool-call output and error status are now recorded, and permission denials from the result message are honoured.
- Cost is estimated from assistant token usage when no final result message arrives.
- Results carry mock call records, a tally, an aborted record and a mock setup failure field.

**Evidence**

`refusing to run against what may be the real server`

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

### Project-level skill, command and agent folders may now be declined for watching

Watching project skills, commands and agents folders can now be declined by a runtime check.

**What**

Watching of project-scoped skills, commands and agents directories for changes can now be refused. When a new boolean check holds and a context argument is present, Claude Code runs an asynchronous check against that context and watches only if it passes; otherwise it falls back to the previous behaviour of watching any directory that exists.

**Details**

- User-level directories are unchanged and always watched.
- What the new boolean check reads is not stated at the call site, so which projects get declined is decided elsewhere.

**Evidence**

`projectSettings`

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

### Archive sync explains conflicts, deletes and skipped files in plain language

Archive sync now tells you plainly about conflicts, restored deletes and files too large to sync.

**What**

The archive engine narrates what it did rather than acting silently. Conflicted files are described with where each copy landed, deletes the agent made that you still have locally are reported as restored, oversize files are named as "too large to sync and will not arrive", and deferred deletes and uploads are announced as arriving with your next messages. A fresh container is described as having "came back without this folder's files".

**Details**

- Each stop condition gets its own line and emits `tengu_dir_sync_stopped`: withdrawn, switched off, lane unavailable, listing abandoned.
- Pulls and pushes emit `tengu_dir_sync_pull` and `tengu_dir_sync_push` with an outcome and counts of applied, conflicted and trashed files.
- Setting `CLAUDE_CODE_DIR_SYNC_DISABLE_ANCHORING` prints a warning that files are read by name and the protection against a cloud session redirecting a read through a swapped directory is off.

**Evidence**

`File sync is reading this folder's files by name (CLAUDE_CODE_DIR_SYNC_DISABLE_ANCHORING is set): the protection against a cloud session redirecting a read through a swapped directory is off`

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

### New remote flag `tengu_cobalt_plinth_thrift`, off without a server value

A new artifact-related server switch exists and stays off without a server value.

**What**

A gate sits next to the artifact enablement code, reading the remote config flag `tengu_cobalt_plinth_thrift` and requiring an exact true. Its fallback is false, so with no server value the feature behind it is off. What it switches is not evident from the surrounding code.

**Details**

- No environment variable or settings key sets it; it is decided entirely by remote config.

**Evidence**

`tengu_cobalt_plinth_thrift`

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

### Six new device-hook telemetry events, none of which can fire yet

Six telemetry events for machine-run cloud hooks exist but cannot fire in this build.

**What**

The machine-side hook client reports each forwarded hook it serves and each registration attempt, plus consent notices, lapse lines and pinning outcomes. All of it sits behind the device-hooks gate that returns false in this build.

**Details**

- `tengu_device_hook_served` carries event, kind, outcome, exit class, duration, whether paths were translated, and whether the run was a replay, blocked, staged, repinned, or waited.
- `tengu_device_hooks_client_register` carries outcome, trigger, counts of forwarded, templates, held and accepted or ignored entries, and attempt number.
- Also `tengu_device_hooks_consent_notice`, `tengu_device_hooks_lapse_line`, `tengu_device_hooks_reach_pinned`, `tengu_device_hooks_source_pinned`.
- Successful lease renewals are deliberately not reported.
- None of these names exist in 2.1.241.

**Evidence**

`tengu_device_hook_served`

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

### Template uploads are digest-pinned and size-capped

Uploaded hook templates are checked against registered digests and refused with specific reasons.

**What**

Every uploaded hook template is checked against the id and digests already registered before its bytes are kept. Unknown ids, digest or version mismatches, oversized bodies and malformed base64 each get their own refusal message, and re-uploading a digest already held answers "already stored" rather than storing it again.

**Details**

- The request handler caps registration at 64 KiB and uploads at 400 KB, and rejects requests tagged with an out-of-date worker generation.
- Same account-flag and policy gate as the template runner.

**Evidence**

`invalid_upload: request larger than 400 KB`

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

### Cloud sessions can accept an uploaded device hook template

Cloud sessions accept a hook template upload message and report refusals with a reason.

**What**

The cloud-session control channel now handles a new `upload_device_hook_template` message next to the existing device hook registration. Both refuse when hook forwarding to your machine is turned off, and both now report the refusal reason through the device-hooks registration telemetry.

**Details**

- Sits behind the same hook-forwarding feature flag and policy checks as device hook registration.
- The message name does not appear in 2.1.241.

**Evidence**

`upload_device_hook_template`

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

### New server flag `tengu_glimmering_glade`

A new server switch was added next to the bridge flags, and nothing reads it yet.

**What**

A flag helper was added beside the existing bridge flags, with a built-in fallback of true. No code in this build reads it, so it changes nothing today and exists for a server-side rollout to hook into later.

**Evidence**

`tengu_glimmering_glade`

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

### Quick web setup still off

Quick web setup stays off, now behind a named helper needing a flag and two entitlements.

**What**

The check that decides whether quick web setup is available was pulled out of an inline expression into an exported `isWebSetupEnabled` helper. It requires the `tengu_cobalt_lantern` flag plus both the remote-sessions and quick-web-setup entitlements, and the flag's built-in fallback is false, so web setup stays off unless the server turns it on.

**Evidence**

`allow_quick_web_setup`

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

### Cobalt-plinth flags renamed again

Several artifact feature switches were renamed and re-gated, discarding any prior rollout.

**What**

One capability now needs `tengu_cobalt_plinth_madder` as well as the existing `tengu_cobalt_plinth_sorrel`; both fall back to true, so it passes without a server value. Two others were renamed to `tengu_cobalt_plinth_thistle` and `tengu_cobalt_plinth_tansy`, replacing a yarrow-named flag and `tengu_loop_noop_fold`, and both fall back to false. Renaming a flag discards whatever rollout the server had under the old name.

**Evidence**

`tengu_cobalt_plinth_madder`

- Flag `tengu_cobalt_plinth_madder`: Off in both readings (read for one account on one subscription tier against v2.1.242; this account: off, anonymous baseline: off, compiled default: off)
- Flag `tengu_cobalt_plinth_sorrel`: Off by default, switched on for this account (read for one account on one subscription tier against v2.1.242; this account: on, anonymous baseline: on, compiled default: off)
- Flag `tengu_cobalt_plinth_thistle`: Not enough to say (read for one account on one subscription tier against v2.1.242; this account: no value returned, anonymous baseline: no value returned, compiled default: on)
- Flag `tengu_cobalt_plinth_tansy`: Not enough to say (read for one account on one subscription tier against v2.1.242; this account: no value returned, anonymous baseline: no value returned, compiled default: on)
- Flag `tengu_loop_noop_fold`: On for this account, and not off by default (read for one account on one subscription tier against v2.1.242; this account: on, anonymous baseline: on, compiled default: not a boolean we can read)
- Area: Artifacts
- Tier: Under the hood
- Useful: 1/5
- Signal: 3/5
- Present in the build but not switched on

### Cache slot for a sandboxed-attempt decision, with nothing reading it

A slot for remembering a sandboxed-retry decision exists with nothing reading it.

**What**

The per-session store that remembers gate answers gains a slot for a sandboxed-attempt decision, sitting next to the plan-mode resume guard. Nothing in the build reads it, so it looks like groundwork for a sandboxed retry behaviour that has not shipped.

**Evidence**

`sandboxedAttemptGateEnabled;`

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

### A second route for published-file and asset reads

Published-file reads gained a second route via a capability lookup; failures explain the relay is unavailable.

**What**

The check deciding whether published-file and asset reads can run is now satisfied by either the original condition or a new alternative that sits behind a capability lookup. When neither holds in a remote cloud session, the read fails as before with the relay reported unavailable, and its long explanation now lives in shared constants.

**Details**

- What turns the second condition on is decided by that capability lookup rather than by anything set in this build.

**Evidence**

`relay_unavailable`

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

### An artifact republish gate is exported but nothing in the build reads it

An artifact republish switch is exported but nothing in the build reads it.

**What**

A new flag, controlled by `tengu_cobalt_plinth_thrift` and requiring the value to be exactly true, is defined and exported alongside the other artifact feature checks. The only occurrence of the name in the whole bundle is the export itself, so turning it on server-side today would change nothing.

**Details**

- The fallback when the setting is absent is false.
- The setting name does not exist in v2.1.241.
- This is a flag plumbed ahead of the code meant to consume it.

**Evidence**

`isRepublishInlinePromptEnabled`

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

### A pre-shutdown callback slot exists but nothing fills it

A pre-exit callback slot exists, but nothing in the build ever fills it.

**What**

The interactive session host gained a way to register a callback that runs just before Claude Code exits, and the exit path does call it. But the register and clear operations are only reachable through two module-level wrappers that nothing in the build calls, so the slot is always empty and the new step does nothing.

**Details**

- The run step is wired into the normal interactive exit sequence, followed by a short wait.
- Infrastructure landed ahead of whatever will register into it.

**Evidence**

`registerBeforeInteractiveShutdown`

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

### The sandboxed-attempt slot has no gate name, no writer and no reader

The sandboxed-retry slot has no switch, no writer and no reader anywhere in the build.

**What**

The declaration of that sandboxed-attempt slot is its only occurrence in the build. Its neighbours all follow a fixed pattern, either filled lazily from a named remote flag with an on-by-default fallback or given an explicit setter and getter; this one has none of that, and no flag name is attached to it.

**Details**

- Nothing assigns it and nothing reads it, so no behaviour depends on it in this build.

**Evidence**

`sandboxedAttemptGateEnabled`

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

### Artifacts built from a type reject live files

Publishing a type-based artifact with live files is now refused immediately with a clear message.

**What**

Publishing an artifact that was created from a type while also passing live files, detaches or reseeds is refused up front with a fixed message and a recorded reason, instead of being attempted and failing later. The live-files branch itself sits behind a feature check inside the artifact module, so whether this path runs at all is not decided by a user setting.

**Details**

- The input validator also gained a check that live paths on `watch` are valid, and a check for removing already-published files via null entries.

**Evidence**

`an artifact made from a type takes no live files`

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

### Stall and silence timers in the cloud headless client

The cloud headless client warns on slow startup and reconnects when a session goes quiet.

**What**

While the cloud container is still provisioning, a timer warns once if anything is already queued to send. After each send, a watchdog reconnects the stream if the cloud session goes quiet, using a longer timeout while the session is compacting its history.

**Details**

- Both timers increment a `watchdog_fires` count in the record written at end of run.
- Reporting presence and setting a session title are skipped when the session is in essential-traffic-only mode.

**Evidence**

`Still waiting for the cloud session to start; what you sent will be delivered when it is ready.`

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

### Messages from other clients on the same cloud session are rewritten before display

Messages from another client on the same cloud session are trimmed to what yours can display.

**What**

When another client is attached to the same cloud session, its messages are not passed through as-is: content blocks this client cannot show become a placeholder and tool calls are dropped entirely.

**Details**

- The number of each is reported as `peer_content_omitted` and `peer_tool_use_omitted` in the end-of-run event.
- Frame types the client does not recognise are dropped with a warning, except for a known list of service events including `synced_file_changed`, `mcp_auth_required` and `heartbeat_probe`, which are dropped silently.

**Evidence**

`[unsupported content from another client omitted]`

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

### File-sync status for a cloud session comes with a stated reason

Cloud file-sync status now states why sync is off instead of just showing off.

**What**

The session snapshot sent to the host now includes a `directory_sync` block, and each off-state carries a written explanation: this directory was never set up for sync, the lookup failed, or sync was set up from a different directory on the same machine.

**Details**

- Without a sync handle for the session, the state reports as `not_opted_in`.
- A `tengu_remote_headless_laptop_linked` event records the sync state, the reason, the file mode and whether a sync handle exists.

**Evidence**

`File sync for this session was set up from another directory on this machine: edits here are not uploaded, and Claude's changes are not written here.`

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

### Eval runs fail loudly when managed policy blocks the mock servers

Eval runs stop with a clear policy error when your org's MCP rules block the mock servers.

**What**

If your organisation's `allowedMcpServers` or `deniedMcpServers` settings would block the stand-in servers, the eval run stops with a policy-blocked error instead of quietly starting nothing.

**Details**

- The message advises allowlisting the stand-in command for eval runs, or passing `--mocks off`.

**Evidence**

`mocks: stand-in blocked by managed MCP policy`

- Area: Plugin Eval
- Names: `allowedMcpServers`, `deniedMcpServers`, `--mocks`
- Tier: You'll notice
- Useful: 2/5
- Signal: 2/5

### Eval results record which mocks answered and why a run aborted

Eval results now list which stand-in servers answered and count calls nothing covered.

**What**

An eval result now includes a mocks block listing each stand-in server's kind and per-tool responder, defaulting to "fixed", plus a tally that counts calls the mocks did not cover as `unmocked`.

**Details**

- A run that aborted, or whose mocks could not be prepared, returns a zero score carrying the server, tool and reason, plus any `auth_rejected` flag.
- Mocked tools are announced per case and excluded from the check that a case exercises a real tool.
- Mock call logs are read back into the result as call records and a tally.
- A separate standalone eval config schema, covering schema version, graders and config overrides, was dropped from this part of the bundle.

**Evidence**

`mocks: could not be prepared for this case`, `mockSetupFailure`, `"Mock stand-ins for MCP servers, from <eval dir>/mocks/ (record | off; default: record — off spawns the real servers, gated by --allow-tools as usual)"`

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

### Tool-host output gets its own attachment types

Two attachment types for tool-host output and notices were added to the accepted set.

**What**

Two attachment types for output from a tool host, one for its result lines and one for a notice about it, were added to the accepted set, along with a matching message subtype check.

**Details**

- The notice is pushed during an attachment scan only when its producer hook is present, so whether it appears in practice depends on that producer being wired up.

**Evidence**

`tool_host_result_lines`

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

### Telemetry for hook templates and expired leases

Three more telemetry shapes cover hook template runs, uploads and expiring registrations.

**What**

Three more event shapes cover the template path: a per-run event with template id, outcome, duration and dropped fields; an upload event with outcome, template and byte count; and one for a registration lease expiring. Emitted only from the gated device-hook handlers.

**Details**

- The existing registration event gained a count of templates installed alongside those awaiting upload and those refused.
- It also records how long the handler waited for the account flags, capped at 5000 ms, and whether it shared an already in-flight flag read.

**Evidence**

`tengu_device_hook_template_upload`

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

## Internal Changes

### Credentials scrubbed from strings before logging

Tokens, keys, secrets and passwords are stripped from text before it gets logged or sent.

**What**

A new helper strips bearer and basic authorization values and token, key, secret and password assignments out of text before it is logged or sent. Directory-sync records were also added to the telemetry field mapper, carrying a project key and session id.

**Evidence**

`dirSyncRecord`

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

### Counters moved from process globals to per-host state

Cost totals, timestamps and one-shot hints are scoped per host, so separate sessions no longer share them.

**What**

Cost totals (input tokens, lines removed, cache-creation tokens, web-search requests), the last API completion timestamp, the package-manager version cache and the one-shot "fast icon hint shown" flag are no longer shared across the whole process; each is now scoped to a host, so separate sessions no longer share them.

**Evidence**

`fastIconHintShown`

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

### Bridge sessions snapshot the state of the git working tree

Sessions bridged from another environment record the git working tree state when they start.

**What**

Sessions bridged from another environment now record a picture of the repository at startup, which explains how those sessions decide what state they resumed into.

**Details**

- Records branch, head commit, count of commits not pushed to `refs/remotes/origin/<branch>`, whether the tree is dirty, whether an upstream exists, whether a rebase, merge, cherry-pick, revert or bisect is in progress, whether submodules exist, and whether Git LFS is in use.
- LFS is detected by scanning the first 64 KiB of `.gitattributes` for `filter=lfs`.
- The whole probe runs under a timeout; timing out reports "worktree probe timed out" and counts as a collection failure.

**Evidence**

`bridge_worktree_state`

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

### Rate-limit telemetry records every window, not just the tightest one

Usage telemetry now records every rate-limit window separately, including seven-day and overage figures.

**What**

Session telemetry now carries a per-window utilization breakdown with optional `five_hour`, `seven_day` and `seven_day_overage_included` entries, each holding a utilization number and a reset time, taken from the rate-limit headers on responses.

**Details**

- Utilization can exceed 1, for example when a lower-priority episode runs past the 5-hour limit.
- Events fire when a window's rounded percentage or its reset time moves, not only when the overall status changes.
- The field is always absent for API-key, Bedrock and Vertex sessions, and only appears once a response carrying the headers has been seen.
- The existing overall overage status is unchanged.

**Evidence**

`unifiedWindows`

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

### One callback can now run just before interactive shutdown

One callback can now run just before an interactive session exits, with errors swallowed quietly.

**What**

The process lifecycle gained a slot for a single shutdown callback that can be set or cleared, runs once before an interactive session exits, is skipped if shutdown is already under way, and has its errors sent to the error reporter rather than surfaced.

**Evidence**

`runBeforeInteractiveShutdown`

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

### Remote-settings kill switch renamed, and stale reads are discarded

The auto-mode fallback warning now names which kill switch stopped it, and stale settings reads are ignored.

**What**

The warning shown when auto mode falls back to the default now says which kill switch stopped it, a local override or one served in the payload. The remote-settings reader also tracks a step counter so a read that has already been superseded is logged and ignored if it fails.

**Evidence**

`"auto mode killswitch active (override- or payload-served) — falling back to default"`

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

### claude.ai MCP servers carry an enterprise-managed marker

MCP servers from claude.ai can be flagged enterprise-managed, driving the "managed by your organization" label.

**What**

MCP server configurations served by claude.ai now include an `enterpriseManaged` boolean, populated from the API's `enterprise_managed` field, which drives the "managed by your organization" label.

**Details**

- Present whenever the server sends the field; there is no flag.
- One config-writing path deletes it before saving, alongside `eligible`, `ineligibleReason` and `discoverSupport`, so the server-derived marker is not persisted into local config.

**Evidence**

`enterpriseManaged`, `enterpriseManaged: m.enterprise_managed`

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

### Command-line system prompts kept separate from resolved ones

Your typed --system-prompt values are kept apart from the resolved ones for cloud and remote setup.

**What**

A session now carries the `--system-prompt` and `--append-system-prompt` values you typed alongside the finally-resolved prompts, so cloud and remote session setup can tell which came from your command line.

**Evidence**

`appendSystemPrompt: e.appendSystemPromptCli`

- Area: System Prompts
- Names: `--system-prompt`, `--append-system-prompt`
- Tier: Under the hood
- Useful: 2/5
- Signal: 3/5

### Telemetry payload churn around analytics status and device bash

Events now flag when analytics are off, which editor clients use to hide thumbs feedback that would do nothing.

**What**

One event payload stops carrying a field about withheld device bash, and another gains a flag saying analytics are off, which the schema documents as the signal IDE clients use to hide per-message thumbs feedback when rating would do nothing.

**Details**

- The flag's value reflects the configured privacy level, the `DO_NOT_TRACK` environment variable, or use of a third-party model provider.

**Evidence**

`@internal True when the CLI has analytics/telemetry disabled (privacy level, DO_NOT_TRACK, or 3P provider).`

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

### Sessions created without a device binding are now recorded on disk

Sessions started without a device binding are now logged to a capped file with a reason and timestamp.

**What**

A new state file records session ids created without a device binding, each with a reason and timestamp, capped in number and subject to the usual retention cutoff.

**Details**

- Written through the newer storage backend when available, otherwise as a plain file.

**Evidence**

`[deviceBind] unbound create not recorded`

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

### Telemetry for the home-directory seeding path

Three events track the path that seeds a fresh home directory, only on accounts where it runs.

**What**

Three new events track the path that seeds a fresh home directory: apply, hold and recovery, each recorded only when that path runs, which is limited to the same accounts it is.

**Details**

- `tengu_home_seed_apply` carries outcome, probe, generation, epoch_gt1, duration_ms, applied_before_first_ask, output_style_check and per-file refusal counters such as files_refused_loader_reach and settings_refused.
- `tengu_home_seed_hold` carries outcome, waited_ms, verdict_wait_ms and first_ask; `tengu_home_seed_recovery` carries outcome, generation, duration_ms and rules_dropped.
- Outcomes are also reported to health tracking under the component name `ccr_home_seed`.

**Evidence**

`tengu_home_seed_apply`

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

### Layout engine starts tracking content that draws outside its box

Terminal layout can now track content drawing outside its box, but nothing uses the flag yet.

**What**

Terminal layout nodes gained a flag for having a descendant that escapes its bounds, cleared on the fast paths that skip layout. Nothing uses it yet; it is groundwork.

**Evidence**

`hasEscapingDescendant`

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

### More paths carry their own storage handle, and directories are created through it

Hooks, plugin refresh, worktrees, memory writes and MCP reconnect are handed storage and credentials directly.

**What**

Hook runs, plugin refresh, worktree creation, session cleanup sweeps, memory writes, MCP reconnect and agent notifications now receive storage and credential handles rather than fetching them. Directory creation goes through the storage layer when that backend is active and falls back to a plain mkdir otherwise.

**Evidence**

`storageV5: o.storageV5`

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

### Token refresh and session state read from an explicit storage handle

Token refresh, logout, onboarding reset and plugin loading now take a storage handle rather than global state.

**What**

`checkAndRefreshOAuthTokenIfNeeded` now receives a v5 storage handle alongside credentials, and the same pair travels through hook execution, session-team registration, logout and onboarding reset, plugin loading and the file-history writers. Login state can therefore live in that store rather than in process-wide state.

**Evidence**

`checkAndRefreshOAuthTokenIfNeeded`

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

### Plugin and marketplace code carries credentials alongside storage

Plugin install, enable and marketplace flows now carry credentials into every plugin store operation.

**What**

The marketplace manager, the plugin install, enable and uninstall flows and the plugin config screens now take credentials from context and pass them to every plugin-store read, write and cache invalidation. Groundwork for authenticated plugin storage; no visible change yet.

**Evidence**

`credentials`

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

### Telemetry for the git-based directory sync engine

Directory sync reporting now names the engine used and adds six git-engine events; syncing itself is unchanged.

**What**

Directory sync push and pull events now record which engine ran and git-specific counters, and six new events cover the git engine's lifecycle. This is reporting only; nothing about syncing changes.

**Details**

- New counters include bundle size in bytes, commit count, prerequisites, withdrawn installs, held-back deletes, parked overflow and skipped rounds.
- New events: git open (carrying the branch fast-forward rule and whether an upload happens at open), fast forward, peer silent, uploads not taken, unreachable, and reachable again.

**Evidence**

`tengu_dir_sync_git_uploads_not_taken`

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

### Turn analysis survives a bad message and reports when it degraded

A bad message no longer breaks turn analysis; failures are skipped and reported instead of passing silently.

**What**

The check for whether a turn was user-driven now catches errors per message and skips the ones that throw. If the whole scan fails it returns a default and emits telemetry `tengu_turn_tail_analysis_degraded`, so the degradation shows up rather than passing silently.

**Evidence**

`tengu_turn_tail_analysis_degraded`

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

### Remote session-state flush now reports dropped patches

Pushing session state to the cloud now reports whether it truly landed, counting discarded updates.

**What**

Flushing session state to the cloud now returns whether it truly succeeded, counting any worker-state update discarded along the way, matching how client and internal event flushes already reported.

**Details**

- A new `droppedWorkerStatePatches` counter backs the check.

**Evidence**

`droppedWorkerStatePatches`

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

### Session wiring moved into one controller with a cached tool list

Session start, resume and adoption now live in one controller, and the tool list is recomputed less often.

**What**

Session start, resume, adoption of agents, shells and workflows, orphan resume and Artifact live-watch rearming now live in a single controller class, and the tool list is recomputed only when its inputs change rather than on every pass.

**Details**

- The cache is keyed on the identity of the permission context, MCP tools and clients, skill tools, agent definitions, repl bridge flags and slack tag.
- App state gained `artifactRoomJoinConsentSlugs`, which records per-Artifact consent to join its comment room.

**Evidence**

`SessionController: used before its screen bound a host`

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

### Transcript storage failures now say which system error caused them

Transcript save errors now name the underlying system error, so a failed write says why.

**What**

Errors saving transcripts carry the underlying operating-system error code and a telemetry code in their message, so a failed append, move or folder creation identifies its cause.

**Details**

- A folder-creation variant was added alongside the existing append and move errors.
- A check that withholds an append when the target file is missing now reports "onlyIfExists stat failed (…) — append withheld".

**Evidence**

`transcript storage folder creation failed`

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

### Worktree cleanup passes account state to the removal hook

Worktree removal now passes account state to your WorktreeRemove hook and the record-clearing call.

**What**

The routine that removes a git worktree now receives storage and credentials and hands both to the WorktreeRemove hook and to the call that clears the recorded worktree.

**Details**

- Lock ownership, Windows reparse point handling and the fallback to `git worktree remove` are unchanged, as is the wording of the kept and removed log lines.

**Evidence**

`WorktreeRemove hook did not remove worktree, kept at: `

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

### Away-summary recaps rebuilt around a focus subscription

The away recap was rewritten around focus events; same setting, same roughly three-minute delay.

**What**

The recap shown when you come back to a session was rewritten as a standalone object driven by focus events instead of React effects. Behaviour is unchanged: still keyed off the `awaySummaryEnabled` setting, still delayed by a remote-config value whose built-in fallback is 180000 ms.

**Details**

- Still skips the recap when the cache is stale, a draft is sitting in the input, background work is pending, or a structured-output recap already exists.
- The delay is clamped to a minimum floor, and the return-to-session telemetry fields are unchanged.

**Evidence**

`tengu_sedge_lantern_config`

- Area: Session Lifecycle
- Names: `awaySummaryEnabled`
- Tier: Under the hood
- Useful: 2/5
- Signal: 2/5

### Cloud session event posting returns structured outcomes

Cloud event posting now distinguishes a dead session from a network failure.

**What**

Posting an event to a cloud session now returns a named outcome instead of a value or null, so an event sent to a session that is no longer running is distinguished from a network failure.

**Details**

- Outcomes: accepted (with a sequence number), session inactive, or failed with a cause separating HTTP, timeout and network errors.
- An inactive session logs "(session not active)" and increments a `remote_send_event_session_inactive` counter.
- Control responses can now supply their own message identifier and are counted as sends.

**Evidence**

`remote_send_event_session_inactive`

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

### Failed WebSocket upgrades are reported with the reason, not just a status

Failed WebSocket connections now record why, separating a server rejection from a Cloudflare block.

**What**

When a WebSocket connection fails to upgrade, the recorded outcome now distinguishes a rejection by the server from one mitigated by Cloudflare, with the HTTP status appended and clamped to 100 to 599. Close reasons for asks that were shown locally but never forwarded are flagged separately.

**Evidence**

`cf_mitigated`

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

### Rate-limit status re-emits on any window change

Rate-limit status updates now fire whenever any usage window changes, not just the coarsest one.

**What**

The utilization tracker now remembers what it last emitted for each of the three windows and emits a status change whenever any one of them differs, rather than on the coarser previous condition. The overage header name became a shared constant used in both places it is compared.

**Evidence**

`lastEmittedWindowParts`

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

### Telemetry-suppression status is now sent on a second channel to IDE hosts

Editors are now told live whether telemetry is off, on a second channel besides startup.

**What**

The boolean saying the CLI has telemetry turned off, previously only in the startup message an editor extension receives, is now also published on a second control-protocol surface, computed from a live check rather than a static value.

**Details**

- It sits alongside the feedback-survey config and the remote-control auto-enable flag on that surface, and is always sent there.
- The description states that IDE hosts fold it into the gate for their own direct telemetry, and that an older CLI which omits it should be treated as unknown rather than as enabled.
- Nothing changes for someone using the CLI directly; it changes what an attached IDE is told.

**Evidence**

`IDE hosts fold it into the gate for their own direct telemetry. Absent (older CLI) → unknown.`

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

### Directory-sync path checks moved onto the storage backend

Path checks for directory sync go through the storage backend, and the mismatch warning wording is shared.

**What**

Checks on paths owned by directory sync now classify a path as real, absent, redirected or not-a-directory through the storage backend when that backend is active, falling back to walking the path with lstat otherwise. The message you see when a session's sync was set up from a different directory now comes from a single shared helper on both paths.

**Details**

- Probe failures now have distinct codes, and a classifier for a vanished source produces codes such as `batch_dir_vanished` and `source_unverifiable_parent_link`.
- Whether the backend path or the lstat fallback runs is decided by an internal storage-backend check that is not named in the surrounding code.

**Evidence**

`sync-owned path probe: unexpected answer`

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

### Notification dispatch accepts a caller-supplied storage handle

Notification sending can be given a storage handle, defaulting to none, so nothing changes today.

**What**

The notification entry point takes an optional third argument holding a storage handle and credentials, forwarded to the session-record write it does before sending on the configured channel. It defaults to empty, so behaviour is unchanged for existing callers.

**Evidence**

`preferredNotifChannel`

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

### Telemetry for plugin test mocks and refused subscriptions

Plugin evaluation mock failures are reported by kind, and refused subscriptions get their own reason.

**What**

Failures setting up mocks during plugin evaluation are now reported under a single event, tagged by kind: registration, identity, missing tools, integrity, or aborted by the mock. Separately, the code that classifies subscription outcomes gained a forbidden-subscribe reason with a latched variant.

**Evidence**

`cli_plugin_eval_mocks`

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

### BigQuery metrics export skips on host mismatch

BigQuery metrics export is skipped when the dispatch host and metrics endpoint host differ.

**What**

Before exporting metrics to BigQuery, the exporter checks that the workload-identity dispatch host matches the host of the configured metrics endpoint, and skips the export when they differ. Only affects deployments using this export path.

**Evidence**

`"BigQuery metrics export: WIF dispatch host differs from the metrics endpoint host, skipping"`

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

### Transcript components take one turn object

Transcript components now share one turn object, and remote sessions write into the same transcript store.

**What**

Transcript components that each received separate streaming, loading and auto-queue callbacks now take a single turn object, with a shared helper deciding whether a response is still streaming. Remote sessions push into the same transcript store rather than setting messages and loading state directly.

**Details**

- The helper prefers an optional `getIsResponseStreaming()` callback and falls back to the turn's mid-turn flag when it is absent.
- Remote-session hooks now call `setExternalLoading` and `markRemoteTurnComplete` instead of the old message and loading setters.

**Evidence**

`getIsResponseStreaming`

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

### Expired cache rows are swept server-side

Expired cache entries are swept away server-side, and cache key names must be 1 to 64 characters.

**What**

A helper was added that deletes key-value cache rows whose expiry has passed, warning rather than failing if the delete errors, and key names are now validated at 1 to 64 characters.

**Details**

- The sweep runs as a single delete over rows with an expiry in the past; rows with no expiry are untouched.
- No condition guards the call site, so the sweep runs whenever that path is reached.

**Evidence**

`DELETE FROM kv WHERE expires_at IS NOT NULL AND expires_at <= now()`

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

### Storage and credential handles passed as arguments instead of read globally

Dozens of functions now receive storage and credentials as arguments instead of reading global state.

**What**

Dozens of functions, including plugin loading, marketplace fetch, session archive lookup, MCP login and logout, worktree helpers, quota probing and session file writers, now take storage and credential handles as parameters. Nothing changes for a user; a pile of implicit global state leaves the credentials path.

**Evidence**

`async function ce(e, { storageV5: o, credentials: t }) {`

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

### Eval output options and judge prompt loosened

Eval runs can output mock call records and grade results on mock calls alone.

**What**

Eval runs can select mock call records as an output alongside trace, last message and files, and results carry the mocks block and a flag for runs graded on mock calls alone.

**Details**

- The suite adoption record now tracks a separate consent decision alongside the adoption decision, both derived rather than fixed to true.
- The judge's closing instruction, previously the hardcoded "Respond with exactly one word: PASS or FAIL.", is now supplied by the caller.

**Evidence**

`"mock_calls"`

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

### Credentials passed into MCP setup, cloud session start and render paths

Credentials now travel into MCP setup, cloud and teleport session start, and several render paths.

**What**

Credentials now travel with the storage handle into MCP session start and setup, into the cloud and teleport session option bags, and into several render paths. The number of sites passing credentials rises from 599 to 800.

**Evidence**

`credentials: e.credentials`

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

### Classifier requests choose how retries are counted

Tool classifier requests choose whether retries count per attempt or per whole call.

**What**

Requests from the tool classifier now say whether the retry budget is counted per attempt or per whole call, changing when they are given up on. The same path can also be handed credentials directly.

**Details**

- The classifier path passes per-attempt; the other caller passes per-call.

**Evidence**

`if ((i.count++, s === "per_attempt" && !u.aborted))`

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

### Remote settings and helper consent mirrored into a second store

Remote settings and helper consent are now also mirrored into a second in-process store.

**What**

Saving remote settings, saving or deleting helper consent, and clearing the cached settings now each also write the new value, or null on deletion, into a second in-process store. Bookkeeping only, with no visible effect.

**Details**

- Applies on both the storage-backend and the plain-file paths.
- The cache clear is now only recorded when the backend delete reports success, and the file-delete path swallows only a missing-file error rather than everything.
- Both mirror writes sit behind a pair of conditions that nothing in this build decides.

**Evidence**

`Remote settings: Saved helper consent via storage backend`

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

### BigQuery export host check runs ahead of the org-setting check

The BigQuery host check runs before the org setting, and a missing dispatch value blocks export entirely.

**What**

The new host comparison happens before the organisation setting is consulted. When workload-identity dispatch is active, the dispatch host (defaulting to `api.anthropic.com` when unset) is compared with the metrics endpoint host; a mismatch reports success without exporting anything, and a missing dispatch value also blocks the export. When that dispatch path is not active the check passes through.

**Evidence**

`BigQuery metrics export: WIF dispatch host differs from the metrics endpoint host, skipping`

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

### Custom session titles can be stored through the v5 backend

Custom session titles can be written through the storage backend, falling back to plain files.

**What**

Writing or deleting the small file that holds a session's custom title now tries the storage backend first, when that backend is enabled and a handle was supplied, and falls back to direct filesystem create and remove otherwise. Failures are logged at error level with a message ending in "via storage".

**Details**

- The plugin and marketplace loaders use the same try-storage-then-filesystem shape, now threading credentials through catalog fetches, and gained a path for reading a preview catalog.

**Evidence**

`deleteSessionTitleSidecar: `

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

### The bundle is split into chunks loaded on demand

The program is split into pieces loaded as needed, changing startup shape but nothing you interact with.

**What**

Code that previously all lived in one file is now split across separate chunk files pulled in as they are needed, which changes startup and memory shape but nothing you interact with.

**Details**

- Over a thousand call sites now load a chunk by path; the previous build had none and resolved everything inline.
- The large design-sync skill prompts have moved out of the main file into a chunk and are still shipped in full.
- Deep-link handling, config enabling, and the two directory-sync engines are now opened this way, which means those paths can fail at load time. The sync engines record `_engine_open_failed` and `_engine_open_timeout` for that case.

**Evidence**

`import("/$bunfs/root/chunk-XXXXXXXX.js")`

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

### The binary is now genuinely code-split

Onboarding, worktrees, tmux, Bedrock, Vertex and more now load on demand instead of at startup.

**What**

Deferred module loads across the CLI, onboarding, worktree and tmux launch, Chrome MCP wiring, machine-id lookup, the SDK, Bedrock, Vertex and Foundry clients, sandbox install, the daemon hub and AWS SSO now load separate chunk files on demand instead of running an inline initialiser. Nothing behaves differently; first-use cost for those paths moves from module init to loading a chunk.

**Details**

- Hundreds of module-init wrappers were removed and their contents inlined or relocated.
- The worktree launch path now pins storage and initialises the debug log and feature-flag credentials before handing off.

**Evidence**

`HOOKS_WORKER_URL`, `await import("/$bunfs/root/chunk-XXXXXXXX.js")`

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

### Update manifests fetched with a compressed variant in parallel

Update manifests fetch plain and compressed versions at once instead of retrying in sequence.

**What**

Fetching the manifest that drives installs and updates now requests the plain and compressed versions at the same time and continues with a logged reason when the compressed one is absent, replacing a serial retry loop.

**Evidence**

`No compressed manifest for `

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

### Lookalike angle brackets come from a fixed table

Lookalike angle-bracket protection uses a fixed 34-entry table instead of scanning 65,000 characters at startup.

**What**

The sanitizer guarding tag parsing against lookalike characters used to scan 65,000 code points at start-up to build its map; it now uses a fixed 34-entry table of characters that fold to "<" or ">". Same protection, no start-up scan, and the same set on every platform.

**Evidence**

`\u27E8`

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

### Optional modules are now loaded from separate chunks inside the binary

Over a thousand code paths, including browser integration, now load only the first time you use them.

**What**

Code that used to be inlined into one big module graph is now split into chunks loaded on demand, across 1,035 sites where the previous build had none. Startup pulls in less; features like the browser integration are loaded the first time they are used.

**Evidence**

`await import("/$bunfs/root/chunk-`

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

### Turn handling moved out of the REPL into the turn controller

Turn handling moved out of the prompt loop into the turn controller with nothing changing on screen.

**What**

Work that lived in the interactive prompt loop now belongs to the turn object, with no change to what happens on screen.

**Details**

- Moved: loading-state reset, spinner tip selection, session signal tracking (bash tools, hosts, tools used), rate-limit auto-continue arm/cancel/prefill/retract, initial message submission with the resume partial-output hint, and queued input execution.
- Many separate references, including file-read state, session env vars, tool state, memory selection, isolation latch, content replacement, tool and MCP computation, dialog requests and sandbox verdicts, were collapsed into one `scope` object on the host.
- The telemetry involved already existed in the previous build.

**Evidence**

`prefillRateLimitAutoQueueContinue`

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

### Much of this release's diff is re-bundling, not behaviour

Most of this release's changed code is the build system moving modules, not features changing.

**What**

A large part of the changed code is the build system moving modules around; the affected features are present in both builds unchanged.

**Details**

- Confirmed still present and unaltered: the /simplify and code-review prompts, prototype and whiteboard skills, todo and ExitPlanMode tool descriptions, design-sync capture, resync and preview-rebuild scripts, the remote-diff script's local mode, the keybindings and run-unit skills, directory sync, git bundle upload, device bash, the Workflow tool description, the ultraplan planning reminder, brief-mode reminder and Artifact tool schemas.
- The Codex import path and the status line are recompilations; the status line now reads messages from a transcript snapshot instead of a live reference, with no visible difference.

**Evidence**

`__ULTRAPLAN_TELEPORT_LOCAL__`, `tengu_ccr_bundle_upload`

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

### Session journal keys are checked for consistency

A key naming a session's own journal is rejected if it also carries agent or run details.

**What**

A key that names a session's own journal is now rejected if it also carries an agent id, an agent path or a run journal, with a message spelling out the rule.

**Evidence**

`a session journal key names the session's own journal: no agentId, agentRelPath or run journal`

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

### More detail in directory-sync, workflow and LSP telemetry

Directory sync, workflow and language-server logs now record more detail about what failed.

**What**

Directory-sync reporting now breaks deletions into five counts, workflow settle failures are logged as warnings, and failing to load a language-server config is now distinguished from a config that loaded but was invalid.

**Details**

- The deletion counts cover deletions sent, newly seen, withheld as a suspiciously large batch, held back, and over the cap.
- Plugin telemetry gained a field identifying which server-side plugin was involved.
- The device-bridge start event no longer reports withheld device bash.

**Evidence**

`workflow_launch_settle_failed`

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

### Design-sync skill scripts moved between bundle slots

The design-sync skill's scripts were repacked into different bundle slots with identical behaviour.

**What**

The embedded scripts for the design-sync skill were re-packed into different parts of the bundle with no behaviour change; the same verdict gate, style-system filter and import-policy checks are present as before.

**Evidence**

`[DTS_STYLE_SYSTEM] filtering `

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

## Removed

### Screenshot no longer explains which apps it hid

Screenshots no longer tell Claude which apps were hidden or how to request access to them.

**What**

The text that told the model which running apps were hidden from a screenshot because they were not on the session's allowlist, and pointed it at the access-request tool to add them, is gone.

**Evidence**

`request_access to add `

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

### Build-machine paths taken out of the bundle

Absolute paths from Anthropic's own build machine no longer ship inside the installed bundle.

**What**

Three helpers that resolved an asset relative to a hard-coded path from Anthropic's internal build machine were removed, so those absolute paths no longer ship.

**Evidence**

`/home/runner/work/claude-cli-internal/claude-cli-internal/src/frame`

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