Claude Code 2.1.246: your Bash allow rule may cover far more than it says

One release since 2.1.245, but roughly sixty entries. The most useful one is a new startup warning: a Bash allow rule with its wildcard placed before the subcommand permits much more than what you wrote. Plus a gateway credential that was going to the wrong host, and three cases where the client reported success instead of an interruption.

claude-code changelog security

One release since the last digest: 2.1.246, shipped August 25. No new commands, no new models, about sixty lines that are almost entirely fixes.

A repair release, then. Except that three of those fixes touch things you thought were settled: the real scope of your permission rules, where your secrets end up, and what the client tells you when a command gets cut short. Here is the triage.

A wildcard in the wrong spot

This is the only entry in the release that asks something of you, and it arrives as a startup warning about Bash allow rules whose wildcard sits before the subcommand, such as Bash(git * main).

The changelog justifies the warning in a single clause: those rules "also match options inserted before the subcommand." Put that way, it sounds like a syntax subtlety. The wildcard patterns documentation is far more blunt, and it is the one to read.

The matching principle fits in one sentence: Claude Code matches everything before the first wildcard literally, so those words, and only those, are what constrain the rule. In Bash(git log *), the constraint is git log. In Bash(git * main), the constraint is git. The rule may end in main, but it does not permit "git commands that operate on main." It permits every git subcommand, plus every option placed before it.

The docs prove it in their own table, and the third row is the one that matters:

You writeIt also matches
Bash(git * main)git merge main, git push origin main, git -c core.fsmonitor=<script> diff main

The -c core.fsmonitor= option makes git run a program you name. So a rule written to narrow permissions down to one branch actually opens up arbitrary program execution under git's cover. The docs spell it out: the wildcard "stands in for whatever text is in its place," here the subcommand, "That includes -c, which makes git run a program you name."

The fix is trivial and it is entirely on your side: put the wildcard after the subcommand. Bash(git log *), Bash(git commit *), Bash(npm run *). If you genuinely need to target a branch, write Bash(git log * main): the subcommand is named, and the wildcard now only stands in for git log options.

Two closing notes on this. First, these rules do not all come from you: the permission dialog writes a rule itself whenever you pick "Yes, and don't ask again," which means a settings file quietly accumulates patterns nobody has reviewed. Second, the same release hardens a neighbouring case: Bash permission checks now always require approval for malformed commands that leave a dangling && or ||. The mechanism is laid out in the compound commands section: Claude Code splits on &&, ||, ;, |, |&, & and newlines, then requires a rule to match each subcommand independently. A command whose parse left an orphaned operator was slipping past that split. It now prompts.

Same family: auto mode gets a dedicated tab in /permissions for viewing and editing its classifier rules (the permission modes docs list which actions the classifier actually sees). And tool calls denied in auto mode with a "temporarily unavailable" on very large sessions are fixed: the safety-check deadline now scales with prompt size instead of being fixed.

Your gateway key no longer travels to Anthropic

One line, buried in the fixes, and nobody will notice it:

Fixed telemetry and metrics requests to Anthropic carrying the API key configured for a third-party gateway (ANTHROPIC_BASE_URL); a credential is now only sent to its own host.

You have to unpack it to see the problem. If your organization routes inference through a third-party gateway or proxy via ANTHROPIC_BASE_URL, the key you configure is not an Anthropic key: it is your own gateway's secret. Telemetry and metrics, meanwhile, do go to Anthropic. So a secret meant for one host was being shipped to another.

This is not a third-party-exploitable flaw, and it only affects gateway setups. But it is a secret that left its perimeter, and the changelog says neither which version introduced it nor how long it lasted. If your internal policy treats any credential that leaves its perimeter as compromised, this is the moment to trigger it rather than discover it at audit time.

Still on credentials, a far more everyday fix: when apiKeyHelper returns short-lived JWTs, the first request after an idle period surfaced a visible API error. The behaviour was structural, since the credential management docs note the script is only called again after five minutes or on an HTTP 401: the stale token went out once before being refreshed. Now an expired cached token is refreshed before sending, and 401 and 403 auth errors retry quietly.

Three times an interruption looked like a success

The most interesting thread running through this release is never stated as one: three separate fixes are about interruptions being reported as completions.

The worst one lies to the model, not to you. In headless and remote sessions, an MCP tool call interrupted by an incoming message was reported to the model as "completed with no output" instead of an explicit interrupted error. That distinction is anything but cosmetic: to a model, a tool that returns nothing is a tool that found nothing. Claude was drawing conclusions from an empty result that was not a result at all. On an automated routine running with nobody watching, that turns into a confident report built on thin air.

The second one lies to you. A command interrupted mid-run displayed as "Ran 1 shell command," with no sign at all that it had been cut.

