# Claude Code v2.1.219

> Claude Code v2.1.219, released 24 Jul 2026 (2026-07-24). 166 entries read out of the shipped bundle. Unofficial, and not affiliated with Anthropic.

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

Thirty-nine entries in this build are present but not switched on. Plan mode can carry an interactive workshop page whose choices feed back into the session through a `.workshop.md` document, and the workshop skill itself moved from hard-disabled to a check on remote config and the Artifact tool schema. Memory files can mark `pinned: true` in frontmatter to be injected into every conversation, and a second memory branch that picks documents from a search index rather than asking the model sits behind an off switch called `tengu_mill_orange`. The `/plugin` dialog gains a Stats tab reporting skill usage and context costs, with `/skill-doctor` redirecting into it, and the keybinding table reserves thirteen action ids for a surface named "strip" that has no handler yet.

Opus 5 joins the bundled model catalog and the `opus` alias now points at it instead of Opus 4.8, with an extra system-prompt instruction telling it not to spawn subagents or run workflows unless asked. Hooks gained a `DirectoryAdded` event that fires when a working directory is registered mid-session, whether by /add-dir or by the SDK. The control-protocol `interrupt` request takes an optional `cancel_queued` boolean that clears the whole main-thread queue alongside the in-flight turn and reports the uuids it killed. Sandbox network settings gained `strictAllowlist`, which denies hosts outside `allowedDomains` outright rather than prompting, and MCP allow and deny policy entries now expand `${VAR}` references with a safety analysis that drops allowlist patterns whose meaning changes under expansion. A new `workflowSizeGuideline` setting takes four values and now defaults to medium, advising fewer than 15 agents per workflow where an unset value previously meant no guidance at all.

On Windows, pointing `CLAUDE_CODE_GIT_BASH_PATH` at a path that does not exist now warns and continues instead of exiting the process. A single memory push failure used to silently stop all further pushes for the rest of the session; suppression now applies per store, and team-memory mounts re-enable themselves once a conflicting manifest clears. Pressing left-arrow to background agents is blocked while there is unsent text in the input, so a draft cannot be lost to the shortcut. Background sessions compare recorded and running CLI versions and skip the upgrade respawn when the running build is already newer, an MCP config using the literal key `__proto__` as a server name is now reported rather than silently loading nothing, and OSC 52 clipboard writes are chunked under GNU screen.

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

### Plan mode can carry a session workshop document

Plan mode could offer an interactive published page where your choices feed back into the plan.

**What**

Plan mode can offer to plan through a published interactive workshop page whose choices feed back into the session, backed by a `.workshop.md` document alongside the plan file.

**Details**

- Plan-mode attachments gained `workshopOfferDocPath` and `workshopActiveDocPath`, which inject the offer text and the active-session text respectively.
- Sparse reminders append "and the session's workshop document (…, editable and publishable as previously granted)"; the full form grants create/edit plus Artifact publish for it.
- Turn-ending rules gain a third exit, publishing the workshop and ending the turn, and Phase 4's "only file you can edit" line becomes conditional.
- The plan file stays canonical; the offer is made once, after judging whether the task has substantive decision points. Forking a plan also copies the `.workshop.md` sibling, and session resume can rebuild the doc from a file snapshot ("Workshop doc recovered from file snapshot").
- Artifact publishing paths thread a `workshopSurface` flag through their result payloads. New telemetry: `plan_workshop_offer` and `plan_workshop_active`.
- Gated on `Wst() = Xde() && !bV() && !Ke("tengu_cedar_transom",!1) && Ke("tengu_larch_pavise", !1)`, so off absent remote values.

**Evidence**

`A decision workshop is in progress for this session`, `## Interactive Workshop Option`, `workshopActiveDocPath`

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

### /plugin gained a Stats tab and /skill-doctor moved into it

A Stats tab in /plugin shows skill usage and context costs, with /skill-doctor redirecting there.

**What**

The plugin UI has a new stats view showing skill usage and context costs, and /skill-doctor now redirects there.

**Details**

- New `"stats"` view and a tab titled "Stats"; both `/plugin stats` and the marketplace argument parser route to it, with help line " /plugin stats - Show skill usage and context costs".
- A notice tells users "/skill-doctor moved — skill usage and context costs now live in this Stats tab."
- /skill-doctor is registered as two variants, an immediate local-jsx one that renders the view and the existing local one that produces plain text in non-interactive mode, both described as "Show which loaded skills are unused and costing context".
- `skill-doctor` was added to the command log-level map as "info".
- Gated on `tengu_lantern_prism` (fallback false) or the `CLAUDE_CODE_LANTERN_PRISM` env var; the skill-doctor registration now sits on the same gate as the Stats tab.

**Evidence**

`/skill-doctor moved \u2014 skill usage and context costs now live in this Stats tab.`, `skill usage and context costs now live in this Stats tab.`

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

### /plugin gains a stats subcommand

A stats subcommand for plugins would show skill usage and context costs, but it stays hidden unless a flag is on.

**What**

The `/plugin` command completion list adds a `stats` subcommand described as showing skill usage and context costs, listed after list, enable, disable, install, uninstall and marketplace.

**Details**

- gated on the `tengu_lantern_prism` flag, which falls back to false, or the `CLAUDE_CODE_LANTERN_PRISM` environment variable
- with the gate off the subcommand does not appear in completions

**Evidence**

`Show skill usage and context costs`

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

### Auto-mode permission classifier can emit a numeric severity instead of a yes/no block

Auto-mode permission checks can score risk 0-100 instead of yes/no, but only for configured models.

**What**

When a severity config exists for the current model, the auto-mode classifier prompt is rewritten to ask for a numeric score rather than a block decision. Without that config the prompt keeps its existing form.

**Details**

- the trailing "## Output Format" section is replaced to demand `<severity>N</severity>` on a 0 to 100 scale, with 50 as the allow/block boundary, instead of `<block>yes/no</block>`
- gated on `severityByModel` in `tengu_auto_mode_config`; models absent from that map take the unchanged path
- the permission rules injected into the classifier prompt are wrapped in `<cc_automode_permissions>` tags
- denials taken because auto-mode is active use a distinct user-facing message naming the mode

**Evidence**

`Permission to use ${e} has been denied because Claude Code is running in don't ask mode.`

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

### Bundled workflows can gate model invocation at runtime; deep-research wired to a new flag

Claude could start deep research on its own instead of waiting for you to ask, if enabled.

**What**

Workflow registration accepts a function for `disableModelInvocation`, and the bundled deep-research workflow now uses one so a remote flag can decide whether the model may invoke it.

**Details**

- When converting a workflow to a command the code checks `typeof e.disableModelInvocation === "function"` and calls it; v2.1.218 only ever read a boolean.
- deep-research previously registered `{ disableModelInvocation: !0 }`, meaning user-invocable only.
- It now passes a function checking gate `tengu_sorrel_avocet` with fallback false, so absent a remote value behaviour is identical to before.
- If the gate is turned on remotely, the model can invoke deep-research itself.

**Evidence**

`tengu_sorrel_avocet`

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

### Index-based memory recall behind `tengu_mill_orange`

Memory recall could pick documents from a search index instead of asking the model, but it is disabled.

**What**

Memory recall has a new branch that selects documents from a search index rather than asking the model to pick, switched off in this build.

**Details**

- The search entry point takes four extra arguments; when the first is true the new branch uses `searchMemoryFilesWithIndex` in place of the model-driven memory selector.
- Emits a `memory_recall_select` event with `via_index`, `candidate_docs`, `index_query_term_count`, `index_hit_count`, `index_below_floor_count`, `index_top_score`, `index_relevance_floor`, `index_loaded_from_disk`, `index_took_ms`, `index_store_held`, `index_open_wait_ms` and `index_store_open_ms`.
- Failure reasons: `index_sweep_incomplete`, `index_query_failed`, `below_relevance_floor`, `no_servable_hits`, `partially_below_floor`, `all_filtered`.
- Gated on `tengu_mill_orange`, whose in-source fallback is false; with it off the old selector path runs unchanged.

**Evidence**

`tengu_mill_orange`

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

### Pinned memories are injected into the session

Adding `pinned: true` to a memory file's frontmatter would apply it to every session automatically.

**What**

Memory files whose frontmatter carries `pinned: true` are injected so they apply to every session rather than only when retrieved.

**Details**

- Pinned files are scanned, sorted by mtime, capped in count, and truncated when oversized
- Frontmatter documentation now describes the `pinned` field
- New one-shot telemetry event `tengu_memdir_pinned_injected` reports pinned_file_count, pinned_injected_count, pinned_injected_chars, truncated and malformed counts
- New scan-failure paths log "memory_scan_readdir_failed" and "pin_scan_failed"
- Gated on `tengu_stone_shell` with fallback false, and suppressed entirely when CLAUDE_COWORK_MEMORY_INDEX_CONTENT is set

**Evidence**

`tengu_memdir_pinned_injected`

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

### Pinned memory files

Memory files marked pinned would be injected into every conversation instead of waiting to be recalled.

**What**

Memory files can mark themselves pinned in frontmatter and are then injected as their own prompt section on every conversation rather than listed with ordinary memory files.

**Details**

- `metadata.pinned` is parsed into a `pinnedState` of true, false or malformed; the most recently modified pinned files are selected up to a cap and read as a new `AutoMemPinned` entry type.
- Rendered as a dedicated section of `<pinned-memory path="…">` blocks with control characters stripped from the path, and excluded from the ordinary memory-file editor list.
- Telemetry: `memory_pinned_inject` records injected counts, truncation, malformed pins and cap drops; `tengu_memdir_pinned_injected` records counts plus truncated/malformed; `tengu_memdir_pin_write` fires on writes to memory files.
- Lives in the cowork memory path, running only when the surrounding index gate passes and the `CLAUDE_COWORK_MEMORY_INDEX_CONTENT` env var is unset.

**Evidence**

`# Pinned memories (apply to every conversation)`, `AutoMemPinned`

- Area: Memory
- Names: `pinned`, `CLAUDE_COWORK_MEMORY_INDEX_CONTENT`
- Tier: Not switched on
- Useful: 3/5
- Signal: 4/5
- Present in the build but not switched on

### Skill usage stats surface expanded

A skills stats tab can report unused skills, and /skill-doctor gains a version that works without an interactive UI.

**What**

The Stats tab in the `/plugin` dialog now receives the context it needs to report unused skills, and a non-interactive variant of `/skill-doctor` is registered.

**Details**

- the tab is handed a `getSkillStatsInputs` callback supplying commands, MCP commands and clients, agent id, main-loop model and permission mode
- rendering the tab without that callback throws "Stats tab rendered without getSkillStatsInputs"
- a second `/skill-doctor` command variant is registered alongside the existing JSX one, marked `supportsNonInteractive` and dispatching after text
- still behind `tengu_lantern_prism` (fallback false) or the `CLAUDE_CODE_LANTERN_PRISM` environment variable

**Evidence**

`Show which loaded skills are unused and costing context`

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

### Reserved keybinding action ids for a "strip" surface

Thirteen keybinding names for a surface called "strip" are accepted but do nothing if you bind them.

**What**

The list of recognised keybinding action ids gained thirteen entries for a surface called "strip", with no handler present in this build.

**Details**

- the new ids are `strip:jump1` through `strip:jump9`, plus `strip:next`, `strip:previous`, `strip:toggle` and `strip:new`
- the names appear nowhere else in the bundle, so binding them validates but does nothing
- this looks like groundwork and validation only

**Evidence**

`strip:toggle`

- Area: Terminal UI
- Names: `strip:next`, `strip:previous`, `strip:toggle`, `strip:new`, `strip:jump1`
- Tier: Not switched on
- Useful: 2/5
- Signal: 4/5
- Present in the build but not switched on

### The workshop skill moved from hard-disabled to flag-gated

The workshop skill went from permanently off to flag-gated, so remote config could switch it on.

**What**

The workshop skill's isEnabled was a function that returned false unconditionally; it is now a composite check, so it can appear for users whose remote config and Artifact tool schema both line up.

**Details**

- Gated on the artifacts flag `tengu_gable_onyx_sluice`, whose fallback is false, so it stays off absent a remote value
- Additionally requires the Artifact tool schema to expose a `capabilities` input and a `read_page_data` action
- All three conditions must hold before the skill is offered

**Evidence**

`tengu_gable_onyx_sluice`

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

### Whiteboard skill reworked with first sketch, named boards and up-front send-permission checks, still switched off

The whiteboard skill gained opening sketches, named boards and clearer send-permission errors, but stays switched off.

**What**

The whiteboard skill and its bundled page were substantially reworked, but registration still gates on a helper that returns false, so none of it is reachable.

**Details**

- Claude is instructed to draw an opening sketch of its read of the design before anything else.
- The page is built through `merge-state.mjs`, which gains a `--title` flag naming the board "Whiteboard — <topic>" and refuses to silently rename a board that was already sent.
- Republishing with capabilities re-declared happens only when the user says Send to Claude is unavailable.
- The bundled page restyles onto inlined Claude Design System tokens.
- The Send button is now disabled at load with a specific reason when the permission state is permanently blocked, rather than the block being discovered on first click; permanent and transient permission codes are handled separately.
- Toasts gain a configurable duration.
- Gate: `isEnabled` returns `PQi() && j2e()`, and `PQi()` returns `!1`.

**Evidence**

`whiteboard send blocked`

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

### Shutdown-synthesized tool results no longer resolve an interrupted turn

Resuming a turn interrupted by shutdown now correctly sees the tool call as still pending instead of faked-answered.

**What**

When history is rebuilt to resume an interrupted turn, tool results that were only synthesized by a shutdown are no longer treated as answers, so the underlying tool call is seen as still pending.

**Details**

- the unwind pass is called with a new option `shutdownUnwindResultsDoNotResolve: !0`
- the affected messages are user messages with `interruptedByShutdown === !0` that contain a tool_result block; these no longer resolve their tool_use and are dropped from the rebuilt list
- the transcript scan for an unresolved tool_use skips those same messages, so a synthesized result cannot mask a genuinely unresolved tool use
- that scan was split into a pure over-messages helper, `findUnresolvedToolUseInTranscript`, with I/O, warn logging and try/catch left in the async wrapper
- consecutive user messages are no longer merged if either was interrupted by shutdown, and the tail-scan for the last live turn skips shutdown-interrupted content
- the user-rejected tool denial path now records `interruptedByShutdown` from the abort signal
- the whole path is reached only under the `CLAUDE_CODE_RESUME_INTERRUPTED_TURN` environment variable

**Evidence**

`shutdownUnwindResultsDoNotResolve`, `findUnresolvedToolUseInTranscript`, `interruptedByShutdown`

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

### Subagent steering can be resolved per model

Subagent nudging behaviour can be picked per model, but the whole feature stays behind a gate.

**What**

Steer mode can now be chosen based on the model in use rather than only env, client data and growthbook.

**Details**

- Resolver values are `default`, `no_nudges` and `counter_steer`
- A fourth source runs after env, client data and growthbook: if a model was registered for this resolution it consults a model-keyed lookup and reports `source: "model"` in `tengu_subagent_steer_applied`
- Env var `CLAUDE_CODE_THISTLE_GREBE` still wins over everything
- Gated on tengu_thistle_grebe / CLAUDE_CODE_THISTLE_GREBE

