# Claude Code v2.1.213

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

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

This release introduces two major new capabilities: native support for Claude Platform on Google Cloud as a first-class provider, and a `/import` command that migrates configuration from OpenAI Codex and Google Gemini CLI into Claude Code. It also adds `CLAUDE_CODE_NO_MODEL_FALLBACK` to lock sessions to a specific model, improves SDK protocol with several new schema fields, and removes the morning brief feature.

## New Features

### Claude Platform on Google Cloud

**What**

Claude Code can now connect to Claude running on Google Cloud infrastructure via `claude.googleapis.com`, using Application Default Credentials or an explicit bearer token provider.

**Usage**

`# Set your GCP project and let Claude Code discover the workspace export ANTHROPIC_GOOGLE_CLOUD_PROJECT=my-gcp-project export ANTHROPIC_GOOGLE_CLOUD_LOCATION=us-east5 # optional, defaults to global export ANTHROPIC_GOOGLE_CLOUD_WORKSPACE_ID=my-workspace # Or provide a full base URL directly export ANTHROPIC_GOOGLE_CLOUD_BASE_URL=https://claude.googleapis.com/v1alpha/projects/... # Enable the Google Cloud path export CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD=true # Skip auth entirely (if your infrastructure handles it externally) export CLAUDE_CODE_SKIP_ANTHROPIC_GOOGLE_CLOUD_AUTH=true` **Details** - Routes to `https://claude.googleapis.com/v1alpha/projects/{project}/locations/{location}/workspaces/{workspace}/invoke` - Authenticates using Google Application Default Credentials (ADC) automatically when no other auth is provided — no API key required - Accepts an explicit `bearerTokenProvider` callback, a `googleAuth` object, or an `authClient` directly - The deprecated text Completions API is not available on this provider - GCP environment variable values for project, workspace, and location are validated to only contain letters, digits, hyphens, and underscores — URL metacharacters that could rewrite the request path are rejected with a clear error - The `anthropicGoogleCloud` provider is now recognized in the provider enum in SessionStart hooks and other SDK schema positions **Evidence** `AnthropicGoogleCloud` client implementation (search for `"Claude Platform on Google Cloud"`, `"https://claude.googleapis.com"`, `ANTHROPIC_GOOGLE_CLOUD_PROJECT`)

### `/import` — Migrate Config from Other AI Coding Agents

**What**

A new `/import` slash command (and `claude import` CLI subcommand) scans for OpenAI Codex and Google Gemini CLI configuration and imports it into Claude Code — MCP servers, slash commands, subagents, skills, and instructions.

**Usage**

`# From the terminal (interactive picker) claude import # auto-detect Codex or Gemini claude import codex # Codex only claude import gemini # Gemini only claude import --dry-run # preview without writing claude import --yes # apply all user-level items without prompting # From inside a Claude Code session /import # scan and list what's importable /import --yes # apply everything without the picker /import --dry-run # show what would be imported` **Details** - Detects Codex config at `~/.codex/config.toml` and `.codex/` (project-level) - Detects Gemini config at `~/.gemini/settings.json` and `.gemini/` (project-level) - Imports MCP servers, slash commands (skills), subagents, and system instructions - Project-level config from `.codex/` or `.gemini/` directories is listed but NOT auto-imported — anyone with repo write access could author those, so they must be reviewed manually - Shell-exec markers (`!{…}` in Gemini, `!`cmd`` in Claude Code) are flagged and require manual porting - Items with no automatic mapping generate a helper skill at `skills/import-to-claude-code/` for follow-up review - Gemini extensions, project-level settings, and subdirectory-organized commands are listed as needing manual review - The `--yes` flag on `claude -p` (headless/scripted) applies the same import that the interactive picker would; on plain `claude -p` sessions without a terminal, it instructs users to run `claude import` from an interactive terminal instead - Codex permission mode `default` maps to `defaultMode: auto` in Claude Code; tool restrictions differ and are dropped with a warning **Evidence** CLI entry point (search for `"claude import [codex|gemini] [--dry-run] [--yes]"`), Gemini config support (search for `".gemini/settings.json"`), Codex support (search for `".codex/config.toml"`)

### `CLAUDE_CODE_NO_MODEL_FALLBACK` — Prevent Model Substitution

**What**

