Claude Code v2.1.289: deny rules now apply to symlinked reads and compound commands, plus agent.spawn for plugins#
Published by Alex Sorokoletov
Part of the Claude Code Version Tracker series. | Official Env Vars | Official Changelog
Claude Code v2.1.289 makes deny and ask rules hold where they used to slip: a nested part of a compound shell command, a Bash command behind an environment-variable prefix such as TZ="$HOME" rm -rf build, and a file reached through a symlink. For plugin authors it adds an agent.spawn hook for teammates with one agent id across hook events. It follows v2.1.288.
Permission rules that now hold#
Several deny and ask rules were being skipped in cases where they should have applied.[1] On managed machines, a deny or ask rule on a nested part of a compound shell command no longer gets overridden by a user-installed mod's approval. Read deny rules now apply to files that are @-mentioned, changed, or selected in the IDE through a symlink; the check resolves the real path before it decides. Under sandbox auto-allow, Bash deny and ask rules now catch a command behind an environment-variable prefix whose value is expanded (TZ="$HOME" rm -rf build), and a command preceded by a bare variable assignment.
Two more fixes stop a plugin from reaching past managed settings. A user-installed plugin can no longer rewrite the descriptions of an organization-managed MCP server's sign-in tools, and claude plugin validate no longer skips a plugin when its folder also holds a marketplace manifest.
agent.spawn for teammates#
The one new capability for plugin authors is an agent.spawn hook.[1] A plugin can see and shape a teammate spawn as it happens, and a single agent id now follows that agent across hook events (the resumable agent id the spawn returns). $.agent.list() reports idle and waiting states alongside the running ones.
| Plugin API | What It Does |
|---|---|
agent.spawn hook | Runs when a teammate is spawned, letting a plugin observe or reshape the spawn. The engine refuses a rewrite into a spawn a permission rule would block, a retype into an agent that does not run as a teammate, a cwd set on a spawn isolated in a worktree, and backgrounding an in-process teammate. |
$.agent.list() | Now reports idle and waiting states in addition to running agents, so a plugin can tell which teammates are working and which are blocked. |
ui.fault | Raised to the owning plugin when a Client it drew fails while on screen, carrying the failure reason as text, so the plugin can draw a replacement view instead of the whole mod going down. |
Mods that fail without ending the session#
A large part of the release keeps a misbehaving mod from taking down the interface.[1] A mod's Client that fails while drawn now fails alone and raises ui.fault, instead of taking down everything the mod drew around it. Supervised and background sessions no longer end when a mod's on-screen handler throws asynchronously, and a session no longer exits with an interface error when a value a mod's ui.render hook wrote makes a row throw; the engine draws its own row instead.
Other rendering fixes: a Box with a border style the terminal does not know no longer freezes or force-quits at launch; plugin panes that drew nothing for a localhost address, an @ in the path, an uppercase host, or a file: path now draw; and right-aligned content in a pane or band no longer draws under the close mark or [-], which now keep one column in from the terminal's edge. Installed mods that failed to load in the first session after an upgrade now load.
Terminal and artifact rendering#
The terminal no longer freezes on short code blocks with many unclosed <script> tags or deeply nested ${ substitutions, and published artifact pages no longer freeze or crash the reader's browser tab on the same kind of code block.[1] Large files open faster in a plugin code pane because the highlighted view is laid out once at its final width.
What These Tell Us#
The permission fixes shorten the distance between where a rule is written and where it is enforced: across the parts of a compound command, across an environment-variable prefix, and across a symlink to the real file. On managed machines, that enforcement now also holds against a user-installed mod or plugin that would otherwise reach past it.
The plugin interface is being hardened so one plugin's failure stays contained. A failing Client raises ui.fault for its own plugin, an async throw in a handler no longer ends a background session, and an unknown border or an unparseable value degrades to a plain draw. A server-controlled gate (tengu_fresh_heron) governs whether a plugin's hooks module loads, with a load timeout and a rule that no hooks module loads in a shared multi-tenant server process.
Set against v2.1.288, this build is about the boundary a plugin runs inside: what its hooks may reshape, what its failures may reach, and which rules hold when it asks.
Sources#
- Claude Code Official Changelog, v2.1.289 release notes
This analysis is conducted for educational and research purposes under fair use principles. All trademarks and software referenced belong to their respective owners.
Related Versions#
- Claude Code v2.1.286: model-refusal fallback, permission-prompt counts, safer credential logs
- Claude Code v2.1.285: WebFetch off switch, provider allowlists, plugin configure
- Claude Code v2.1.284: Sonnet 5.5 and auto mode as the default
- Claude Code v2.1.283: exact model allowlists, model deny rules, and grouped gateway requests
- Claude Code v2.1.269: plugin eval reports and Bash command diffs
Previous Claude Code analysis: v2.1.288 (October 2, 2026)
All 63 Claude Code analyses: Claude Code Version Tracker · Official Claude Code changelog