**Evidence**

`counter_steer`

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

### Opus 5 replaces Opus 4.8 as the default Opus model

Opus 5 is now the default Opus, with a 1M-context option in the model picker.

**What**

The bundled model catalog gains `claude-opus-5` and the `opus` alias now points at it, so anything resolving the newest Opus resolves Opus 5 instead of Opus 4.8.

**Details**

- Catalog entry: display name "Opus 5", knowledge cutoff "May 2026", 1M-token context with `supports_1m_suffix`, `max_output_tokens` default 64000 and upper 128000, `pricing: "tier_5_25"`, `default_effort: "high"`, `fallback_3p: "claude-opus-4-8"`, `vertex_region_env_var` "VERTEX_REGION_CLAUDE_5_OPUS", and a new capability tag `opus_5_prompt_bundle`.
- Provider ids ship for bedrock (`us.anthropic.claude-opus-5`), vertex, foundry, mantle (`anthropic.claude-opus-5`) and gateway. The `opus` alias resolves to claude-opus-5 for first-party, bedrock, vertex, mantle and anthropic_aws; foundry still maps to claude-opus-4-6 and gateway to claude-opus-4-7.
- Three internal default-model constants changed from the literal "opus48" to "opus5", and the first-party preference order went from ["opus48","opus47","opus46","opus45"] to ["opus5","opus48","opus47","opus46","opus45"]. The alias table gained `"claude-opus-5": "opus5"` and `latest_per_family.opus` moved to claude-opus-5.
- The picker shows a plain "Opus" row plus an "Opus (1M context)" variant, and Opus 4.8 is demoted to "Opus 4.8 · Previous Opus version". `OPUS_NAME` is now "Claude Opus 5" with the old pair kept as `PREV_OPUS_ID` / `PREV_OPUS_NAME`.
- Pricing suffix lookups now query `claude-opus-5`, unrecognized-model fallbacks return "claude-opus-5" instead of "claude-opus-4-8", and `claude-fable-5` falls back to it via `fallback_3p`.
- The catalog data is unconditional in this build; whether a row is offered still depends on the model being available for the active account and provider, which the picker checks via provider id / modelOverrides.

**Evidence**

`Opus 5 - best for everyday, complex tasks`, `Opus 5 with 1M context window - for long sessions with large codebases`, `opus_5_prompt_bundle`, `PREV_OPUS_NAME: "Claude Opus 4.8"`, `claude-opus-5`

- Area: Models
- Names: `opus`, `claude-opus-5`, `VERTEX_REGION_CLAUDE_5_OPUS`
- Tier: You'll notice
- Useful: 5/5
- Signal: 0/5

### `claude mcp list` shows why a server failed

claude mcp list now tells you why a server failed, like a timeout or HTTP error, not just "Failed to connect".

**What**

A failed server is no longer reported as just "Failed to connect"; the status line can now say what went wrong.

**Details**

- the per-server status result optionally carries an `issue` field, which the list formatter appends to the status
- the detail is derived from the error code and `displayDetail`, for example "request timed out" or an HTTP code
- MCP error reporting joins `error` and `displayDetail` into a single sanitised string

**Evidence**

`Failed to connect`

- Area: MCP
- Names: `claude mcp list`
- Tier: Use it now
- Useful: 4/5
- Signal: 0/5

### CLAUDE_CODE_GIT_BASH_PATH no longer kills the process

A bad CLAUDE_CODE_GIT_BASH_PATH on Windows now warns and auto-detects instead of quitting.

**What**

On Windows, pointing the variable at a path that does not exist used to print an error and call process.exit(1). It now warns and carries on.

**Details**

- Falls back to normal auto-detection instead of exiting
- The path is additionally rejected unless its basename is bash.exe, sh.exe, bash or sh
- Windows git-bash lookup only

**Evidence**

`falling back to auto-detection`

- Area: Windows
- Names: `CLAUDE_CODE_GIT_BASH_PATH`
- Tier: Use it now
- Useful: 4/5
- Signal: 0/5

### MCP policy allow/deny entries now expand `${VAR}` environment references, and unsafe URL expansions fail closed

MCP allow and deny policy entries now expand ${VAR} references, skipping allowlist entries whose expansion looks unsafe.

**What**

`allowedMcpServers` and `deniedMcpServers` predicates are no longer matched literally; stdio `serverCommand` entries and remote `serverUrl` patterns go through environment-variable expansion first, with a safety analysis that discards allowlist entries whose expansion changes what the pattern means.

**Details**

- Denylist expansion uses a merged env drawn from global config, user, flag and policy settings as fallbacks; the allowlist path uses the settings-derived env.
- Substituted values are normalised through a placeholder that returns the constant `zzenvsubzz` (or a digit-zeroed form for numeric values, `::` for IPv6-looking ones) so the expanded URL can be re-parsed and compared against the original.
- Flagged as unsafe: an injected value that adds wildcard semantics, changes the URL's structure or hostname, or introduces traversal or query/fragment characters.
- Allowlist URL entries with an unsafe expansion are skipped, so they fail closed; denylist entries still apply.
- Missing variables emit a debug warning that the predicate references environment variables not present in the policy expansion env; unsafe expansions log that the expansion was unsafe.
- Neither the placeholder nor the wildcardVars analysis exists in v2.1.218, and the settings schema descriptions were not updated to mention any of it.

**Evidence**

`zzenvsubzz`, `MCP policy URL predicate expansion was unsafe`

- Area: MCP
- Names: `allowedMcpServers`, `deniedMcpServers`, `serverCommand`, `serverUrl`
- Tier: Use it now
- Useful: 4/5
- Signal: 0/5

### New DirectoryAdded hook event

You can now run a hook whenever a directory is added mid-session, including via /add-dir.

**What**

Hooks gained a `DirectoryAdded` event that fires after a working directory is registered mid-session, so you can react to /add-dir and to SDK-driven directory registration.

**Details**

- Described in the hook config as "After a working directory is added mid-session"; fires after the directory is registered and after the sandbox config is refreshed.
- Hook input JSON carries the absolute `directory` and a `source`; the matcher can match on `"slash_command"` for /add-dir and `"register_repo_root"` for the SDK control request.
- Registered in the hook config schema, the hook registry, the SDK hook-event enum and the /hooks UI.
- Failure handling differs by path: for /add-dir a failure count is summarised to Claude and hook `systemMessage` output reaches Claude; for `register_repo_root` failures are debug-logged only, via "DirectoryAdded hook failed: ".

**Evidence**

`"slash_command" for /add-dir, "register_repo_root" for the SDK control_request.`, `After a working directory is added mid-session`

- Area: Hooks
- Names: `DirectoryAdded`, `/add-dir`, `/hooks`, `slash_command`, `register_repo_root`
- Tier: Use it now
- Useful: 4/5
- Signal: 0/5

### New sandbox setting: network.strictAllowlist

Set strictAllowlist to have the sandbox flat-out deny hosts outside your allowed domains instead of prompting.

**What**

A `strictAllowlist` boolean on sandbox network settings makes the sandbox runtime deny hosts outside `allowedDomains` outright instead of prompting. The runtime check existed before but there was no way to turn it on.

**Details**

- Added to the sandbox network settings schema and propagated through settings merging (`if (o.strictAllowlist === !0) l.strictAllowlist = !0;`), derived from any of user, managed or CLI settings
- The runtime permission check is `if (!r || Hl.network.strictAllowlist)`; a host matching no rule is denied rather than prompted when this is on
- Honoured only from user, managed/policy or `--settings` sources; per its own description project settings (`.claude/settings.json` and `.claude/settings.local.json`) are ignored
- Described as "Enforced for sandboxed commands only"; in-process tools such as WebFetch are not gated by it
- Absent by default, so behaviour is unchanged unless configured

**Usage**

`{"sandbox":{"network":{"allowedDomains":["api.example.com"],"strictAllowlist":true}}}` **Evidence** `the sandbox runtime deterministically denies hosts not in allowedDomains instead of prompting`, `When true, the sandbox runtime deterministically denies hosts not in allowedDomains instead of prompting. `

- Area: Sandbox
- Names: `sandbox.network.strictAllowlist`, `allowedDomains`, `--settings`
- Tier: Use it now
- Useful: 4/5
- Signal: 0/5

### SDK init message reports skipped MCP server configs

SDK callers get an mcp_server_errors array naming rejected MCP configs, so CI can fail on it.

**What**

The `system`/`init` SDK message now carries `mcp_server_errors`, so a programmatic caller can see which MCP config entries were rejected at validation time rather than silently getting fewer servers.

**Details**

- each element is `{name, type, message}` for a config entry that failed validation and was skipped
- entries whose server did connect anyway are filtered out of the array
- the MCP config loader tags validation failures with a stable code, adding `"invalid_config"` alongside the existing categories
- the schema note says the key is omitted when empty, and that "CI can fail on `(mcp_server_errors?.length ?? 0) > 0`"
- the REPL bridge currently passes an empty list, so this is populated on the SDK path only

**Evidence**

`mcp_server_errors`

- Area: SDK
- Names: `mcp_server_errors`
- Tier: Use it now
- Useful: 4/5
- Signal: 0/5

### SDK interrupt can now cancel everything queued in one round-trip

Send interrupt with cancel_queued true to stop the current turn and everything queued behind it.

**What**

The interrupt control_request takes a new optional boolean `cancel_queued` that sweeps the whole main-thread queue along with the abort, so a remote UI can offer a "Stop means stop everything" button without walking uuids one at a time.

**Details**

- When true, every uuid-stamped main-thread command still queued, and any already dequeued for the imminent turn, is cancelled alongside the abort and emits a terminal 'cancelled' lifecycle
- The response returns those uuids under a new `cancelled` array, with `still_queued` always empty
- A wrapper that wants per-uuid control leaves the flag false and keeps using cancel_async_message
- Absent or false preserves the existing interrupt_receipt_v1 behaviour exactly

**Usage**

`{"type":"control_request","request":{"subtype":"interrupt","cancel_queued":true}}` **Evidence** `When true, the interrupt also cancels every uuid-stamped main-thread command still in the queue`

- Area: SDK Protocol
- Names: `cancel_queued`, `interrupt`, `cancel_async_message`
- Tier: Use it now
- Useful: 4/5
- Signal: 0/5

### Workflow size guideline now defaults to medium instead of unrestricted

Without configuration, sessions now advise fewer than 15 agents per workflow; change it in /config.

**What**

With no workflow size guideline configured, sessions now get a medium guideline advising fewer than 15 agents per workflow, where previously an unset value meant no guideline text at all.

**Details**

- unset resolves to "medium" flagged as a default, injecting the advisory plus a pointer to /config
- a value in a settings file, including managed settings, takes precedence and hides the /config row entirely
- the settings schema description documenting this precedence is new in this build

**Usage**

`/config` **Evidence** `This session has the default workflow size guideline:`

- Area: Settings
- Names: `/config`, `workflowSizeGuideline`
- Tier: Use it now
- Useful: 4/5
- Signal: 0/5

### interrupt control request can now cancel the whole queue (cancel_queued)

An interrupt can now clear your whole queued command backlog at once and report what it cancelled.

**What**

The control-protocol `interrupt` request accepts an optional `cancel_queued` boolean that clears queued main-thread commands along with the in-flight turn, and reports which uuids it killed.

**Details**

- When `cancel_queued === !0`, every uuid-stamped main-thread command still queued, including ones dequeued for the imminent turn but not yet reachable by the abort, gets a terminal `cancelled` lifecycle event.
- The response comes back with `still_queued: []` and the cancelled uuids on a new optional `cancelled` field.
- Without the flag, the previous still_queued behaviour is unchanged.
- The capability string `interrupt_cancel_queued_v1` is advertised on system/init next to `interrupt_receipt_v1` and `msg_lifecycle_v1`, so clients can feature-detect it. None of these strings exist in v2.1.218.
- The in-process SDK client's `interrupt()` wrapper does not send the flag, so this is for control-protocol consumers only.

**Evidence**

`interrupt_cancel_queued_v1`

- Area: SDK Protocol
- Names: `cancel_queued`, `interrupt_cancel_queued_v1`
- Tier: Use it now
- Useful: 4/5
- Signal: 0/5

### register_repo_root accepts launch-time directories and rejects duplicates

You can register repo roots under any directory passed with --add-dir, and duplicates now say so clearly.

**What**

The SDK register_repo_root request no longer requires the target to be a strict subdirectory of cwd, and now reports an already-registered directory as its own error instead of a subdirectory failure.

**Details**

- the path is realpathed and confirmed to be a directory before anything else
- accepted locations are directories under cwd or under any directory passed at launch via cliArg sources, that is `--add-dir` or the SDK `additionalDirectories`
- registering a directory that is already a working directory fails with a dedicated error
- on success, DirectoryAdded hooks are dispatched; hook failures are debug-logged and do not fail the request
- the request schema description was rewritten to match
- previously this threw `register_repo_root: ... is not a subdirectory of cwd`

**Evidence**

`register_repo_root: directory is already a registered working directory`

- Area: SDK
- Names: `--add-dir`, `additionalDirectories`, `register_repo_root`
- Tier: Use it now
- Useful: 4/5
- Signal: 0/5

### Credentials scrubbed from summarized error text

Tokens and passwords caught in error text are now redacted before you or your logs see them.

**What**

Short error and detail summaries are run through a redactor before display, so tokens pasted into error strings do not leak into output or logs.

**Details**

- Rewrites bearer and basic values, and token/key/secret/password/authorization/credential assignments of 8 or more characters, to "[redacted]".
- Runs after a byte-safe truncation of the summary.
- Unconditional; no gate.

**Evidence**

`/\b(bearer|basic)[\s:=\uFF1A\uFF1D]+([A-Za-z0-9._~+/=%-]{8,})/gi`

- Area: Error Handling
- Tier: You'll notice
- Useful: 4/5
- Signal: 0/5

### Headless text output recovers the last full assistant message on an incomplete run

In headless text runs, an incomplete ending no longer swallows the last full answer.

**What**

In `-p` runs that print plain text, a run that ends incomplete no longer discards the last complete assistant message.

**Details**

- Applies when the output format is neither `json` nor `stream-json`; the last and prior top-level assistant texts are tracked.
- If the final result is a success-but-error whose text ends with "The response above may be incomplete." and equals the last assistant text, the prior assistant text is printed above the result.
- Compact boundaries and superseded messages reset the tracker.

**Evidence**

`The response above may be incomplete.`

- Area: Headless Mode
- Names: `-p`
- Tier: You'll notice
- Useful: 4/5
- Signal: 0/5

### Left-arrow backgrounding refuses to eat an unsent draft

Left-arrow no longer backgrounds agents while you have unsent text, so drafts survive.

**What**

Pressing left-arrow to open or background agents is blocked while there is unsent text in the input box, so a draft is no longer lost to the shortcut.

**Details**

- the block shows an explanatory message telling you to send or clear the text, noting that double-tapping esc clears it
- it emits `tengu_left_arrow_blocked` with reason `draft`
- the editor disarms the pending left-arrow confirmation on any non-left keypress and when history up or down is used

**Evidence**

`you have unsent text in the input. Send it or clear it first (double-tap esc clears)`

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

### MCP connection failures can carry an extra display detail