A new environment variable that locks Claude Code to exactly the configured model and disallows any automatic fallback to a different model — including during compaction.

**Usage**

`export CLAUDE_CODE_NO_MODEL_FALLBACK=true claude --model claude-opus-4-5 ...` **Details** - When set, the availability fallback chain collapses to `[primary]` only — no alternate models are tried - Compaction is disabled with the message "Compaction unavailable: CLAUDE_CODE_NO_MODEL_FALLBACK is set and model substitution is disabled · unset it to allow the swap" - If code attempts a model-fallback pivot while the flag is active, a tripwire throws an error identifying the call site (this is a developer safety net, not a normal user-visible error) - The flag is intended for environments where model consistency is a hard requirement (compliance, billing, reproducibility) **Evidence** Tripwire implementation (search for `"CLAUDE_CODE_NO_MODEL_FALLBACK tripwire: a model-fallback pivot was attempted"`, `"CLAUDE_CODE_NO_MODEL_FALLBACK"`)

## Improvements

### Background Sessions: Clearer "No Terminal" Error Messages

Error messages that previously said "in a background session" now say "no terminal is attached to this background session" and include instructions on how to attach. Three affected messages:

- MCP server authentication: "Can't authenticate MCP servers while no terminal is attached to this background session. Attach to it and try again."
- `/install-github-app`: "Can't run /install-github-app while no terminal is attached to this background session. Attach to it and run the command again."
- `/mcp` panel: "Can't open MCP settings while no terminal is attached to this background session. Attach to it and run /mcp again, or use \`/mcp enable|disable|reconnect <server>\` to steer without the panel."

**Evidence**

Background session guards (search for `"no terminal is attached to this background session"`)

### SDK Protocol: `aborted` Field on Assistant Messages

**What**

The SDK wire schema now includes an optional `aborted: true` field on assistant messages that were cut short by an interrupt or abort before the stream completed.

**Details**

- Present when `stop_reason` was never received and content may end mid-word
- Absent on normally completed messages
- Allows SDK hosts to visually distinguish truncated responses from complete ones

**Evidence**

Schema addition (search for `"True when this assistant message was truncated by an interrupt/abort"`)

### SDK Protocol: task-notification `subkind: "scheduled-trigger"`

**What**

The `task-notification` origin kind now carries an optional `subkind` field. When `subkind` is `"scheduled-trigger"`, the turn is the automatic firing of a stored scheduled prompt.

**Details**

- Lets SDK integrations distinguish scheduled task deliveries from other background notifications
- The harness frames scheduled triggers using a `[SCHEDULED TASK - AUTOMATED FIRING OF A CONFIGURED PROMPT]` prefix instead of the generic background-notification frame

**Evidence**

Schema addition (search for `"Present when the delivery is the fired stored prompt of a scheduled task/routine"`, `"[SCHEDULED TASK - AUTOMATED FIRING OF A CONFIGURED PROMPT]"`)

### SDK Protocol: Heartbeat and Subagent-Retry tool_progress Filtering

**What**

The SDK message adapter now silently drops `tool_progress` frames that carry heartbeat or subagent-retry payloads instead of surfacing them as messages.

**Details**

- `tool_progress` frames with `heartbeat: true` or a `subagent_retry` object are now filtered
- These frames exist for session keepalive and retry signaling; passing them to SDK consumers as messages caused confusion
- A debug log entry is written: `[sdkMessageAdapter] Ignoring heartbeat/subagent-retry tool_progress frame`

**Evidence**

Filter logic (search for `"[sdkMessageAdapter] Ignoring heartbeat/subagent-retry tool_progress frame"`)

### SDK Protocol: Plugin Version Field

**What**

Plugins now expose their `version` as declared in `plugin.json` through the SDK wire format.

**Details**

- The version string is emitted verbatim from the plugin manifest (plugin-author-controlled — validate before trusting)
- Omitted when the manifest declares no version
- The `version` field appears in the `plugins` array in two SDK message types: the `system:init` message (sent at session startup listing all loaded plugins) and the `reload_plugins` engine-control response (sent after a plugin reload)
- In both locations the field is typed as `S.string().optional()`, so hosts must handle its absence for plugins that omit a `version` in their manifest
- A separate `plugin_version` attribute is also recorded in telemetry events for marketplace-approved plugins when a skill command is invoked, enabling per-version usage metrics on the server side; third-party plugins emit `"third-party"` as the repository sentinel and no version is sent to telemetry