The third one is about subagents. A subagent that stops at its maxTurns limit now returns its output marked as partial, with a hint to continue it via SendMessage, instead of appearing finished. That is the right fix, because resuming was already nearly free: the subagents docs state that a resumed subagent retains "their full conversation history, including all previous tool calls, results, and reasoning" and "picks up exactly where it stopped." Everything was in place except the one thing that mattered, knowing it had stopped early.

Two more entries follow the same logic. Non-interactive sessions (-p, the SDK, cloud sessions) now automatically continue a response cut off mid-stream by a server error, a dropped connection or a stall, instead of ending on an error. And resumed sessions that failed with a 400 on every turn, when the saved history contained tool blocks the Anthropic API does not accept (typically written by a third-party proxy), work again.

The sweep that deleted your worktrees

This one is worth reading twice: the background retention sweep was removing git worktrees under .claude/worktrees/ that you had created yourself, whenever an old background-session record happened to point at them.

A deleted worktree takes everything uncommitted in it. If you use that directory for your own worktrees and have watched folders vanish without explanation, there is your cause.

The rest of background sessions gets repaired alongside: they failed to open after 45 seconds when Claude Code's starting directory had been deleted, when the machine had slept, or on a host slow to start processes. And they failed with "Couldn't start the background service … EACCES" when another Claude Code process was reinstalling the npm package at that exact moment.

/cd becomes a real project switch

Until now, /cd moved the working directory but you kept the previous project's configuration until a restart. That is fixed: the new directory's project settings, hooks, .mcp.json servers (behind the usual approval prompt), skills and agents all take effect right after the move, instead of on --resume.

That turns /cd into an actual context switch rather than a dressed-up cd. Two limits the Cd rules docs spell out and the changelog leaves unsaid: Cd is not a model-invocable tool, so the rules apply only when you run /cd yourself; and adding a single Cd allow rule flips /cd into allowlist mode, where any unlisted target is refused.

Everything else worth a line

  • Much faster transcript on large diffs: a diff containing one very long single line (a base64 string, typically) caused a severe slowdown. Such lines now render truncated with a marker. Relatedly, memory stopped growing with session length in the fullscreen and Ctrl+O views.
  • The Write tool reported "Out of memory" or froze for a long time after overwriting a very large existing file, even though the file had already been written.
  • Mistyped MCP arguments: tool arguments were sent as JSON strings when the parameter's schema is empty ({}), instead of their real type.
  • A misleading MCP permission: tools marked requiresUserInteraction still offered "Yes, and don't ask again," an option that wrote an allow rule the tool then ignored.
  • --strict-mcp-config prompted to approve .mcp.json servers it would never load, which left background sessions waiting at startup.
  • Dynamic workflows: pressing ← or running /background mid-workflow restarted subagents that had already finished. Claude now asks first and says how many subagents would restart.
  • /goal now caps at three the check-ins an idle session starts on long-running background work, per goal. Your next message allows three more.
  • A keybindings.json entry with an unknown action name silently deadened that key. It is now skipped, the default binding keeps working, and a warning is logged under --debug.
  • Markdown rendering: it was disabled for an entire message whenever its first 500 characters contained none, and it ignored + and N) lists and setext headings.
  • Fullscreen: blank transcript after a terminal resize, erratic scrolling when you had scrolled back, and keyboard focus jumping to whatever control sat under the pointer when you clicked only to bring the window back into focus.
  • Plugins: cache creating duplicate SHA-named directories for the same plugin, a doubled <plugin>: prefix in the slash menu, claude plugin update failing on a bare name, installation broken by a UTF-8 BOM in plugin.json, /reload-plugins reporting 0 skills for plugins declaring them under skills/*/SKILL.md, and hook error messages showing a literal ${CLAUDE_PLUGIN_ROOT} instead of the resolved path.
  • Themes: /rename replaced the prompt border colour with the default cyan, and custom diff colours were ignored.
  • /code-review can now be started by Claude on its own on Bedrock, Vertex AI and Foundry, through the Claude apps gateway, and when telemetry is disabled.
  • A nice touch: the end-of-turn duration line now shows the completion time, e.g. ✻ Sautéed for 23s · done 6:05 PM.

What to actually do

One action today: launch Claude Code and see whether it shows you a warning about a Bash rule. If it does, the wildcard is in the wrong place and the rule is wider than you thought you wrote. That is five minutes reviewing your permissions file, and it is the only entry in this release that touches your exposure surface.

If you run through a third-party gateway, add key rotation to your list. For everyone else, 2.1.246 has nothing to learn: it is a repair release, and three of its fixes give the client back the habit of telling you when something went wrong. No reason to delay it.

Pierre Rondeau

Pierre Rondeau

Developer and indie builder. I build products and automations with AI. Creator of Claude Hub.

LinkedIn