Failed MCP connections now tell you why, with the error detail shown in list and get output.

**What**

Failed MCP connections can now attach a `displayDetail` explaining why, and the CLI surfaces it instead of just showing a bare status.

**Details**

- `displayDetail` is optional, threaded through the failure constructor and appended to the user-visible error.
- The Authorization-header rejection path attaches `Error detail:` plus the server response body when that body is non-empty.
- `claude mcp list` builds its status line as the status, an em dash and the issue; `claude mcp get` prints an `Issue:` line.
- The "! Connected · tools fetch failed" status now carries the underlying error as its issue.

**Evidence**

`displayDetail`

- Area: MCP
- Names: `claude mcp list`, `claude mcp get`
- Tier: You'll notice
- Useful: 4/5
- Signal: 0/5

### MCP servers skipped by --mcp-config are now reported

MCP servers rejected from --mcp-config are now listed on stderr and in the init message instead of vanishing.

**What**

Servers passed via `--mcp-config` that fail validation are no longer silently absent; they are collected, surfaced on the SDK init message, and printed to stderr on a TTY.

**Details**

- Validation warnings carrying `mcpErrorMetadata.skipReason` and a server name are collected, and servers that ended up unregistered are recorded
- A new optional `mcp_server_errors` array on the system/init payload lists each skipped entry with name, a stable `type` category (unknown_type, url_missing_type, invalid_config, reserved_name) and a message, filtered to servers with no live client
- On a TTY the same list is printed to stderr as a control-character-scrubbed warning
- Affected servers no longer appear only as gaps in `mcp_servers[]`, so CI can fail on a non-empty list
- Also new on init: `fast_mode_disabled_reason`
- Neither the collection nor the init-message field existed in 2.1.218

**Evidence**

`MCP server config entries from --mcp-config that failed validation and were skipped`, `skipped due to invalid config`

- Area: MCP
- Names: `--mcp-config`, `mcp_server_errors`
- Tier: You'll notice
- Useful: 4/5
- Signal: 0/5

### Memory sync no longer suppresses pushes for the whole session

One failed memory sync no longer silently stops all memory pushes for the rest of your session.

**What**

A single memory push failure used to silently stop all further debounced pushes and change notifications for the rest of the session; suppression now applies only to individual stores.

**Details**

- The per-scope memory watcher state dropped its `pushSuppressedReason` field, along with the session-wide suppression it drove (logged as "suppressing retry for the rest of this session")
- The two early-returns keyed on that field are gone, and change events now always re-arm the debounce timer
- Per-store suppression remains, carrying a `suppressedUntilMs` expiry, and the "unlink cleared store suppression for" recovery path is unchanged

**Evidence**

`memory-watcher[`, `pushSuppressedReason`

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

### Opus 5 gets an extra system-prompt instruction restricting AgentTool, workflows and deep-research

On Opus 5, Claude is told not to spawn subagents, workflows or deep research unless you ask.

**What**

Models carrying the opus-5 prompt bundle now receive a built-in instruction telling Claude not to spawn subagents or run workflows and deep-research unless the user asked for it.

**Details**

- the `tengu_heron_brook` injection previously came only from client data or the remote flag string; it now falls back to a built-in two-line block
- the fallback applies when the model carries the opus_5_prompt_bundle capability
- killswitch is `tengu_fennel_godwit`
- the applied event still fires as `tengu_heron_brook_applied` with `fromClientData: !1` on this path

**Evidence**

`Do not use workflows or deep-research unless the user requested it`