**Evidence**

Schema addition (search for `"The plugin's version as declared in its plugin.json manifest, emitted verbatim"`); wire positions (search for `u7a` used in `system:init` plugins array and `reload_plugins` response schema)

### SDK Protocol: `matched_ask_rule` in Permission Requests

**What**

When Claude Code escalates a tool call to the host for permission (auto-mode escalation), the permission request now includes a `matchedAskRule` object describing which rule triggered the ask.

**Details**

- Fields: `source`, `toolName`, and optionally `ruleContent`
- Lets SDK hosts make informed decisions about why a specific call was escalated rather than just seeing that it was

**Evidence**

Permission protocol addition (search for `"matched_ask_rule"` in SDK message schema)

### Hook System: `policySettings` Source

**What**

A new hook source type `policySettings` is recognized in the hook source registry. It joins `userSettings`, `projectSettings`, `localSettings`, `pluginHook`, `sessionHook`, and `builtinHook`.

**Details**

- Represents enterprise managed settings delivered out-of-band by an administrator; displayed to users as `"managed"` (short), `"Managed"` (header), and `"enterprise managed settings"` (long form) in the settings/hooks UI
- Unlike `userSettings`, `projectSettings`, and `localSettings`, `policySettings` is always included in the allowed sources list regardless of the `--allow-setting-source` CLI flag (it is force-added alongside `flagSettings` in the source filter)
- `policySettings.disableAllHooks = true` disables all hooks entirely — even plugin hooks — across all sources
- `policySettings.allowManagedHooksOnly = true` restricts hook execution to only `policySettings.hooks`; user, project, and local hooks are silently ignored for the session
- If `qi().disableAllHooks` is `true` but `policySettings.disableAllHooks` is not `true`, hooks from managed settings still run (the setting overrides only non-managed hooks)
- Hooks declared under `policySettings.hooks` are therefore the only hooks an administrator can guarantee will always run; user-side hooks can be suppressed without touching the user's own settings files
- In hook priority sorting, hooks sourced from `policySettings` share bucket 999 with `pluginHook` and `builtinHook`, making them sort after user/project/local hooks in the display

**Evidence**

Hook source labels (search for `"policySettings"` in hook source map at new `TJt` variable); hook resolution logic (search for `Lzi`, `iOe`, `QX`, `allowManagedHooksOnly`, `disableAllHooks`)

### SessionStart Hook: `fork` Source Value

**What**

The `source` field on `SessionStart` hook events now accepts a new `"fork"` value, in addition to the existing `"startup"`, `"resume"`, `"clear"`, and `"compact"`.

**Details**

- Fires when a session is loaded with `forkSession: true` — the code path activated when a skill carries `context: "fork"` or when the fork-subagent experiment (`tengu_copper_fox` / `tengu_fork_subagent_enabled`) spawns a subagent by resuming an existing conversation under a new session ID
- The hook JSON payload is `{ hook_event_name: "SessionStart", source: "fork", agent_type: <string>, model: <string>, session_title: <string> }` — same fields as `"resume"` events
- Unlike `"clear"` and `"compact"`, a `"fork"` event does propagate the session title returned by the hook (same behaviour as `"startup"` and `"resume"`): the title is stored so the forked session can be labelled distinctly
- The new session ID passed in the hook is the forked child's fresh ID (not the parent's); the hook fires after the fork's conversation history is loaded but before the first turn runs
- Skills with `context: "fork"` are excluded from being re-dispatched inside the fork they created, preventing recursive expansion
- Hook output (additional context messages, `reloadSkills`, `watchPaths`) works identically to other `SessionStart` sources — hooks can inject context or block the fork by returning a blocking error

**Evidence**

Schema enum (search for `"fork"` in SessionStart hook schema alongside `"startup", "resume", "clear", "compact"`); fork dispatch (search for `forkSession`, `x$e("fork"`, `"fork" : "resume"`, `aye = "fork"`, `tengu_copper_fox`)