- Flag `tengu_heron_brook`: Not enough to say (read for one account on one subscription tier against v2.1.219; this account: no value returned, anonymous baseline: no value returned, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Flag `tengu_fennel_godwit`: Off in both readings (read for one account on one subscription tier against v2.1.219; this account: off, anonymous baseline: off, compiled default: not a boolean we can read) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: System Prompt
- Tier: You'll notice
- Useful: 4/5
- Signal: 0/5

### Interaction schemas are now discovered, not hardcoded

The artifact tool's schema option now lists whatever schemas your session actually registered.

**What**

The artifact tool's `schema` argument stops naming "workshop-decisions" in its description and error text and instead reports whatever the session actually registered.

**Details**

- The available schema list is computed per session and the example in the description is taken from it
- Requesting an unregistered schema returns a distinct errorCode 8 that lists the available schemas
- The `schema` argument only exists when the session registers at least one interaction schema

**Evidence**

`is not available in this session. Available schemas: `

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

### New setting: workflowSizeGuideline

Cap how big Claude's dynamic workflows get with a new workflowSizeGuideline setting.

**What**

A settings key that gives Claude an advisory size hint for the dynamic workflows it writes, with four accepted values.

**Details**

- Accepts "unrestricted", "small", "medium" or "large": small is under 5 agents, medium (the default) under 15, large under 50
- A settings-file or managed value takes precedence over the "Dynamic workflow size" row in `/config`, and that row is hidden entirely while a settings file supplies the key
- The `/config` row now also shows whether the current value is the default
- The `/config` row appears only when the workflows feature is on

**Usage**

`{"workflowSizeGuideline":"small"}` **Evidence** `Advisory size guideline for the dynamic workflows Claude writes`

- Area: Workflows
- Names: `workflowSizeGuideline`, `/config`
- Tier: Use it now
- Useful: 3/5
- Signal: 0/5

### SDK reports why fast mode is unavailable

SDK messages now tell you exactly why fast mode was unavailable for a request.

**What**

An optional `fast_mode_disabled_reason` field now travels on SDK init and result messages, so a client can tell why a request could not use fast mode rather than guessing.

**Details**

- Added to the init message and to both the success and error result message schemas.
- Populated on every result path, including `"tool_deferred_unavailable"`, `"budget_exhausted"` and `"turn_setup_failed"`.
- Enum values: `free`, `preference`, `extra_usage_disabled`, `network_error`, `unknown`, `not_first_party`, `disabled_by_env`, `model_not_allowed`, `sdk_opt_in_required`, `pending`.
- A companion Set of the first five values normalizes server-supplied reasons: unrecognized values map to `unknown`, null maps to `preference`.
- The field is absent when nothing blocks fast mode; a request may still choose standard speed.

**Evidence**

`Why fast mode can't serve right now. Absent when nothing blocks it (a request may still choose standard speed).`

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

### Skill listing tracks a modified timestamp from frontmatter

You can add a modified date in skill frontmatter to influence which skills get pinned.

**What**

Skill and memory file listings read an explicit modification time from frontmatter, which feeds the recency ordering used when picking pinned candidates.

**Details**

- A `modified` frontmatter field is parsed into `modifiedMs`.
- When the field is missing or unparseable, the file mtime is used instead.

**Evidence**

`modifiedMs`

- Area: Skills
- Names: `modified`
- Tier: Use it now
- Useful: 3/5
- Signal: 0/5

### Slash-command hook results can carry a system message

Hooks on slash commands can now show you a system message, not just output and pass/fail.

**What**

A hook attached to slash-command execution can now surface a system-level note to the user rather than only output and a pass/fail signal.

**Details**

- The command execution result propagates `systemMessage` from the hook's output alongside the existing `output`, `succeeded` and `blocked`.

**Evidence**

`systemMessage`

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

### Subagent-delegation nudges can now be suppressed by the model itself

Subagent delegation nudges are now switched off for Opus 5; you can also control them by env var.

**What**

The prompt block that nudges Claude to delegate to subagents can be turned off based on the model, not just by env var, client data or remote config.

**Details**

- resolution order was env `CLAUDE_CODE_THISTLE_GREBE`, then client data, then growthbook; a fourth model-derived resolver was added
- the model resolver returns `no_nudges` for models carrying the opus_5_prompt_bundle capability, suppressing the "## Delegating to subagents" block
- telemetry reports `source: "model"` when this path decides

**Evidence**

`tengu_subagent_steer_applied`

- Area: System Prompt
- Names: `CLAUDE_CODE_THISTLE_GREBE`
- Tier: Use it now
- Useful: 3/5
- Signal: 0/5

### Two new API betas documented: scalar `fallbacks: "default"` and mid-conversation tool changes

Docs now describe two beta headers for automatic refusal fallbacks and swapping tools mid-conversation.

**What**

The skill documents two beta headers that did not appear in the previous build's docs.

**Details**

- `fallbacks: "default"` sits behind `server-side-fallback-2026-07-01` and lets the server pick the fallback by refusal category; the array form stays on `server-side-fallback-2026-06-01`, and pairing either header with the other form returns 400.
- A new tool-use-concepts section covers `mid-conversation-tool-changes-2026-07-01`: `tool_addition` and `tool_removal` blocks on a `role: "system"` message change the tool set between turns without invalidating the prompt cache.
- Tools involved must be declared with `"defer_loading": True`.

**Evidence**

`mid-conversation-tool-changes-2026-07-01`

- Area: Skills
- Names: `server-side-fallback-2026-07-01`, `mid-conversation-tool-changes-2026-07-01`, `defer_loading`
- Tier: Use it now
- Useful: 3/5
- Signal: 0/5

### Workflow size guideline gets a default and a policy override

Workflow size now defaults to medium, and an admin policy setting overrides whatever you chose.

**What**

The workflow size guideline now resolves to `medium` when nothing is configured, and a value set in policy settings takes precedence over the user's own setting.

**Details**

- The resolver returns `{ size, isDefault }`.
- Prompts distinguish the two cases: "This session has the default workflow size guideline:" versus "A workflow size guideline is configured for this session:".
- The /config pointer is only shown when the value is the default.
- The unrestricted notice was reworded to "Workflow size is now unrestricted — no size guideline applies." and no longer attributes the change to the user.

**Evidence**

`Workflow size is now unrestricted — no size guideline applies.`

- Area: Configuration
- Names: `/config`
- Tier: Use it now
- Useful: 3/5
- Signal: 0/5

### MCP policy URL expansion fails closed on wildcard-injecting env values

MCP policy URL allowlists now refuse environment variables that sneak wildcards in and widen a match.

**What**

Expanding `${VAR}` inside MCP policy URL predicates now detects environment values that smuggle in wildcard semantics and refuses to treat the resulting predicate as safe.

**Details**

- The expander gains a `wildcardVars` output listing offending variables.
- A value counts as a wildcard if it contains `*` after NFKC normalization, matches `%2a` case-insensitively, or decodes to a `*` after percent-decoding.
- A predicate whose expansion pulled in any such variable is marked unsafe alongside the existing host, hash and query checks.
- Allowlist URL entries using such an expansion fail closed; denylist entries are unaffected.
- The expander also gained a fallback env map so policy-settings env can supply variables missing from the process env.
- Neither `wildcardVars` nor the check existed in the previous build; this closes a route to widening a policy match through an environment variable.

**Evidence**

`wildcardVars`, `a value injected wildcard semantics`

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

### Opus 5 wired through region env vars, refusal fallbacks and pricing copy

An Opus 5 model is wired through region variables, fast mode, 1M context and pricing text.

**What**

Supporting plumbing for a new Opus 5 model lands across region selection, effort/speed handling, context eligibility and billing copy.

**Details**

- `VERTEX_REGION_CLAUDE_5_OPUS` joins the recognized Vertex region environment variables.
- The effort/speed table treats `claude-opus-5` like `claude-opus-4-8` for fast mode.
- 1M-context eligibility now matches on `claude-opus-5`, and the internal model-family list gains `"opus5"` ahead of `"opus48"`.
- The upsell/pricing copy branch keys on `claude-opus-5` instead of `claude-opus-4-8`.
- The system-prompt blurb drops Opus 4.8 and now reads "The most recent Claude models are the Claude 5 family and Haiku 4.5."
- Some Mythos-era strings were deleted, including one reading "This model's safeguards flagged this message. This sometimes happens with safe, normal conversations."

**Evidence**

`VERTEX_REGION_CLAUDE_5_OPUS`

- Area: Models
- Names: `VERTEX_REGION_CLAUDE_5_OPUS`
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Effort profile added for claude-opus-5

A reasoning-effort table now includes settings for a model named claude-opus-5, with new medium and high profiles.

**What**

The per-model reasoning-effort table gains a row for claude-opus-5, so effort levels map to model-specific settings when that model is in effect.

**Details**

- low reuses the shared low profile; max reuses the shared max
- medium and high both map to a new cell "o5-bmin" with `modelEffort: "typed"` and no finder budget hint
- xhigh reuses the existing o48-xhigh-v1 profile
- the cell name "o5-bmin" does not appear in 2.1.218, and there is a downstream branch that special-cases it

**Evidence**

`o5-bmin`

- Area: Model Config
- Tier: Under the hood
- Useful: 3/5
- Signal: 0/5

## New Features

### `register_repo_root` accepts launch-time roots and rejects duplicates

Registering a repo root now accepts launch-time --add-dir roots and clearly rejects duplicates or bad paths.

**What**

The SDK control request for registering a repo root now accepts more legitimate targets and fails loudly on cases it previously handled silently or not at all.

**Details**

- Previously the target only had to be a subdirectory of cwd; it now also accepts subdirectories of directories registered at launch via `--add-dir`.
- Rejects cwd itself with a dedicated message, rejects paths that are not directories, and rejects an already-registered directory instead of silently re-running registration.
- Rejection message names both accepted roots: "is not a subdirectory of cwd or of a launch-time --add-dir root".
- The tool description now states that the registration pipeline and DirectoryAdded hooks do not re-run on a duplicate.

**Evidence**

`is not a subdirectory of cwd or of a launch-time --add-dir root`

- Area: SDK Protocol
- Names: `--add-dir`, `register_repo_root`
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Bundled Claude API skill docs rewritten around Opus 5

The bundled Claude API skill docs now target the new Opus, covering thinking defaults, fallbacks and fast mode.

**What**

The vendored `claude-api` skill documents were retargeted from a hardcoded model ID to placeholders resolving to the new Opus, with new guidance on thinking defaults, refusal fallbacks and fast mode.

**Details**

- Python, Go and Ruby guides, the error-code reference, the managed-agents reference and the top-level skill all replace `claude-opus-4-8` with `{{OPUS_NAME}}`/`{{OPUS_ID}}`, and add a `{{PREV_OPUS_NAME}}`/`{{PREV_OPUS_ID}}` row.
- On the new Opus, thinking is on by default, so omitting `thinking` runs adaptive; `{type: "disabled"}` is accepted only at effort `high` or lower and returns 400 at `xhigh` or `max`.
- A new pitfall section covers the two failure modes of disabling thinking.
- Refusal fallbacks gain a scalar form `fallbacks: "default"` under beta header `server-side-fallback-2026-07-01`; the array form keeps `-2026-06-01` and mixing the two returns 400.
- Fast mode is documented as new-Opus/4.8 only and Claude API only, with Opus 4.7 fast mode removed rather than deprecated, plus a note that Priority Tier does not cover the new Opus.
- Mid-conversation system messages are documented for several models rather than one.

**Evidence**

`server-side-fallback-2026-07-01`

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

### New "Migrating to {{OPUS_NAME}}" migration guide with two breaking changes

A migration guide now spells out the two thinking-related changes that break existing API request code.

**What**

shared/model-migration.md gains a section for the new Opus covering the two behaviours that break existing request code.

**Details**

- Breaking change 1: thinking is on by default when `thinking` is omitted, a silent cost and truncation change measured against `max_tokens`.
- Breaking change 2: `thinking: {type: "disabled"}` is accepted only at effort `high` or lower; pairing it with `xhigh` or `max` returns 400, validated per request.
- Documents disabled-thinking failure modes: tool calls emitted as plain text, and `<thinking>` tag leakage.
- Also notes a 512-token cache minimum, a separate rate-limit bucket, and adds a [BLOCKS]/[TUNE] checklist.
- Older Opus rows in the migration table now all target the new ID.

**Evidence**

`### Breaking change 2: disabling thinking is capped at`

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

### Workshop artifacts get a parse and serialize fixed-point check at publish time

Workshop artifacts that would silently mutate on republish are now blocked at publish time.

**What**

Pages the verifier classifies as a workshop surface are re-parsed and re-serialized before publishing, and the publish is refused if the round trip changes anything. This catches artifacts that would silently mutate on every decision confirm, since each confirm republishes the page.

**Details**

- two checks run: the round-tripped page must still pass the strict contract, and a second round trip must produce byte-identical output
- two new violation rules, `reparse-divergence` and `reparse-not-fixed-point`, block the publish and point at the first divergence
- only reached for pages that verify as a workshop surface; other pages are unaffected

**Evidence**

`reparse-not-fixed-point`

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

### `opus5` in the local model listing

The local model listing endpoint now includes Opus 5 among its available keys.

**What**

Opus 5 now appears in the model enumeration served by the built-in SDK/proxy model-listing endpoint.

**Details**

- The list of model keys now includes `"opus5"` alongside `"opus48"` and `"fable5"`.

**Evidence**

`"opus5"`

- Area: Models
- Names: `opus5`
- Tier: Under the hood
- Useful: 2/5
- Signal: 0/5

### The API skill's model placeholders now resolve to Claude Opus 5, with the previous Opus kept as a separate placeholder

The API skill's model placeholders now render Opus 5, keeping the previous Opus named separately.

**What**

The substitution map that renders the bundled `claude-api` skill docs moved `OPUS_ID`/`OPUS_NAME` onto Claude Opus 5 and added two keys for the outgoing model.

**Details**

- Every doc writing `model="{{OPUS_ID}}"` now emits the new ID, as do the deprecation targets and the Bedrock ID table.
- The alias table keeps `opus` and adds an `opus 5` row.
- `PREV_OPUS_ID`/`PREV_OPUS_NAME` carry claude-opus-4-8 so migration text can name both.
- The `/claude-api` skill is registered with no isEnabled gate, so this is what users of that skill get on this build.

**Evidence**

`OPUS_ID: "claude-opus-5"`

- Area: Skills
- Names: `/claude-api`
- Tier: You'll notice
- Useful: 2/5
- Signal: 0/5

### New protocol capability interrupt_cancel_queued_v1 on system/init

Sessions now advertise a capability string so clients can detect queue-cancelling interrupts without version checks.

**What**

The `capabilities` array advertised at init gains a second entry next to interrupt_receipt_v1, letting SDK consumers feature-detect cancel_queued support instead of sniffing the CLI version.

**Details**

- Capability string is `interrupt_cancel_queued_v1`
- The capability list is built unconditionally in this build, so it is present for every session
- Pairs with the `cancel_queued` field on the interrupt control_request

**Evidence**

`'interrupt_cancel_queued_v1' = the interrupt control_request honors cancel_queued:true`

- Area: SDK Protocol
- Names: `interrupt_cancel_queued_v1`
- Tier: Under the hood
- Useful: 2/5
- Signal: 0/5

### Cyber Verification Program copy and links

Safety interruptions now show dedicated wording and a link about the Cyber Verification Program.

**What**

Cyber-safeguard interruptions now have their own user-facing text, pointing at the Cyber Verification Program as a way to reduce how often they happen. A new support article URL is used when the flagged model is Opus 5.

**Details**

- new strings cover the cyber-category refusal case specifically, separate from the generic safeguard message
- the dedicated support link is selected on the Opus 5 path

**Evidence**

`Apply to the Cyber Verification Program to reduce these interruptions.`

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

### Upgrade links now carry a per-surface UTM campaign

Upgrade links now include tracking parameters so each entry point is attributable.

**What**

Every link into the Max upgrade flow is now built with source, medium and campaign query parameters instead of pointing at the bare upgrade URL, so each entry point is attributable.

**Details**

- The flow builds `https://claude.ai/upgrade/max?utm_source=claude_code&utm_medium=cli&utm_campaign=<surface>`.
- Surfaces in use: `pro_trial_expired_dialog`, `rate_limit_options_menu`, and `upgrade_command` from the `/upgrade` command.
- The pro-trial-expired dialog was also switched to the same shared entry point the rate-limit options menu already used.
- Active for everyone; no gate.

**Evidence**

`https://claude.ai/upgrade/max?utm_source=claude_code&utm_medium=cli&utm_campaign=`

- Area: Elsewhere
- Names: `/upgrade`
- Tier: Under the hood
- Useful: 1/5
- Signal: 0/5

## Improvements

### Org-disabled fast mode notification now states the actual reason

The startup notice about Fast mode being off now names the actual reason instead of a generic line.

**What**

The startup notice about Fast mode being turned off no longer prints a generic organization message; it prints the specific sentence derived from the reason code.

**Details**

- The old text "Fast mode has been disabled by your organization" is gone.
- The notification is skipped entirely when no reason resolves.
- It fires only when the disabled state came from the server, not from a local guess.

**Evidence**

`Fast mode has been disabled by your organization`

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

### Remote Control now explains exactly which endpoint setting blocks it

When Remote Control refuses, it now names the exact setting or variable blocking it.

**What**

The one-line refusal about Remote Control needing api.anthropic.com is now followed by a diagnosis of the specific configuration in play.

**Details**

- Separate sentences for gateway sessions, Bedrock/mantle, and a custom `ANTHROPIC_BASE_URL`, each naming the environment variable to unset.
- Explicitly notes that the assume-first-party escape hatch does not lift the restriction.

**Evidence**

`_CLAUDE_CODE_ASSUME_FIRST_PARTY_BASE_URL does not apply to Remote Control.`

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

### MCP allow/deny rules compare expanded env-var values

Your MCP allow and deny rules now match servers whose commands or URLs use environment variables.

**What**

Matching a server against `allowedMcpServers` and `deniedMcpServers` now expands environment-variable references in the configured command and URL before comparing, so rules match what actually runs rather than the literal template.

**Details**

- The deny path uses a fallback env when expansion needs one.
- An allow-rule URL whose expansion is flagged `unsafeExpansion` is skipped rather than treated as a match.
- A separate new validator flags untrimmed whitespace in MCP config `command`, `url`, `args`, and env/header names and values.

**Evidence**

`unsafeExpansion`

- Area: MCP
- Names: `allowedMcpServers`, `deniedMcpServers`
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Post-compaction file restore skips memory files

After compaction, Claude stops wasting its re-read budget on memory files it already injects.

**What**

After compaction, the routine that re-reads recently touched files no longer spends its budget re-reading CLAUDE.md-family memory content that is injected anyway.

**Details**

- Builds a set of canonical memory-file paths for every scope in `["User", "Project", "Local", "Managed", "AutoMem"]` and filters them out, alongside the agent's own memory file.
- A new optional parameter also excludes non-truncated pinned memory-directory entries; that exclusion applies only when the caller opts in and the pinned-memory precondition holds.
- Partial compaction (`compact_partial`) now also passes the message list so the plan-mode attachment is regenerated, and opts into the pinned-file exclusion.

**Evidence**

`tengu_post_compact_file_restore_success`, `AutoMem`

- Area: Compaction
- Names: `CLAUDE.md`
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Memory recall skips the current session's own logs

Memory recall no longer feeds a session its own transcript notes back to itself.

**What**

Candidate memory files belonging to the running session are now filtered out, so a session cannot recall its own transcript notes back into itself.

**Details**

- A predicate passed into memory recall rejects any candidate under `logs/` whose basename is `<id>.md` or starts with `<id>-`, where `<id>` is the first 8 characters of the current session identifier.
- Built defensively: returns undefined if the session id cannot be read, in which case no filtering is applied.

**Evidence**

`if (!r.startsWith("logs/")) return !1;`

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

### Prompt-caching doc rewrites the minimum-cacheable-prompt table and adds cache-preserving escape hatches

Caching docs now list per-model minimums and which request changes keep your cache alive.

**What**

The per-model minimum table was regrouped and a new table lists which top-level request changes can be made without dropping the cache.

**Details**

- Minimums run from 512 tokens for the newest models up to 4096 for Opus 4.6/4.5 and Haiku 4.5, with an explicit note that the value is not monotonic across generations.
- The old Amazon Bedrock override is documented as removed.
- Cache-preserving forms: tool definitions via `tool_addition`/`tool_removal` (beta), and system-prompt content via a `role: "system"` message with no beta header.
- Model switch has no escape hatch.
- Mid-conversation system-message availability widens from one model to four, with a caveat that sources conflict on Sonnet.

**Evidence**

`**The minimum is not monotonic across generations**`

- Area: Prompt Caching
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Code-review prompt tuned for Opus 5

Code reviews on Opus 5 use a tighter prompt: one diff pass, at most fifteen findings.

**What**

A new minimal review prompt is selected for both the medium and high review levels when the review model is claude-opus-5.

**Details**

- Prompt cell is named `o5-bmin`
- Instructs a single careful diff pass over `git diff @{upstream}...HEAD`, falling back to main...HEAD then HEAD~1, plus `git diff HEAD` for uncommitted work
- Caps output at 15 findings, submitted through the review tool and restated one per line in the reply
- Not used for other review models

**Evidence**

`minimal prompt \u2192 single careful diff pass \u2192 \u226415 findings`

- Area: Code Review
- Names: `git diff @{upstream}...HEAD`
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Republishing an artifact page now verifies it came from this CLI build

Republishing an artifact page fails if it was built by a different CLI version; re-run the command.

**What**

Republish checks the stored page against this build's template and refuses when they have drifted, instead of pushing a page assembled by a different CLI version.

**Details**

- Pinned markup fragments, script hashes, an inline blind-spots style allowlist and the diagram runtime must all match
- On mismatch the republish fails with `pr_review_republish_template_drift` and advises re-running `/artifact-pr-review`
- A separate new failure, `pr_review_republish_mermaid_unavailable`, covers a page carrying a diagram runtime this CLI could not load

**Evidence**

`pr_review_republish_template_drift`

- Area: Artifacts
- Names: `/artifact-pr-review`
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Left-arrow session exit now blocks on unsent input

Pressing left-arrow to leave a session now warns you when unsent text is still sitting in the input box.

**What**

The confirmation guarding a session exit, which previously fired only for queued commands or background tasks, now also fires when the input box still holds text you have not sent.

**Details**

- The prompt tells you to send or clear the input first
- Queued-command and background-task cases are unchanged

**Evidence**

`you have unsent text in the input`

- Area: Session Exit
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Teleport branch-mismatch error explains what the checkout actually is

Teleport now tells you whether the repo genuinely mismatched or the git remote just couldn't be read.

**What**

The "You must run claude --teleport ... from a checkout of ..." error now tells you why the current directory did not match, separating a genuine repo mismatch from a git remote it could not parse.

**Details**

- repo mismatch shows the session's repo plus a "This repo is ..." line
- an unparseable remote reports that case explicitly instead of implying a mismatch
- the session repo is qualified with its host when the host is not the default

**Evidence**

`Couldn't parse your git remote: `

- Area: Teleport
- Names: `claude --teleport`
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Model-downgrade chain and effort tables know about Opus 5

Model fallbacks and reasoning-effort settings now handle Opus 5, stepping down to Opus 4.8 when needed.

**What**

The stepwise "older model" resolver and the reasoning-effort tables were extended for Opus 5, so downgrades and effort selection land on the right model.

**Details**

- an Opus 5 name now steps down to Opus 4.8
- a `fable-5` name resolves to Opus 5 instead of Opus 4.8
- the reasoning-effort configuration table gained a `claude-opus-5` entry
- the set of models on the special effort path now contains both `claude-opus-4-8` and `claude-opus-5`

**Evidence**

`if (t.includes("opus-5") || t.includes("opus_5"))`

- Area: Models
- Names: `claude-opus-5`
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### MCP config warns about stray whitespace in values

MCP config now warns when a value has stray leading or trailing whitespace instead of silently using it.

**What**

Loading MCP server entries now warns when a value has leading or trailing whitespace, naming the fields involved, because the values are used exactly as written.

**Details**

- the warning tells you to strip the whitespace rather than doing it for you
- reserved-name rejections now carry a machine-readable `reserved_name` code alongside the existing message

**Evidence**

`Leading or trailing whitespace in:`

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

### MCP error text is sanitized and credential-redacted before display

MCP error messages now hide credentials and strip weird characters before showing them to you.

**What**

Server-supplied error text reaching MCP UI surfaces is now normalized and scrubbed before it is interpolated into user-visible strings, and an invalid URL is no longer echoed back.

**Details**

- Reconnect failures pass the tools-list error through a sanitizer (NFKC normalize, strip angle brackets and quote characters, collapse whitespace, truncate with an ellipsis) before interpolation into "Reconnected to ..., but fetching tools failed: ...".
- The same sanitizer applies to a caught error message in the artifact/QR surface, and to messages embedded in `Error reconnecting to ` and `Failed to reconnect to `.
- Bearer, api-key and password-looking values are rewritten to `[redacted]`.
- MCP conflict notices in the transcript run their `message` and `suggestion` through a control-character stripper.
- Schema-validation error formatting for MCP goes through the same path.
- An invalid MCP `url` no longer echoes the URL back; the message is now a fixed string.

**Evidence**

`'url' is not a valid URL. Update the server's config and reconnect.`, `Error reconnecting to `

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

### Model picker drops the "Requires usage credits" suffix and the manual 1M relabel

The model picker now shows labels straight from the model data, without extra credit or context suffixes.

**What**

Entries in the model list are shown as the model data defines them, without two hardcoded rewrites applied at list-build time.

**Details**

- `" · Requires usage credits"` is no longer appended to the affected entry.
- The `opus[1m]` label is no longer rewritten to "Opus (1M context)"; that label now comes from the entry itself.
- The highlighted-name pass in the model description recolours "Opus 5" instead of "Fable 5", "Mythos 5" and "Sonnet 5".

**Evidence**

`Opus 5`

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

### "Dynamic workflow size" hidden when a policy pins it

The workflow size setting disappears from config when your organization has already fixed it.

**What**

The workflow size setting no longer appears in config when a managed or flag settings source already defines it, so you cannot change a value your organization has fixed.

**Details**

- New computed field `workflowSizeGuidelineToggleable` is the negation of "a managed/flag settings source defines workflowSizeGuideline"
- The `workflowSizeGuideline` enum row (label "Dynamic workflow size", options unrestricted/small/medium/large) is listed only when that is true
- Previously the row appeared whenever workflows were toggleable

**Evidence**

`workflowSizeGuidelineToggleable`

- Area: Settings
- Names: `/config`, `workflowSizeGuideline`
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Fast mode unavailability reworked into machine-readable reason codes

Fast mode unavailability is tracked as reason codes internally, feeding clearer messages and telemetry.

**What**

The check for why Fast mode is unavailable now returns a code rather than an English sentence, with a separate function that turns the code into the user-facing text.

**Details**

- Codes include `not_first_party`, `disabled_by_env`, `model_not_allowed`, `sdk_opt_in_required`, `pending`, plus server-supplied reasons such as `extra_usage_disabled`.
- Telemetry gains a `fast_mode_disabled_reason` field with a schema enum for the codes.
- The org-status cache records provenance as `source: "guess"` or `source: "server"`, and only emits a change event when the value or the reason actually changed.

**Evidence**

`sdk_opt_in_required`

- Area: Models
- Names: `fast_mode_disabled_reason`
- Tier: Under the hood
- Useful: 2/5
- Signal: 0/5

### Unused MCP skills are now called out separately

Unused MCP skills get their own line in the skill report with server-specific advice.

**What**

The skill report no longer lumps every never-invoked skill together; MCP-provided skills get their own line with guidance specific to servers.

**Details**

- Never-invoked skills are split into settings-owned, plugin-owned and MCP-owned groups.
- The MCP line explains those skills live on the server rather than on disk, and that disabling the server in /mcp also removes its tools.

**Evidence**

`MCP skills live on the server, not on disk`

- Area: Skills
- Names: `/mcp`
- Tier: You'll notice
- Useful: 2/5
- Signal: 0/5

### Org memory no longer auto-selects a lone candidate store

When only one org memory store is found, it is no longer picked for you automatically.

**What**

Discovery finding exactly one candidate memory store no longer silently selects it; the choice is left to you.

**Details**

- Three call sites that auto-set the store when a single candidate existed (`onlyIfAbsent`) were removed, and the helper is gone from the build.
- Discovery still records `org_memory_discovery` telemetry and populates the candidate list.
- Org memory overall still requires the `tengu_haze_glass` gate and respects `CLAUDE_CODE_DISABLE_ORG_MEMORY`.

**Evidence**

`org_memory_discovery`

- Flag `tengu_haze_glass`: Off in both readings (read for one account on one subscription tier against v2.1.219; this account: off, anonymous baseline: off, compiled default: on) These values were read against a different version of Claude Code, so treat them as the nearest reading available instead of one taken on this release.
- Area: Memory
- Names: `CLAUDE_CODE_DISABLE_ORG_MEMORY`
- Tier: You'll notice
- Useful: 2/5
- Signal: 0/5

### Tools can opt out of the aggregate tool-result budget

Individual tools can now skip the shared output size budget and just use the hard ceiling.

**What**

A tool definition can now declare that its output is not subject to the shared tool-result budget lookup.

**Details**

- The tool-result size limiter takes a new fourth argument; when set it clamps straight to the ceiling instead of consulting the per-tool remote-config override.
- The caller passes `e.skipAggregateToolResultBudget === !0` from the tool definition, so the behaviour is gated per tool.

**Evidence**

`skipAggregateToolResultBudget`

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

### Advisor tool result payload is now a discriminated union that can be encrypted

Advisor tool results can come back encrypted with no text field, so reading .text may find nothing.

**What**

The advisor-tool docs warn that `advisor_tool_result.content` differs by advisor model, so code that reads `.text` unconditionally can silently get nothing.

**Details**

- Plaintext advisors return `advisor_result` with `text` and `stop_reason`.
- The newest advisors return `advisor_redacted_result` with `encrypted_content` and no `text`.
- The executor-to-advisor compatibility table is expanded with the new Opus, Fable and Mythos IDs.

**Evidence**

`advisor_redacted_result`

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

### Managed-agents docs: mid-session `system.message` model gate widened, plus two path/method typo fixes

Mid-session system messages now work with more models, and two doc references were corrected.

**What**

The documented model gate for mid-session system messages now covers more models, and two incorrect references were corrected.

**Details**

- The gate list adds the previous Opus alongside the new one, Sonnet, Fable and Mythos; the rejection error name is unchanged.
- The polling troubleshooting note now names `GET /v1/sessions/{id}/events/stream` as the endpoint you may have hit by mistake, where it previously named the paginated endpoint twice.
- The overview's SDK advice corrects `session.events.list()` to `sessions.events.list()`.

**Evidence**

`model_does_not_support_mid_conversation_system`

- Area: Elsewhere
- Names: `sessions.events.list()`
- Tier: You'll notice
- Useful: 2/5
- Signal: 0/5

### Background-agent daemon upgrade check is prerelease-channel aware

Background agent daemons stop mistakenly retiring themselves when you run prerelease builds.

**What**

The stale-daemon retire decision and the generic version-newer comparison now understand prerelease channels, so an `-engine.` build is no longer compared as if it were stable.

**Details**

- A version's prerelease channel is extracted before comparison; the recognized channels are `["dev", "engine"]`.
- If the two versions carry different channels, neither is treated as newer than the other.
- If either side is a prerelease and both cannot be parsed as dated builds, the comparison returns false.
- Previously the check only special-cased the substring `-dev.`.

**Evidence**

`daemonVersion`

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

### Safety-refusal messages rewritten

Refusal messages are shorter, tell you to start a new session, and link somewhere to learn more.

**What**

The generic Usage Policy refusal now says the model can't help and tells you to start a new session to continue, instead of the older longer text. A learn-more/feedback line is appended, and it differs depending on whether you are running through the SDK or interactively.

**Details**

- the generic refusal names the model and directs you to start a new session
- the cyber-topic variant is reworded around "safeguards flagged this message"
- applies to everyone, no gate

**Evidence**

`can't help with this. Start a new session to continue.`

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

### Screen-reader rendering gains an incremental append fast path

Screen-reader output redraws less, appending only new characters when the line is still anchored.

**What**

When the only change to a frame is text appended to the current row and the frame is anchored, the renderer writes just the new characters plus cursor moves rather than redrawing the tail of the output.

**Details**

- an anchor state of "clean", "lastRowAnchored" or "broken" decides when the fast path is safe
- a broken anchor falls back to the existing redraw of the tail
- lives in the screen-reader rendering path

**Evidence**

`lastRowAnchored`

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

### Bundled SDK reference docs updated for the new Opus

Bundled Java and PHP API docs now describe thinking defaults and a new XHIGH effort level.

**What**

The Java and PHP API reference documents shipped inside the binary describe thinking behaviour for the new Opus model.

**Details**

- thinking is on by default for the new Opus, and omitting it runs adaptive, unlike Opus 4.8 and 4.7
- ThinkingConfigDisabled (Java) and ['type' => 'disabled'] (PHP) are accepted only at effort high or lower; XHIGH and MAX return 400
- XHIGH added to the listed effort enum values
- the basic Java example switches to the `.model(String)` overload, and the refusal-fallback example now points at the previous Opus id

**Evidence**

`thinking is on by default`

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

### Clearer failure text when an artifact PR review can't be matched

A failed artifact PR review now tells you to re-run the command instead of blaming gh auth.

**What**

When a review payload names a pull request but no review target was resolved, the mismatch message no longer asks whether gh is authenticated as if fixing it now would help.

**Details**

- the text explains that the target is fixed at invocation time, so nothing changed afterwards can revive that run
- it points you at re-running the command instead
- applies to everyone

**Evidence**

`the target is fixed at invocation, so fixing anything now cannot revive this run`

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

### Safeguard-refusal copy rewritten

Safeguard refusals are now a one-liner, with longer explanations only for category-flagged cases.

**What**

The message shown when a model's safeguards flag your message is shorter and no longer references a model generation, and the longer explanation is now reserved for category-flagged refusals.

**Details**

- the old paragraph beginning "The safeguards are intentionally broad right now and may flag safe and routine coding, cybersecurity, or biology work" is gone, along with the Mythos-capabilities sentence
- the assembled string `They may flag safe, normal content as well.` was removed
- the generic case is now a one-liner; the longer explanation appears only for category-flagged refusals
- a cyber-category refusal, when the relevant check passes, gets its own message pointing at the Cyber Verification Program plus a dedicated support link
- the refusal body reads "can't respond to this message with" rather than "this request with"

**Evidence**

`This sometimes happens with safe, normal conversations.`, `Our intentionally broad safeguards allow us to deliver more capabilities faster, but can sometimes flag legitimate coding, cybersecurity, and biology tasks.`

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

### Auto-memory rm allowlist reworked

Auto-memory file deletion now rejects every flag except -f or --force, so fewer commands slip through.

**What**

The memory-context Bash allowlist for deleting `.md` files is now deny-by-default on flags instead of blocklisting recursive ones.

**Details**

- previously it rejected `--recursive` and `-r*` and allowed other flags; it now rejects every flag except `-f` and `--force`
- `--` end-of-options is supported
- each path is validated by a shared helper that requires a `.md` suffix under the memory directory
- the denial message spells out the rule, and the pre-scan regex for delete-like commands moved to a shared compiled pattern

**Evidence**

`rm (no flags except -f)`

- Area: Memory
- Names: `-f`, `--force`
- Tier: You'll notice
- Useful: 2/5
- Signal: 0/5

### read_page_data refuses unknown interaction schemas

Artifact page-data reads now reject unknown schemas up front and list valid options instead.

**What**

The artifact `read_page_data` action validates the requested schema against what the session actually offers and fails with a dedicated error code instead of attempting a lookup.

**Details**

- The check runs before any lookup and throws its own error code on mismatch.
- The tool description is generated with the concrete list of available schema names inlined, so the model sees valid options up front.

**Evidence**

`read_page_data: the requested interaction schema is not available in this session.`

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

### API-error assistant messages round-trip through the SDK

Resumed sessions can now still tell which past messages were API errors rather than real replies.

**What**

Assistant messages flagged as API errors keep that flag across serialization, so a resumed or replayed session can distinguish them from real assistant output.

**Details**

- Transcript serialization emits `is_api_error_message: !0` for such messages.
- The reverse conversion maps it back to `isApiErrorMessage`.
- This joins the existing `aborted` and `is_virtual` flags.

**Evidence**

`is_api_error_message`

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

### Workshop doc file snapshots are deduplicated

Duplicate workshop doc snapshots are no longer written repeatedly, keeping your transcript smaller.

**What**

Repeated identical workshop doc snapshots are no longer written to the transcript, cutting duplicate large entries.

**Details**

- The `file_snapshot` transcript writer remembers the last workshop doc recorded for the current session and skips writing an identical one again
- It stores `{ sessionId, doc }` after a successful record
- The oversize guard ("Workshop doc over snapshot cap") is unchanged

**Evidence**

`file_snapshot`

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

### Generic denial message when permission prompts are unavailable

Tool denials in background agents now say permission was denied instead of wrongly blaming don't-ask mode.

**What**

The async-agent path that refuses a tool because permission prompts are not available in that context now says "Permission to use X has been denied." rather than blaming don't-ask mode.

**Details**

- the longer wording naming don't-ask mode still exists and is now used only by the don't-ask-mode paths
- the two situations are no longer conflated in the message shown to the user

**Evidence**

`has been denied because Claude Code is running in don't ask mode.`

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

### MCP 404 error now shows only the server origin

MCP 404 errors now show only the server origin, not the request path you hit.

**What**

When an HTTP MCP server returns 404 on connect, the error message quotes just the URL origin instead of the full path.

**Details**

- previously interpolated the sanitized URL, with query, credentials and hash stripped and the trailing slash removed
- now interpolates only the origin, so the request path is no longer echoed back in the error

**Evidence**

`Check the URL in your MCP config.`

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

### Memory-file truncation warning distinguishes index from topic files

When a memory file gets truncated, the warning now gives advice that fits the file type.

**What**

The warning appended when a memory file is truncated for size now says something different for a regular memory file than for the index.

**Details**

- the warning takes a kind argument defaulting to "index"
- for a regular memory file it says "this memory file is …" and advises keeping each file focused on one topic
- for the index it keeps the advice to keep entries short
- the byte-limit phrasing branches on the same argument

**Evidence**

`its lines are too long`

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

### Smaller adjustments

Small polish: credit labels, an extra help link, bigger artifact fetch limit, safer MCP environment handling.

**What**

A group of small changes to model rows, help links, artifact fetching and MCP environment expansion.

**Details**

- the "Requires usage credits" suffix on model rows moved into a shared helper applied to Fable entries, instead of being appended inline
- a third support-article URL joined the set of links auto-marked with a "learn more: " marker
- the artifact WebFetch read raises its `maxContentLength` by an extra 64KB of headroom
- MCP env expansion for settings-provided `env` blocks now builds on a frozen snapshot of `process.env`, using a variant that no longer stashes NO_COLOR and FORCE_COLOR as a side effect

**Evidence**

`artifact_webfetch_read`

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

### Live-session discovery sanity-checks timestamps

Corrupt timestamps in session files can no longer confuse which sessions look live.

**What**

Epoch timestamps read from session sidecar files are now validated before use, so a corrupt or garbage value cannot skew live-session discovery.

**Details**

- The peer-session module exports a new `isSaneEpochMs` predicate and a `MAX_SANE_EPOCH_MS` constant set to 4000000000000000.
- Non-finite, negative and absurdly large epoch values are discarded rather than passed through.
- Not gated; active for everyone.

**Evidence**

`MAX_SANE_EPOCH_MS`

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

### Fast mode advertised on Opus 5 in the environment prompt

Claude is now told Fast mode applies to Opus 5 as well as earlier models.

**What**

The two environment-info prompt builders that describe Fast mode now list Opus 5 alongside 4.8 and 4.7, so the model is told Fast mode applies to the new default.

**Details**

- Applies to both environment-info prompt builders.
- No gate; the line is emitted for everyone.

**Evidence**

`It can be toggled with /fast and is available on Opus 5/4.8/4.7.`

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

### Setting descriptions reworded

Two setting descriptions were reworded; nothing behaves differently.

**What**

Two settings descriptions changed text without changing behaviour.

**Details**

- the background compaction precompute setting no longer shows an "@internal" prefix in its user-facing description
- the model-switch setting now says "safeguards" where it previously said "safety measures"

**Evidence**

`When safeguards flag a message, automatically switch to a different model to keep chatting. When off, your session will pause instead.`

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

### Announcement banners can turn off their accent bar

Announcement banners can now appear without the coloured accent bar, depending on what the server sends.

**What**

The remote announcement schema gained an `accentBar` boolean, so a server-sent announcement can render without the coloured accent bar.

**Details**

- `accentBar: boolean` defaults to true in the schema
- the announcement slot exposes `showAccentBar`, which treats anything other than an explicit false as true
- surrounding chrome draws or omits the bar based on that value
- content comes from remote announcement config, so what you see depends on what the server sends; absent a value the bar is drawn

**Evidence**

`showAccentBar`

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

## Bug Fixes

### OSC 52 clipboard writes are chunked under GNU screen

Copying long text inside GNU screen now works, since the clipboard escape is split into chunks.

**What**

Copying long text while running under GNU screen no longer emits a single oversized escape sequence.

**Details**

- Under `screen` the payload is split into fixed-size chunks, each re-wrapped in its own DCS passthrough segment.
- tmux keeps its previous single-sequence path.
- Previously one sequence was built for all multiplexers.

**Evidence**

`]52;c;`

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

### Team-memory mount conflicts fail closed on write and recover automatically

Shared team memory mounts stop clobbering each other and switch back on once a conflict clears.

**What**

Writing a store manifest into a shared mount is now race-safe, and a mount suppressed for a conflicting manifest re-enables itself once the conflict is gone.

**Details**

- Manifest writes into an absent non-user mount stage to a temp file and hard-link into place, so a racing store cannot be overwritten.
- Finding another store's manifest at write time throws rather than proceeding.
- Mounts suppressed as `mount_dir_foreign_partition` or `mount_dir_unmanifested_nonempty` are re-adopted automatically, emitting `tengu_team_mem_foreign_partition_recovered`.
- Write-time guidance strings were rewritten to name the conflicting directory and state that sync re-enables itself.

**Evidence**

`mount dir holds another store's .memory-sync manifest at write time (fail closed)`

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

### "__proto__" rejected as an MCP server name

An MCP server literally named __proto__ now warns you instead of silently loading nothing.

**What**

An MCP config with a literal `__proto__` key under mcpServers used to be swallowed by prototype semantics, loading nothing and saying nothing. It is now detected and reported.

**Details**

- Surfaces as a validation warning with skipReason `reserved_name`, which feeds the new skipped-server reporting
- Warning messages and suggestions are now passed through a sanitizer before being pushed

**Evidence**

`"__proto__" is a reserved MCP server name and was not loaded`

- Area: MCP
- Names: `mcpServers`
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### Background sessions no longer respawn when the running one is newer

Background sessions stop needlessly restarting when your running build is already newer.

**What**

Background session adoption compares the recorded session's cliVersion against the running build and skips the upgrade respawn when the running session is already newer, instead of treating any version difference as stale.

**Details**

- affects both background session adoption and the upgrade-respawn path
- a version mismatch alone is no longer sufficient to force a respawn
- applies to everyone

**Evidence**

`tengu_bg_adopt_upgrade_respawn`

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

### Tool-result eviction no longer double-counts already-seen results

Duplicate tool results no longer inflate the size math that decides what gets dropped.

**What**

The size accounting that decides which tool results to evict no longer inflates its running total when the same result appears more than once.

**Details**

- In the previously-seen-results total, ids already marked seen contribute zero.
- Budget comparisons therefore reflect distinct results rather than repeats.

**Evidence**

`toolUseId`

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

### Armed fallback model must belong to the same family

Fallback model only kicks in when it truly matches the model you asked for.

**What**

The armed fallback model is only used when it genuinely matches the target, instead of being pushed through a remapping helper first.

**Details**

- The remapping helper call is gone from this path.
- The fallback is used only when its normalised family matches the mapped target's family and it has not already been tried.
- Otherwise the code falls through to the regular resolution path.

**Evidence**

`armedFallbackModel`

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

### Artifact live-edit no longer aborts on a proxy-blocked verification read

Live editing artifacts works again behind egress proxies that block the artifact host.

**What**

In environments whose egress proxy blocks the artifact host, live-edit failed on the pre-flight check instead of proceeding.

**Details**

- The pre-flight read that checks whether a live-edit target is a workshop page tags egress-proxy 403s with `deterministic: "egress-blocked"`
- The live-edit guard throws its "could not verify the target page is not a workshop page" refusal only when the failure is not that tag

**Evidence**

`egress-blocked`

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

### Vim NORMAL mode passes bare left arrow on an empty prompt to the editor

In vim normal mode, pressing left arrow on an empty prompt now works as expected.

**What**

The left-arrow gesture on an empty prompt did not work while idle in vim NORMAL mode, because only unshifted up and down were forwarded to the underlying input.

**Details**

- The vim-mode key router now also forwards `left` when the buffer text is empty
- This is the gesture guarded by the "Press ← again" arming notification, key `left-arrow-again-for-agents`
- The arming state gained an explicit disarm helper that clears the timer and removes that notification; not every call site of the disarm could be determined
- The guard itself reads tengu_left_arrow_editing_guard, fallback true

**Evidence**

`left-arrow-again-for-agents`

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

### Sub-agent stream messages keep their own parent tool id

Output from nested tool calls inside sub-agents is now attributed to the right call.

**What**

Streamed tool events re-enqueued from a sub-agent now carry the parent tool use id from the event itself, so nested tool output is attributed to the right call.

**Details**

- the id on the event is preferred, with the enclosing tool use id only as a fallback
- previously output from nested tool calls could be reattributed to the outer tool

**Evidence**

`parentToolUseID`

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

### Typing cancels a pending left-arrow confirmation in the prompt input

An armed left-arrow confirmation now clears the moment you type anything else.

**What**

An armed left-arrow-on-empty confirmation in the prompt input is now dropped as soon as you press any other key.

**Details**

- the text-input state object exposes a new `disarmLeftArrowConfirm` callback
- the key handler invokes it for any key that is not a plain unmodified left arrow
- previously the armed confirmation could survive intervening keystrokes

**Evidence**

`disarmLeftArrowConfirm`

- Area: Prompt Input
- Tier: You'll notice
- Useful: 3/5
- Signal: 0/5

### MCP reconnect failure text is sanitized and length-capped

MCP reconnect errors now show cleaned-up, shortened text instead of raw messy error output.

**What**

The two /mcp reconnect failure messages no longer interpolate a raw error string.

**Details**

- Covers "Tried reconnecting to …, but the connection failed:" and "Got new credentials, but reconnecting to …", both assembled at runtime.
- The extracted error now passes through the shared display sanitizer, which NFKC-normalizes, strips angle brackets, quotes and assorted bracket characters, collapses whitespace, and truncates with an ellipsis at 200 characters.

**Evidence**

`Reconnection failed after authentication`

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

### Org memory root mount defends its own name

Org memory setup renames aside anything squatting its mount name rather than colliding.

**What**

Deriving the org-memory root mount now updates an existing entry in place and renames aside any other config that has taken the derived mount name, instead of colliding with it.

**Details**

- The prompt index is taken from the server config, falling back to a default and reporting `prompt_index_absent`.
- A discovered config squatting the derived mount name is renamed aside, logged as "org-memory-discovery: discovered config squatting the derived mount name".
- New event `tengu_org_memory_root_mount_derived` records the mount name, mode and prompt-index source.
- Runs on the org-memory discovery path; whether that feature is enabled in this build was not determined.

**Evidence**

`org-memory-discovery: discovered config squatting the derived mount name`

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

### File-snapshot lookups validate their entries

Malformed file snapshot records are skipped rather than returned when restoring from history.

**What**

Reading a snapshotted file from history now rejects malformed records instead of returning them.

**Details**

- `snapshotFiles` must be an array.
- Each candidate must be an object with a matching string `key`, plus string `path` and `content`.
- A candidate failing any of these is skipped and the scan continues.

**Evidence**

`snapshotFiles`

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

### Interrupt no longer double-reports cancelled queued commands

Interrupting queued commands no longer reports the same cancellation twice or lists it as still queued.

**What**

Interrupting a session with queued commands emitted a second "cancelled" lifecycle callback for commands whose cancel had already been acknowledged, and listed them again as still queued.

**Details**

- The command queue gains `consumeCancelPendingAcked` / `hasCancelPendingAcked` on top of the existing cancel-pending set
- Drain suppresses a second `"cancelled"` lifecycle callback for a uuid whose cancel was already acknowledged
- The interrupt control_response filters those uuids out of `still_queued`
- Affects SDK clients watching command lifecycle around `cancel_queued`

**Evidence**

`consumeCancelPendingAcked`

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

### PR-review anchor repo names reject "." and ".."

PR review artifacts now reject repository names like "." or ".." that could escape paths.

**What**

Repository names read back from published review artifacts could be `.` or `..`, a path-traversal risk; they are now rejected.

**Details**

- The regex used to validate PR review anchors and the review JSON schema changed from `/^[A-Za-z0-9._-]{1,100}$/` to a form with a leading negative lookahead
- A repo named `.` or `..` now fails with "anchor.repo is not a valid repository name"

**Evidence**

`anchor.repo is not a valid repository name`

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

### Queued-command cancellations are acknowledged once

Cancelled queued commands are reported once and no longer linger in the interrupt receipt.

**What**

A queued command whose cancellation was already reported no longer produces a second cancelled event, and no longer appears as still queued in the interrupt receipt.

**Details**

- the command queue gained `consumeCancelPendingAcked` and `hasCancelPendingAcked` on top of the existing cancel-pending marks
- commands whose "cancelled" lifecycle event was already emitted are skipped on later passes
- those commands are filtered out of the `still_queued` list in the interrupt receipt sent to SDK clients

**Evidence**

`still_queued`

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

## In Development

### Memory recall now triggers on CJK input with no spaces

Chinese, Japanese and Korean messages without spaces could trigger memory recall, but that branch is off here.

**What**

Recall previously required whitespace in the user's message, which excluded Chinese, Japanese and Korean text written without spaces.

**Details**

- Adds a branch accepting NFKC-normalized text matching CJK and Hangul ranges.
- That branch is gated on `eJi() = Ke("tengu_mill_orange", !1)`, so off absent a remote value.
- Separately and unconditionally, exhausting the per-session recall byte budget is now reported as a feature error instead of returning silently.

**Evidence**

`session_budget_exhausted`

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

### Rewritten memory system prompt

Rewritten memory rules would make Claude save corrections and environment quirks immediately, before replying.

**What**

The memory instructions given to Claude were rewritten around three criteria and now specify when a memory must be written rather than leaving it to judgement.

**Details**

- Criteria are applicable, durable, legible
- Explicit MUST-save triggers: user corrections however phrased, skeptical questions, environment discoveries
- New rule that memory writes happen before the reply that engages the correction, not after the conversation settles
- The frontmatter template now includes a `metadata: pinned:` field
- Gated on `tengu_stone_shell`, fallback false

**Evidence**

`A good memory is applicable, durable, and legible`

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

### Workshop HTML artifact publishing gains a publish-time verifier

Published HTML artifacts can be checked against rules first and refused outright instead of shipping something unsafe.

**What**

Artifact publishing can now run the generated HTML through a verifier that refuses to publish when rules are violated, rather than shipping it and relying on later checks.

**Details**

- publishing accepts a new `verifyWorkshopHtml` option; publishing a plan as an artifact passes `verifyWorkshopHtml: "probe"` when the gate is on
- refusal reports per-rule violations and emits telemetry under `workshop_html_publish` and `verifier_refused` instead of publishing
- verified slugs accumulate into app state under `workshopVerifiedSlugs`
- for workshop surfaces, a redeploy must pass `refusedSidecarHistory`, sourced from a new AppState key `sidecarHistorySlugs`, otherwise publishing is refused rather than skipping the sidecar-ordering gate
- sidecar files whose content type is not in the inert text or binary allowlists are flagged as renderable or executable
- the gate falls back to false and additionally requires the surrounding workshop feature check; none of these names exist in 2.1.218

**Evidence**

`refusing rather than skipping the sidecar-ordering gate`, `verifyWorkshopHtml`

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

### Workshop artifact pages get a publish-time structural verifier

Publishing a workshop page would run a structural check and refuse instead of shipping violations.

**What**

Publishing a workshop page runs a structural check over the rendered HTML and refuses the publish on violation instead of shipping it.

**Details**

- Verifies against a computed allowlist of blessed inline-script hashes; refusals list rule, where and hint, capped at 12 entries followed by "(and N more)".
- Sidecar files are policed: workshop pages may not ship renderable or executable sidecars, and non-workshop sidecars of renderable types are probe-verified individually.
- New telemetry: `workshop_verifier_refused`, `workshop_sidecar_refused`, `workshop_sidecar_contract`.
- Gated on `Xde() = mN() && Ke("tengu_gable_onyx_sluice", !1)`, so off absent a remote value.

**Evidence**

`workshop pages cannot ship renderable or executable sidecar files`

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

### live-edit refused on workshop pages

Live-edit would be blocked on workshop pages so changes go through the validating publish path.

**What**

The Artifact tool blocks live-edit actions against workshop pages so edits go through the publish path that validates them.

**Details**

- Target slug is checked against the session's known workshop slugs, then open frame URLs, then a server-side fetch looking for a `ws-decisions` island.
- A match refuses with `workshop_live_edit_refused`; a transient read failure refuses with `workshop_live_edit_unverifiable` and tells you to retry or WebFetch the page.
- Same gate as the verifier: `Xde()` / `tengu_gable_onyx_sluice`, fallback off.

**Evidence**

`live-edit is not available on workshop pages \u2014 edit the local workshop file and republish; the publish path is the validation chokepoint live-edit would bypass`

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

### Transcript-sharing prompt wired into the session memory viewer, but unreachable in this build

A prompt to share your transcript after rating a memory is built but never appears.

**What**

The session memory viewer gained a full transcript-share flow that arms when a memory is rated, but the precondition that would show it always returns false here.

**Details**

- The viewer now takes a `messagesRef` and renders states `transcript_prompt`, `submitting`, `submitted` and `thanks`.
- The success panel shows the saved bundle path with the text telling you to send the file to your Anthropic account representative or attach it to a support request.
- Telemetry goes to `tengu_session_memory_share_event` with `transcript_prompt_appeared`, `transcript_share_yes/no/dont_ask_again`, `transcript_share_submitted` and `transcript_share_failed`.
- The arming precondition calls a predicate whose body is `return !1`, so nothing is ever shown.
- Two further suppressors exist regardless: `CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY` and a `transcriptShareDismissed` setting.

**Evidence**

`Send this file to your Anthropic account representative or attach it to your support request.`

- Area: Memory
- Names: `CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY`, `transcriptShareDismissed`
- Tier: Not switched on
- Useful: 2/5
- Signal: 3/5
- Present in the build but not switched on

### Workshop skill gains a direct-HTML authoring lane, and it is now the default

The workshop skill would now write HTML documents by default, with a strict publish check, but isn't offered.

**What**

The bundled `workshop` skill text was rewritten around two authoring lanes, with HTML promoted over markdown, though the skill is not offered on this build.

**Details**

- v2.1.218 said flatly that the workshop document is markdown and stays markdown for its whole life.
- A new "Choosing the lane" section tells the model to copy a template to a path ending in `.workshop.html`, fill it, and publish that file.
- The markdown `.workshop.md` lane becomes secondary, reserved for when the user asks for markdown or the document will be dominated by quoted external content.
- Publishing runs a structural verifier on the final payload of every publish and refuses out-of-contract pages: fixed scripts, no inline `on*` handlers, no iframes/forms/base/link, escaped quoted content, island and markup agreement.
- Registration uses `isEnabled` backed by gate `tengu_gable_onyx_sluice` with in-source fallback false, so absent a remote value the skill is not offered; it additionally needs the Artifact tool's `capabilities` and the `read_page_data` action.

**Evidence**

`**Default to the TEMPLATE-HTML lane**`

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

### Workshop doc: snapshots, plan-mode writes and verified slugs

A per-session workshop document gets snapshotted with your session files and is only writable in plan mode.

**What**

The per-session `*.workshop.md` document is now snapshotted with the rest of the session files and gets explicit read and write permission handling, all behind the workshop gate.

**Details**

- gated on the workshop feature gate, combining `tengu_larch_pavise` (fallback false) with `tengu_cedar_transom` as a kill-switch
- the doc is included in the file-snapshot system entry under the key "workshop"
- it is skipped with a log line when it exceeds the snapshot character cap, and skipped when unchanged
- write permission for `<session>.workshop.md` is granted via an `includeWorkshopDoc` option only when the caller's permission mode is `plan`, with the mode newly threaded into that check
- the read side always includes it, so "Plan files for current session are allowed for reading" covers the workshop doc unconditionally
- publishing a plan records the returned slug in `workshopVerifiedSlugs` app state

**Evidence**

`Workshop doc over snapshot cap`, `Plan files for current session are allowed for writing`

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

### Compact recall/remember summaries in the transcript

Memory recall notes could show as a compact grouped line in normal sessions, but that view is off.

**What**

Memory attachments rendered only in verbose or transcript mode; a compact fallback view exists for normal sessions but is switched off in this build.

**Details**

- When the gate is on, non-verbose sessions get a compact grouped node built from the attachment's memories
- Labelled "Recalling"/"Recalled" or "Remembering"/"Remembered" depending on whether the group is still active
- Gated on tengu_stone_shell, fallback false

**Evidence**

`isActiveGroup`

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

### Composed PR-review publish refusal explains whether it is a live switch

If composed review publishing is unavailable, the refusal now says whether it was turned off mid-session.

**What**

When composed review publishing is unavailable, the refusal now distinguishes a feature switched off mid-session from one that was never on.

**Details**

- Replaces the blanket "re-run /artifact-pr-review and follow the template flow" message with two cases: an operator turning it off during the session (retry later, do not loop) and a session where it was never enabled (start a new session).
- The pr-review skill text now states that a published composed review page is updated only through the acting loop's republish, adds a "Communicating while this skill runs" section, and requires validating a second island `prr-anchor` whose `publishedAt` must match `YYYY-MM-DDTHH:MM:SSZ`.
- Driven by a runtime switch checked at publish time.

**Evidence**

`composed review publishing was turned off by an operator during this session`

- Area: Code Review
- Names: `/artifact-pr-review`
- Tier: Not switched on
- Useful: 2/5
- Signal: 2/5
- Present in the build but not switched on

### Memory subagent's delete permission narrowed

The memory cleanup agent could only delete plain memory files, never anything with flags or outside the folder.

**What**

The consolidation subagent's ability to delete files was tightened to a specific shape of `rm` command.

**Details**

- Only .md files, only inside the memory directory, only outside protected subdirectories such as .git or agents, and with no flags except -f
- Both the tool-permission denial message and the subagent's own prompt were updated to state the narrower rule
- Part of the memory feature, gated on `tengu_stone_shell`, fallback false

**Evidence**

`of .md files inside the memory directory only (outside protected subdirectories like .git or agents`

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

### Category-based server-side fallback beta

The server can now pick a fallback model by category when a request is refused, instead of the app naming one.

**What**

A second server-side-fallback beta lets the server choose the fallback model by category rather than the client naming one, but it only takes effect if the server accepts the new beta header.

**Details**

- the descriptor is `server_side_fallback_category` with header `server-side-fallback-2026-07-01`
- when it applies, the request sends `fallbacks: "default"` instead of an explicit `[{ model: ... }]` list, letting the server route, for example, cyber-category refusals to the previous Opus
- fallback-error classification gained `category_beta_header` (the new header was rejected) and `default_unconfigured` (from a body containing "has no default fallback configuration")
- the retry path un-arms the category header on those errors and falls back to the existing explicit-list behaviour
- the code path also still requires the existing default-fallback conditions

**Evidence**

`"server-side-fallback-2026-07-01"`

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

### Org memory: discovery telemetry and store-picker copy

The memory store picker nudges you to pick a specific project instead of leaving the legacy default.

**What**

Org-memory discovery reports a new empty case and reworked candidate counts, and the store picker gains copy nudging users off the legacy selection.

**Details**

- When the server returns no candidates and no stores, discovery settles "empty" and logs `org_memory_discovery` with `stores: 0, cached: !1`
- Candidate telemetry replaces the old project counts with `candidate_count_public` and `candidate_count_private`
- The store picker adds a legacy-selection nudge event `tengu_org_memory_legacy_pick_nudged`, the nudge description "set automatically — pick a project to choose where saves go", a "public project" row description, and the summary label "all public projects" for the legacy selection
- Gated on tengu_haze_glass, fallback false, and additionally refused when CLAUDE_CODE_DISABLE_ORG_MEMORY or CLAUDE_MEMORY_STORES is set

**Evidence**

`set automatically — pick a project to choose where saves go`

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

### Org-memory discovery can return a prompt_index path

Org memory discovery can pick up a prompt index path from the server and attach it to the mount.

**What**

The store discovery response can now carry a prompt index path, which becomes part of the derived mount.

**Details**

- New optional `prompt_index` string field on the discovery response schema, max 512 characters, coerced away on parse failure
- It is turned into a mount's promptIndex and reported through `prompt_index_source` on `tengu_org_memory_root_mount_derived`
- A "prompt_index_absent" signal is reported when the server omits it
- Org memory requires tengu_haze_glass, fallback false, and the allow_memory_sync entitlement

**Evidence**

`prompt_index_absent`

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

### App state tracks verified workshop slugs and sidecar history

Your session would remember which artifact pages passed verification and which sidecars were refused.

**What**

Publish results are recorded in app state so later publishes can tell whether a slug already passed verification and what sidecars were refused.

**Details**

- New AppState fields `workshopVerifiedSlugs` (slugs whose publish passed the workshop verifier) and `sidecarHistorySlugs` (per-slug record of refused sidecar content types).
- A redeploy that omits `refusedSidecarHistory` is refused as a contract violation rather than quietly skipping the ordering gate.
- Same workshop gate as the verifier.

**Evidence**

`workshop publish contract violation: redeploys must pass refusedSidecarHistory`

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

### New bundled template file `templates/workshop-page.html`

A second bundled workshop page template ships, showing what the HTML authoring style looks like.

**What**

The workshop skill's file map ships a second template, the blessed starting point for the new direct-HTML lane.

**Details**

- The existing `templates/artifact-workshop.html` remains the mechanically-filled plan-artifact template with `{{TITLE}}`, `{{TAB_TITLE}}`, `{{EYEBROW}}` and `{{SUMMARY}}` slots.
- `templates/workshop-page.html` describes itself in a header comment as the blessed template for direct-HTML workshops.
- It ships a filled sample page: a `cache-store` decision with Spanner and Redis options, a populated `ws-decisions` island, and hand-drawn inline SVG figures.
- It carries the same vetted decisions script and adds `figure`/`figcaption` styling for the inline SVG diagrams the HTML lane prefers over mermaid.
- Reachable only behind the same workshop skill gate, which is off here.

**Evidence**

`"templates/workshop-page.html"`

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

### Admin-workspace helpers get a random per-run token and path guards

Admin workspace helpers gained a random run marker and stricter name and path-traversal checks.

**What**

Admin-workspace helper code gained a per-run marker and input validation patterns, with no confirmed user-facing surface in this build.

**Details**

- A module initialiser builds a random `zzadminwc`-prefixed marker from 8 random bytes.
- New regexes reject characters such as `/`, `@`, `#`, `?`, `\` and whitespace in names.
- A separate pattern catches dot-segment path traversal, including the `%2e` encoded form.
- Whether any of this is reachable for users could not be resolved.

**Evidence**

`zzadminwc`

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

### PR-review artifact description has an alternate wording that cannot ship in this build

An alternate PR-review description exists but you'll always see the original one.

**What**

The pr-review command description became a function that picks between two strings, one of which describes republish-only editing of composed review pages. The selector can never choose it here.

**Details**

- the alternate string ends "a published composed review page is updated ONLY through the acting loop's republish — never by editing its HTML directly."
- the selector combines a hard-coded false with `tengu_walnut_sconce`, and the helper it bottoms out in returns false unconditionally in this build
- users see the original description

**Evidence**

`never by editing its HTML directly.`

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

### Transcript-sharing feedback prompt reworked

The offer to share your transcript was rebuilt, now only appears once per session and records when you dismiss it.

**What**

The transcript share offer is restructured around an arm/select/dismiss hook, with its submitting and submitted states moved into a shared component, and dismissals are now reported.

**Details**

- a guard prevents the prompt re-arming within the same session
- the arming timer is cleaned up on unmount
- dismissal emits a new `transcript_share_dismissed` event
- the separate "frustration" survey branch of the feedback UI was deleted
- whether this surface is enabled in this build could not be resolved

**Evidence**

`transcript_share_dismissed`

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

### Opus 5 carries an extra system-prompt block and suppresses subagent nudges

On some models Claude will stop spinning up subagents or research workflows unless you actually ask for it.

**What**

Models advertising a new capability get an appended prompt block telling the model not to reach for subagents or research workflows unprompted, and the matching steering nudge is turned off for them.

**Details**

- gated on the `opus_5_prompt_bundle` capability, with a remote kill-flag checked inside the capability test (fallback: not killed)
- the appended block reads "Do not call the AgentTool unless the user requested it" and "Do not use workflows or deep-research unless the user requested it"
- application is logged as `tengu_heron_brook_applied` with `fromClientData: !1`
- the `tengu_heron_brook` string flag still overrides
- the same capability check feeds the "thistle grebe" steering resolver with a new "model" source returning "no_nudges", suppressing the delegate-to-subagents steering text

**Evidence**

`Do not call the AgentTool unless the user requested it`

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

### Team memory mounts can un-suppress themselves

A memory mount that got suppressed can now recover on the next sync instead of staying stuck until restart.

**What**

Two suppression reasons on multi-store memory sync are now recoverable instead of sticking until restart.

**Details**

- Applies to `mount_dir_foreign_partition` and `mount_dir_unmanifested_nonempty`
- On the next sync the mount dir is re-examined; if the partition now matches or the dir is empty, suppression is cleared with the log "condition cleared — re-adopting the mount dir" and failure counters reset
- In the same area the separate `pushSuppressedReason` field is gone entirely, so pushes and periodic resync are no longer gated on it and per-mount suppression is the single mechanism
- Gating of team memory sync itself was not examined

**Evidence**

`condition cleared — re-adopting the mount dir`

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

### Memory recall tolerates label-prefixed selections

Recalled memories no longer get silently dropped when the model echoes back the category label with the name.

**What**

A recalled memory could be lost purely because the model echoed the category label shown in the prompt.

**Details**

- When the recall model returns memory names, any that do not match a known memory have a leading bracketed category label stripped by regex before the match/filter step
- Covers the `[user] ` / `[project] ` prefixes the prompt displays
- Previously such a response was silently dropped
- Gating of the memory recall feature was not resolved

**Evidence**

`memory_recall_select`

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

### The decisions island's opening bytes are now a named constant, and reserved page-wide

Workshop pages would be rejected at publish if they mention the decisions marker more than once.

**What**

The literal island-open sequence is lifted into a module-level constant next to the templates, and the skill text makes its uniqueness on the page a hard rule.

**Details**

- Island extraction scans for exactly that byte sequence, so the verifier refuses any page where the scan and the real island could disagree.
- An author page mentioning the island id anywhere else, in a quoted example or an attribute, fails the publish instead of being silently mis-extracted.
- Only reachable behind the workshop skill gate, which is off here.

**Evidence**

`id="ws-decisions">`

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

### Memory truncation warning now covers ordinary memory files

Oversize memory warnings would finally say something sensible about ordinary files, not just index entries.

**What**

The oversize warning always spoke about index entries regardless of what was truncated; it now branches on file type.

**Details**

- For non-index files it tells Claude to "Keep each memory file focused on one topic"
- Adds "its lines are too long" as a stated reason for truncation
- Gated on `tengu_stone_shell`, fallback false

**Evidence**

`this memory file is `

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

### Repo-mismatch display disambiguates by host

Resuming into a same-named repo from a different host can now be labelled host/repo to tell them apart.

**What**

Resuming against a same-named repo on a different forge can now show which is which, though the callers of the new helper were not traced.

**Details**

- The port-stripping normalizer moved out of the inline closure into its own helper
- A new function returns `sessionDisplay` / `currentDisplay`, prefixing the repo with its host as `host/repo` only when the two hosts differ after normalization

**Evidence**

`sessionDisplay`

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

## Internal Changes

### Fast mode disable records a source and a new reason

When fast mode is switched off, it now records why and where from, including hitting extra-usage limits.

**What**

The fast-mode disabled state now records where the disable came from and distinguishes an overage rejection, and both disable paths avoid redundant app-state writes.

**Details**

- the disabled state object carries `source: "server"`
- an overage rejection sets reason `extra_usage_disabled` and emits the disabled event
- both disable paths skip the app-state write when `penguinModeOrgEnabled` is already false
- the preference-disable path bails out early only when the existing disabled state came from the server and is not a recoverable reason

**Evidence**

`extra_usage_disabled`

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

### Memory-directory edits get more telemetry and cache invalidation

Editing files in your memory directory now clears stale caches right away, so changes take effect sooner.

**What**

Edit and Write operations inside the memory directory record extra detail and clear derived caches before refreshing.

**Details**

- In addition to `tengu_memdir_file_edit` and `tengu_memdir_file_write`, the tool input is recorded through an extra reporter.
- The pinned and index caches are invalidated ahead of the existing refresh call.

**Evidence**

`tengu_memdir_file_write`

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

### bun:ffi dropped for native Bun and process APIs in three low-level paths

Low-level process and hardening paths now use built-in APIs instead of loading system libraries.

**What**

Three places that used dlopen through bun:ffi now call native APIs directly, and a fourth FFI helper was deleted.

**Details**

- Process replacement no longer dlopens libSystem/libc for `execve`; it calls `process.execve` with a sanitized env object where `__proto__` is defined rather than assigned.
- The macOS TCC responsibility-disclaim spawn dropped its posix_spawn FFI dance for `process.execve(e, r, n, { macDisclaimResponsibility: !0 })`.
- PR_SET_DUMPABLE hardening now calls `Bun.ant.setDumpable(!1)`.
- The SO_PEERCRED/getpeereid FFI peer-credential helpers were removed outright.
- These are macOS and Linux paths; the execve helper still returns early on Windows.

**Evidence**

`prctl(PR_SET_DUMPABLE,0) returned nonzero`

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

### `fast_mode_disabled_reason` on turn-completion telemetry

Turn logs now record why fast mode was skipped, such as env setting or model not allowed.

**What**

Turn-completion events now report why fast mode was not used, alongside the existing state field.

**Details**

- New `fast_mode_disabled_reason` sits next to `fast_mode_state` and is populated from the fast-mode eligibility check.
- Values include `not_first_party`, `disabled_by_env`, `model_not_allowed`, `sdk_opt_in_required`, `pending` and `unknown`.
- Present at 18 sites in this build; did not exist in the previous one.

**Evidence**

`fast_mode_disabled_reason`

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

### command_lifecycle telemetry description covers interrupt-driven queue sweeps

Queued commands can now be marked cancelled when an interrupt sweeps the queue.

**What**

The internal schema description for queued-command lifecycle states documents a new way a command reaches 'cancelled' and tells exactly-once resenders how to reconcile against it.

**Details**

- New cause for 'cancelled': the command was swept by an interrupt carrying cancel_queued:true
- Resenders should reconcile against the interrupt receipt's `cancelled` list in addition to their own cancel_async_message responses before resending
- The named receipt capability string is new in this build

**Evidence**

`swept by an interrupt with cancel_queued:true`

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

### Bug-report telemetry values now pass through redaction helpers

Bug report data now goes through the same scrubbing as other telemetry before being sent.

**What**

Bug report events send their identifier and level fields through the same sanitizing helpers already used for other fields, rather than as raw strings.

**Details**

- affects `tengu_bug_report_submitted` and `tengu_bug_report_description`
- feedback_id, survey_appearance_id and strip_level are wrapped instead of being sent raw or via `String(...)`

**Evidence**

`tengu_bug_report_submitted`

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

### Version bumped to 2.1.219

The version you see in --version and bug reports is now 2.1.219.

**What**

Embedded build metadata moves to version 2.1.219 with a build time of 2026-07-24T03:24:19Z and a new git sha.

**Details**

- flows into `--version` output, the HTTP user agent, update and downgrade messages, daemon logs, doctor output and bug reports

**Evidence**

`BUILD_TIME: "2026-07-24T03:24:19Z"`

- Area: Elsewhere
- Names: `--version`
- Tier: Under the hood
- Useful: 2/5
- Signal: 0/5

### Sessions record a parked job id

Sessions now remember which background job they were handed off to, and validate their timestamps more strictly.

**What**

Session metadata now carries the id of the background job a session was handed off to, and timestamp fields in the metadata reader are validated more strictly.

**Details**

- `parkedJobId` is written when a session is handed off to the background agents view and cleared when it returns
- the reader parses it as an optional string
- `startedAt`, `updatedAt` and `statusUpdatedAt` now go through a shared bounds check for finite, non-negative and below a maximum, replacing a bare typeof number test

**Evidence**

`parkedJobId`

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

### Wildcard URL patterns parsed through a dedicated helper

Wildcard URL patterns are now parsed by a shared helper, including odd cases like a wildcard in the port.

**What**

Wildcard URL patterns are now parsed by a dedicated helper instead of ad-hoc string handling, used to decide which URL component a template variable expands into.

**Details**

- the helper substitutes a sentinel for `*`, parses the result as a URL, and retries with a placeholder port when the wildcard sits in the port position
- it returns the URL components with the sentinel mapped back to `*`
- callers use it to classify an expansion as scheme, authority, or the rest, for MCP-style URL templates
- the security consequence of the change could not be fully established

**Evidence**

`pathname`

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

### New transcript helper for unresolved tool_use lookups

Looking up an unfinished tool call in the transcript now logs a warning instead of erroring out.

**What**

A helper that scans the current transcript for an unresolved `tool_use` id was added, with non-throwing failure behaviour.

**Details**

- when nothing is found, or the transcript cannot be read, it logs a warning naming the transcript path and message count
- it returns null in those cases rather than throwing

**Evidence**

`findUnresolvedToolUse: failed to read transcript`

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

### MCP error escaping, typed telemetry ids and shared version normalisation

MCP server error lines are escaped before display, plus minor telemetry and version-string cleanups.

**What**

Three small plumbing changes around display escaping and telemetry value typing.

**Details**

- Server error lines in the MCP UI pass code, path and message through a sanitising helper before rendering.
- Prompt-cache diagnosis telemetry wraps `requestId` and `previousMessageId` in the typed-value helpers instead of passing raw strings.
- Version strings in the logging pipeline are normalised via a shared helper rather than an inline regex.

**Evidence**

`tengu_prompt_cache_diagnosis_received`

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

### Compaction hook plumbing reworked

Automatic compaction passes more context when rebuilding attachments and handles failures on a dedicated path.

**What**

The reactive-compaction path passes more context into attachment building and handles failures through a dedicated path.

**Details**

- The attachment builder is now called with the message list and an agent-context descriptor in addition to its previous arguments.
- Errors go through a dedicated handler rather than a generic catch that logged and returned empty attachments.

**Evidence**

`reactive_compact`

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

### Artifact publish options moved to an options object

Artifact publishing takes cleaner options and can report which sidecar types were refused.

**What**

The artifact publish helper takes a single options bag instead of positional flags, and reports one more field on success.

**Details**

- Options are `injectDiagramRuntime`, `injectHighlightRuntime` and `composedPrReview`.
- Successful validation can attach `refusedClassSidecarTypes` to the result.
- Content-type detection for uploaded files moved from an inline extension map to a helper.

**Evidence**

`injectDiagramRuntime`

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

### Daemon peer-uid lookup moved to a Bun API

The background daemon uses one runtime call to identify socket callers on Mac and Linux; Windows still unsupported.

**What**

The unix-socket peer credential lookup dropped its separate macOS and Linux implementations in favour of a single runtime call.

**Details**

- Both platforms now call `Bun.ant.getPeerUid(t)`
- Still returns null on Windows
- Other failures log "[daemon] peer uid lookup failed:" with the error

**Evidence**

`Bun.ant.getPeerUid`

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

### Dangerous-delete detection uses a prebuilt regex

Permission checks for risky delete commands do slightly less work each time, with identical results.

**What**

The PowerShell and cmd delete-command matcher no longer rebuilds its alternation on every call.

**Details**

- The matcher takes a second parameter and tests against a module-level regex compiled once from the same `rm|remove-item|ri|del|erase` alternation
- Matching behaviour is unchanged; this removes per-invocation work on a hot permission path

**Evidence**

`rm|remove-item|ri|del|erase`

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

### Fast-mode availability records how it was derived

When fast mode looks unavailable, the cache now records whether that was a guess or confirmed.

**What**

The cached fast-mode availability record notes when a disabled state was guessed locally rather than confirmed by the server.

**Details**

- the disabled branch now carries `source: "guess"` alongside the existing `reason: "unknown"`

**Evidence**

`{ status: "disabled", reason: "unknown", source: "guess" }`

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

### Workshop and sidecar slug state now propagates out of forked agent contexts

Subagents can now pass workshop and sidecar tracking state back to your main session.

**What**

The allowlist of AppState keys a forked or subagent context may write back to the parent session grew from one key to three.

**Details**

- `ASYNC_SHARED_APP_STATE_KEYS` previously held only `frameUrls`
- it now also includes `workshopVerifiedSlugs` and `sidecarHistorySlugs`

**Evidence**

`ASYNC_SHARED_APP_STATE_KEYS`

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

### Daemon supervisor staggers child spawns

The daemon now starts its child processes two seconds apart instead of all at once.

**What**

The daemon supervisor no longer starts its children all at once, spreading them out instead.

**Details**

- Both the initial spawn loop and the loop that restarts children after a config reload now pass an increasing start delay, 2000ms per child.
- The existing backoff timers are unchanged and still apply on top of this.
- Only reached when the daemon supervisor is running; no effect otherwise.

**Evidence**

`spawned ${A}`

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

### Teleport repo-mismatch telemetry records where the check ran

Repo-mismatch warnings now record which check caught the problem, for diagnosis.

**What**

Repo mismatch and not-in-repo teleport events now say which code path detected the problem.

**Details**

- New `stage` field: `resume_validate` on the resume path, `direct_arg_detect` when detected from the direct argument.
- The mismatch display strings are now produced by a shared helper rather than built at each site.

**Evidence**

`direct_arg_detect`

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

### Telemetry ids validated before sending

Analytics ids are now filtered to safe characters before being sent, with no change you'll see.

**What**

A large set of analytics call sites now run ids through a validator, bounding what can reach the analytics pipeline.

**Details**

- The validator allows only `[A-Za-z0-9_-]` up to 128 characters and otherwise reports the literal "nonconforming".
- Applied to background job ids and attach shorts, `session_id` on `tengu_remote_attach_session`, `appearance_id` on feedback/survey and transcript-share events, `requestId` and `previousMessageId`, and `trigger_ids` which are now joined through the same validator.
- Numeric fields such as `http_status` and bug-report `status_code` moved from `String(...)` to the string-coercing sanitizer.
- No user-visible behaviour change.

**Evidence**

`nonconforming`

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

### Telemetry string extractors routed through a sanitizer

Error names and file positions pass through a sanitizer before telemetry; nothing changes for you.

**What**

Error names, errnos and file:line:col values are now marked as vetted before reaching telemetry.

**Details**

- The already-regex-validated result of each extractor passes through a wrapper on its way out.
- Literal fallbacks at call sites are wrapped too, including the "unknown" errno defaults in fleet dispatch, render-error reporting and respawn logging.
- Behaviourally invisible.

**Evidence**

`errno_code`

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

### Remainder of this group is minifier churn, not behaviour

Most changes here are renamed internals with identical behaviour, including unchanged upload and watcher limits.

**What**

Most of the added and removed one-liners in this area are renames of identical code between builds, with no user-visible effect found.

**Details**

- The sandbox marker helper still builds `CMD64_`-prefixed log tokens with the same regex.
- The color-error helper still produces the same `No color named "..."` text.
- The skills watcher's polling constant and the review-mode `finderBudgetHint` helper, which always returns false, both survive under new names.
- The design-consent global setter/getter pair is unchanged in substance.
- Numeric constants match v2.1.218 under different symbol names, including the 67108864 upload cap, the 0.7 voice-meter smoothing factor and the watcher intervals.

**Evidence**

`No color named "`

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

### Artifact review-page validator takes a single options object

The PR-review page validator was reshaped to take one options object; behaviour is identical.

**What**

The PR-review template validator's signature collapsed from a positional regex plus a trailing options object into one options bag.

**Details**

- The bag carries `crUrlRe` alongside `allowMermaidFence`
- The single caller passes `crUrlRe: s` where `s` is null, so behaviour is unchanged
- Shape refactor only

**Evidence**

`crUrlRe`

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

### Cross-session safety rule wrapped in a named tag

A safety rule in the auto-mode prompt got a wrapper tag; the rule text is unchanged.

**What**

The automode safety evaluation prompt now encloses its cross-session clause in a named wrapper element, making the block addressable as a unit.

**Details**

- `<cc_automode_session_rules>` wraps the existing `<cross_session_messages_rule>` element
- the rule text itself is unchanged

**Evidence**

`<cc_automode_session_rules>`

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

### Turn state extracted into a reusable factory

The per-turn bookkeeping in the query loop was restructured with no change you can see.

**What**

The per-turn accumulator used by the query loop is now built by a factory rather than assembled inline. No user-visible behaviour changes.

**Details**

- the accumulator holds assistant messages, tool results, `tool_use` blocks, and the follow-up, end-turn, prevent-continuation and deferred flags
- `reset` can optionally keep assistant messages
- `rebuildStreamingToolExecutor` re-creates the streaming tool executor from the injected constructor

**Evidence**

`rebuildStreamingToolExecutor`

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

### Screen-reader renderer tracks an anchor state

Screen-reader output tracks one more piece of state to decide what to redraw.

**What**

The screen-reader diffing state carries an extra anchor value alongside its existing bookkeeping.

**Details**

- `prevScreenReaderAnchor` is initialised and reset to "clean" together with the previous-lines and park-position state.

**Evidence**

`prevScreenReaderAnchor`

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

### New "engine" prerelease channel recognized for worker respawn

Background workers now recognise a second build channel and won't respawn themselves across channels.

**What**

Background worker and daemon respawn now understands a second prerelease channel and refuses to upgrade across channels.

**Details**

- The version regex accepts `engine` alongside `dev|alpha|beta|rc|test|nightly` as a prerelease tag
- A new helper set `["dev", "engine"]` classifies a version by which of those tags its build string contains
- A `-dev.` worker is not respawned as `-engine.` or vice versa; previously only the dev tag was understood
- This build reports VERSION "2.1.219"

**Evidence**

`nightly|engine`

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

### Upgrade prompt from the spend-limit menu names its surface

Upgrades started from the spend-limit menu are now labelled with that origin internally.

**What**

Upgrades started from the spend-limit options menu are now attributed to that menu instead of being indistinguishable from other entry points.

**Details**

- Switched from the generic upgrade call to `callUpgradeFromSurface(..., "spend_limit_options_menu")`

**Evidence**

`spend_limit_options_menu`

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

### Auto-mode permission rules are wrapped in their own prompt tag

Auto-mode permission rules are now wrapped in their own tag inside the prompt.

**What**

The text substituted for `<permissions_template>` in the auto-mode prompt is now enclosed in its own tag pair rather than inlined bare.

**Details**

- the block is delimited by `<cc_automode_permissions>` and `</cc_automode_permissions>`
- content is unchanged; only the framing around it is new

**Evidence**

`<cc_automode_permissions>`

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

### Engine builds recognised by the version-timestamp parser

Build-age checks now read timestamps correctly from a second class of internal builds.

**What**

The helper that pulls a build timestamp out of a version string now understands `-engine.` prerelease tags, so engine builds get correct build-age handling.

**Details**

- previously only `-dev.` tags matched
- the version-normalising replace matches both forms as well

**Evidence**

`/-(?:dev|engine)\.(\d{8})\.t(\d{6})(?:\.|$)/g`

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

### Dev and engine build versions collapsed before telemetry

Internal build versions get trimmed before metrics are sent, so daily builds report one value.

**What**

A `version` field matching a dev or engine build pattern has its `.tNNN.shaXXXX` suffix stripped before the metric is emitted, leaving `x.y.z-dev.YYYYMMDD`.

**Details**

- Runs alongside the existing metric normalisations that collapse `mcp__*` tool names to `mcp` and unknown model names to `other`.
- The effect is lower per-build cardinality, since every dev or engine build of the same day now reports one value rather than one per commit.
- Not gated; applies to all builds that emit metrics.

**Evidence**

`(?:dev|engine)`

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

### Telemetry values routed through explicit sanitiser wrappers

Telemetry values now pass through marker helpers that declare them safe; nothing sent actually changed.

**What**

Several telemetry values are now tagged at the call site with marker helpers that declare them safe to emit, with no change to what is actually sent.

**Details**

- Three new identity-function helpers: one for nullable values coerced to string, one for joining an array, and one that validates each array element against the ID pattern `^[A-Za-z0-9_-]{1,128}$` before joining.
- The plugin telemetry id now passes through one of these wrappers. That id is still a sha256 of `plugin@version` salted with a fixed string and truncated to 16 hex characters.
- The hashing is unchanged and the wrappers return their input unmodified, so there is no runtime behaviour change in this build.

**Evidence**

`claude-plugin-telemetry-v1`

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

## Removed

### Memory push-suppression state removed

Failed memory pushes no longer stay suppressed for your whole session; retries resume normally.

**What**

The per-scope latch that stopped team and user memory push retries for the remainder of a session is gone; suppression is now tracked only on individual stores.

**Details**

- `pushSuppressedReason` and its log line "suppressing retry for the rest of this session" are removed
- the unlink-clears-suppression handling and the scheduler skip that consulted the latch are removed with it
- `tengu_team_mem_push_suppressed` is now emitted only from the multi-store path

**Evidence**

`tengu_team_mem_push_suppressed`

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

### Org public-projects memory kind dropped

The org public-projects memory option no longer appears when choosing where memory is stored.

**What**

The `org-public-projects` memory selection kind is gone from the build, so it no longer appears as a choice.

**Details**

- it is filtered out of the selectable list
- the mount description changed from one pointing at `/config orgMemoryRead` to a plain "public project memory, read-only"
- the `onlyIfAbsent` special-case when persisting an org memory selection was removed

**Evidence**

`public project memory, read-only`

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