- Flag `tengu_copper_fox`: Off in both readings (read for one account on one subscription tier against v2.1.213; 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.

### Docker: Additional Global Flag Pass-Through

**What**

Several additional Docker CLI global flags are now recognized and passed through in the Bash safety analysis, preventing them from being treated as suspicious when used before a subcommand.

New flags recognized: `-r`, `--url`, `--connection`, `--identity`, `--remote`, `--module`, `--out`

**Evidence**

Docker argument list (search for `"--connection"` in Docker flags array)

### Shell Safety: Command Length Limit

**What**

Very long Bash commands now bypass static analysis rather than attempting to parse them, avoiding edge cases where the parser could diverge from what the shell sees.

**Details**

- Commands exceeding the length limit return `{ behavior: "passthrough", message: "Command too long for read-only analysis" }`
- The command is still executed; only the safety pre-flight parse is skipped

**Evidence**

Length check (search for `"Command too long for read-only analysis"`)

### Shell Safety: Improved Redirect Analysis

**What**

The Bash parser now validates redirect nodes for additional dangerous patterns that could hide behavior from the safety classifier.

New cases detected:

- `fd-variable assignment` — a redirect that uses a shell variable (`{var}>`) as the file descriptor; this modifies the variable as a side effect
- `close-fd redirect followed by a word` — `>&-` or `<&-` followed by text that bash would pass as a hidden argument to the command
- `redirect target starting with -` after `>&` or `<&` — bash treats the dash as a close-fd operator and passes the remainder as an argument

**Evidence**

Redirect analysis (search for `"Redirect uses"`, `"fd-variable assignment"`, `"Close-fd redirect is followed by a word"`)

### Shell Safety: Test Command Gap Analysis

**What**

The Bash safety parser now checks test commands (`[[ ... ]]`) for unparsed bytes between children or after the last child, which would indicate the parser missed content the shell will actually see.

**Details**

- A new recursive function `dnu()` walks `test_command` AST nodes and their four nested compound-expression node types: `unary_expression`, `binary_expression`, `negated_expression`, and `parenthesized_expression`
- For each node the function tracks a byte-position cursor from `startIndex` to `endIndex` and measures the gap before each child and after the last child; gaps that contain only whitespace (spaces, tabs), backslash-newline continuations, or (in bash mode) comment characters are allowed
- **Between-children gap**: if the bytes between two adjacent parsed children are non-trivial, the function returns `{ kind: "too-complex", reason: "Test command has unparsed bytes between children — parser dropped content that shell will see" }`
- **After-last-child gap**: if bytes remain between the last child's `endIndex` and the node's own `endIndex`, the function returns `{ kind: "too-complex", reason: "Test command has unparsed bytes after its last child — parser dropped content that shell will see" }`
- **Child out-of-bounds**: if a child's span extends outside the parent's span, the function returns `{ kind: "too-complex", reason: "Test command child extends past the node span — gap byte accounting is untrustworthy" }`
- All three cases produce a `"too-complex"` result; the safety classifier treats `"too-complex"` as inability to determine read-only status, so the command is not automatically approved — the user sees the standard "approve this command?" prompt rather than a silent pass-through
- This closes a semantic gap where a tree-sitter parse failure inside `[[ ]]` could have caused the classifier to analyse a truncated/simplified version of the condition and incorrectly allow a command that the shell would evaluate differently

**Evidence**

Test command validation (search for `"Test command has unparsed bytes between children"`, `"Test command has unparsed bytes after its last child"`); gap-check implementation (search for `dnu`, `Zru`, `unu`)

### OTEL: Configurable Content Truncation Limit

**What**

The length at which telemetry content is truncated is now configurable via environment variables, and respects standard OpenTelemetry limit variables.

**Usage**

`export CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH=8192 # Claude Code specific cap # Or use standard OTel variables (Claude Code takes the minimum): export OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT=4096 export OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT=4096 export OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT=4096` **Details** - Previously the limit was hardcoded to 60 KB - The truncation message now includes the actual limit: `[TRUNCATED - Content exceeds NKB limit]` **Evidence** Configurable limit implementation (search for `"CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH"`, `"OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT"`)

### Host OTLP Telemetry for SDK Integrations

**What**

SDK host integrations can now emit custom telemetry events into Claude Code's OTLP pipeline using a `host.*` namespace. The system includes rate limiting and deduplication of error messages to avoid log spam.

**Details**

- Events must follow the naming pattern `host.<lowercase.dotted.name>`
- Attributes must use `snake_case` keys with string values
- Payload is validated and rejected if it uses reserved namespaces (`claude_code.*`) or has invalid shapes
- Rate cap: 240 tokens, refilling over time
- Error categories: `bad_envelope`, `bad_event_name`, `bad_attributes_shape`, `too_many_attributes`, `bad_attribute_key`, `bad_attribute_value`, `payload_too_large`, `emit_failed`

**Evidence**

OTLP routing implementation (search for `"host OTLP event dropped"`, `"host OTLP event route error"`, `"host OTLP event emit failed"`)

### Org Memory: Silo and Grouping Selection [Gradual Rollout]

**What**

The org memory system (controlled by `tengu_haze_glass`) now supports selecting specific memory silos or groupings within an organization, rather than mounting all granted stores.

**Details**

- Controlled by the `tengu_moth_lantern` feature flag
- Users can have a stored memory selection (persisted in settings as `orgMemorySelection`) that pins them to a specific silo
- If the selected silo is no longer in the grant, the system degrades gracefully to the full grant
- The `orgMemorySelectionAccount` field validates the selection belongs to the current account

**Evidence**

Silo selection logic (search for `"silo_id"`, `"grouping_id"`, `"selection_degraded"`, `"tengu_moth_lantern"`)

- Flag `tengu_haze_glass`: Off in both readings (read for one account on one subscription tier against v2.1.213; 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_moth_lantern`: Not enough to say (read for one account on one subscription tier against v2.1.213; 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.

### Usage URL Updated

**What**

The URL shown when users reach their usage limit now includes a tracking parameter.

Old: `claude.ai/settings/usage` New: `claude.ai/settings/usage?from=cc_cli_limit_message`

This change is internal but users will see the updated URL in limit messages.

**Evidence**

URL constant (search for `"claude.ai/settings/usage?from=cc_cli_limit_message"`)

### Memory System: Multiple Team Mount Directories

**What**

The memory system's prompt generation now supports multiple team memory directories with per-mount read/write modes, instead of a single team directory.

**Details**

- The `teamMounts` parameter replaces `teamDir` in internal memory prompt construction
- Each mount can be individually read-only or read-write
- Prompt text accurately describes which directories are writable vs. read-only

**Evidence**

Memory prompt builder (search for `"read-only — do not write there"` in new memory prompt logic)

## Bug Fixes

- Shell redirect parser no longer misses content between children in test commands — the gap-byte accounting check is now applied to `test_command` nodes (search for `"Test command comparison is missing its right-hand side"`)
- The `set_permission_mode` engine control now validates the mode value and returns a descriptive error for unrecognized modes instead of silently failing (search for `"[engine] set_permission_mode rejected — unrecognized mode"`, `"Cannot set permission mode: must be one of"`)
- GCP environment variable values containing URL metacharacters (`:`, `/`, `\`, `?`, `#`, `@`, whitespace) now raise a clear error instead of silently corrupting the request path (search for `"GCP path-segment env var failed the charset gate"`)
- Error codes `ENODEV`, `ENOMEM`, `EUNKNOWN`, `UNKNOWN`, and `Unknown system error *` are now treated as retryable I/O errors (previously only `EDEADLK`, `EINTR`, `ENXIO`, `ECANCELED`, `ENEEDAUTH`, `ESTALE` were) (search for `"ENODEV"` in retryable error check)
- Frontmatter parser now detects ambiguous `---` closing delimiters (a value containing `"---"` could cause part of the block to be read as body) and reports a `rewriteHazard` warning instead of silently truncating (search for `"the closing --- is ambiguous"`)

## Removals

### Morning Brief Feature Removed

The morning brief feature (`CLAUDE_CODE_ENABLE_MORNING_BRIEF`, `CLAUDE_CODE_MORNING_BRIEF_PROMPT`) has been removed. The environment variables no longer have any effect. This was a limited-availability feature that has been deprecated.

**Evidence**

Removal of `CLAUDE_CODE_ENABLE_MORNING_BRIEF` and `CLAUDE_CODE_MORNING_BRIEF_PROMPT` from environment variable registry (confirmed absent in new version, confirmed present in old version via string diff).
