Verified 2026-08-05
Extending · 27 rules

Will my plugin's hooks and scripts run in Cowork?

Yes — plugin hooks do fire in Cowork. The determinant is where the plugin was installed from: a plugin installed through the Cowork application is visible to Cowork tasks, and one installed through the CLI's own path is not, because they occupy different namespaces. The plugin-root token resolves differently depending on which consumer expands it, so use it for file-tool reads and discover the mounted path at runtime for anything you shell out to.

Do not use a hook to export environment variables for your skill's shell commands.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.MeasuredObserved live in a real session, with a control where noted.

Hooks run, but the environment they modify is not the environment those commands see. This is a common way a plugin appears broken in Cowork while working in the CLI: the hook fires, the export succeeds, and the value is simply absent downstream.

As of the 2026-08-05 capture · Desktop 1.25927.0 · not individually re-checked since this rule last changed

Expect plugin hooks to fire in Cowork.

FRICTIONIt works, but costs the user an extra prompt or step.MeasuredObserved live in a real session, with a control where noted.

Hooks shipped by a plugin do run in Cowork, in both the local and the cloud lane: in live checks, prompt, before-tool, after-tool and stop hooks fired on both lanes, and the session-start hook on the local lane. Two things make it look otherwise. A plugin installed where Cowork tasks do not look appears to have no hooks at all. And a hook that succeeds without printing anything leaves no trace in the session's record, so check a side effect it produces rather than the record.

Caveat: In a new cloud session the session-start hook did not fire (checked live); in an earlier instrumented run it fired only when the session resumed. Do not depend on session-start work having happened before the first turn there.
Caveat: A stop hook that blocks shows the user nothing in the app: no notice, no error — the first reply stays visible and the corrected one appears beneath it. If your hook blocks, make the corrected reply self-explanatory.
Caveat: A prompt hook that blocks does stop the prompt in both sandboxes, but in the remote sandbox the user sees nothing: no reply and no notice. Locally the app shows the reason, labelled as an organization policy.

Checked live 2026-09-28 · Desktop 2.9939.2 · agent 2.1.281

Match the shell under both of its names in a before-tool hook.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised.

The shell tool is called one thing in the command-line tool and in a cloud session, and another in a local session. The agent translates between the two when matching hooks only if the session carries a mapping between the names, which current desktop builds pass but older ones and other launchers may not. Listing both names in the matcher works either way, on both lanes.

Caveat: Checked live on both lanes: in a cloud session the command-line name matched the shell; in a local session the command-line name, the local name and a match-all each fired for the same shell call.

Checked live 2026-09-25 · Desktop 2.9939.2 · agent 2.1.281

Ship every script your hook commands name inside the plugin: on Linux a missing script can block tool calls.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised.

When a hook runs a script that is not there, the exit code depends on the shell. The usual shell on Debian and Ubuntu exits with 2, the code a hook uses to block; the macOS shell exits with a different code that does not block. Claude Code recognises the missing-script case only for stop-type hooks and a plugin's prompt hook, and shows a warning with a hint to reinstall the plugin instead of blocking. For every other event, a before-tool hook included, the 2 is taken as a real block, so every tool call the hook matches is refused. A hook that only warns when tried on a Mac can block on Linux.

Caveat: The agent's handling is read from the shipped agent; a blocked tool call from a missing script was not reproduced in a live session. The usual shell was measured as dash in a cloud session's container and in the Cowork virtual machine on the user's computer; hooks of a local session on a Mac run under the macOS shell.

Re-read from the shipped code 2026-09-27 · Desktop 2.9939.2 · agent 2.1.281

To get a personal skill into Cowork, upload it as a skill rather than packaging it as a plugin.

FRICTIONIt works, but costs the user an extra prompt or step.MeasuredObserved live in a real session, with a control where noted. Both sandboxesEstablished for both sandboxes. Can change without a version bumpServed from Anthropic's side, so it can change without an app update.

A skill uploaded from the Skills section of the desktop application's Customize screen becomes part of the user's account. Within about a minute it reached the command-line tool, a cloud session and a local one, each time with its full instructions, its scripts and its reference files, and the scripts ran. Zip the skill with its folder at the top level; the uploader refuses a zip that also contains a plugin manifest.

Caveat: Measured on one account, on 2026-09-27. In both Cowork sandboxes the scripts ran under a Linux shell, and in the local one the skill's files were read-only.
Caveat: Skills belong to an organization: upload while the application is in the organization the user works in.

Checked live 2026-09-27 · Desktop 2.9939.2 · agent 2.1.281

A user can create and change their own skills by asking Claude in Cowork; point them there before suggesting a re-upload.

FRICTIONIt works, but costs the user an extra prompt or step.MeasuredObserved live in a real session, with a control where noted. Both sandboxesEstablished for both sandboxes. Can change without a version bumpServed from Anthropic's side, so it can change without an app update.

In a Cowork task that is allowed to save skills, asking Claude to create a skill, or to change one the user already has, produces a card the user confirms; the result is saved to their account and reaches their other sessions and the command-line tool within about a minute. A change replaces only the skill's instructions and keeps its other files, so an uploaded skill with scripts can be edited this way. A new skill created in chat has instructions only. One that needs scripts or reference files can be uploaded, or, in a cloud session, packaged as a .skill file and sent to the user: the Save skill button on that file installs every file.

Caveat: Skills belong to an organization. A skill uploaded while the application was in one organization is not visible to, and cannot be changed from, a session in another.
Caveat: Whether a session may save skills depends on the organization's settings and plan: an admin can turn it off, and the access list can block it by plan tier or entitlement, so not every user will be offered it. The live behaviour was measured on one account, 2026-09-29; the gating is read from the desktop application's code.
Caveat: The Save skill button on a sent .skill file was seen in one cloud session on one account (2026-10-01), relayed from a sibling session.

Checked live 2026-09-29 · Desktop 2.9939.4 · agent 2.1.284

Do not rely on a prompt-blocking hook to tell the user why their message was stopped.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.MeasuredObserved live in a real session, with a control where noted. Both sandboxesEstablished for both sandboxes. Can change without a version bumpServed from Anthropic's side, so it can change without an app update.

A plugin hook that blocks a prompt works in Cowork: the turn stops and the model never sees the message. On the user's own machine the app shows the hook's reason, though it presents it as an organization policy. In the remote sandbox the user sees nothing at all: their message stays, with no reply and no notice. If the user needs to know, block at the tool level instead, where the model sees the denial and can explain it.

Caveat: Measured with three blocking forms on each sandbox, on one account, 2026-09-27 and 2026-09-28.

Checked live 2026-09-28 · Desktop 2.9939.2 · agent 2.1.281

Do not pass a credential to a plugin's server through an environment placeholder.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.MeasuredObserved live in a real session, with a control where noted. Both sandboxesEstablished for both sandboxes.

In a plugin's server configuration, only a few standard variables are filled in: the home folder, the search path, the shell, the user and the terminal, plus the plugin's own root. Any other placeholder reaches the server as its literal text in both Cowork sandboxes, even when the variable is set on the user's machine, and a placeholder with a default gets the default instead. The server still starts, so the failure shows up later as rejected requests. Have the server read its credential itself, for example from a file in the user's home folder or from the system keychain, and treat a value that still looks like a placeholder as missing. A server the user adds to the desktop application's own MCP configuration, with the key in that server's environment block, does receive it.

Caveat: Measured on one machine with a variable set in the desktop application's environment. In the remote sandbox the refusal is also the desktop application's documented behaviour in its log; on the user's own machine the mechanism behind it was not determined.

Checked live 2026-09-28 · Desktop 2.9939.2 · agent 2.1.281

Install a plugin through the Cowork application if you want Cowork tasks to see it.

LOUDYou get an error straight away. Annoying, but you'll notice.MeasuredObserved live in a real session, with a control where noted.

Plugins installed through the CLI's own installation path land in a namespace that Cowork tasks never read, so the plugin appears installed and is simply invisible. This is the usual explanation for 'my plugin works in the CLI but not in Cowork'.

As of the 2026-08-05 capture · Desktop 1.25927.0 · not individually re-checked since this rule last changed

Do not write into your plugin's own directory at runtime.

LOUDYou get an error straight away. Annoying, but you'll notice.MeasuredObserved live in a real session, with a control where noted.

Plugin content is mounted for reading. Treat everything shipped with the plugin as immutable during a session and put generated state in the outputs location.

As of the 2026-08-05 capture · Desktop 1.25927.0 · not individually re-checked since this rule last changed

Expect plugin files to be present as a mounted directory, and find it rather than hardcoding it.

LOUDYou get an error straight away. Annoying, but you'll notice.MeasuredObserved live in a real session, with a control where noted.

Plugin content is made available to the session as its own mount. The exact location embeds identifiers that vary per plugin and per install source, and the shape has changed across releases, so discovery at runtime is the only stable approach.

As of the 2026-08-05 capture · Desktop 1.25927.0 · not individually re-checked since this rule last changed

Use ${CLAUDE_PLUGIN_ROOT} for file-tool reads, and discover the mounted path at runtime for anything you execute.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised. Local sandboxApplies to the sandbox on the user's own computer. Not established for the cloud one.

${CLAUDE_PLUGIN_ROOT} is a token that expands to your plugin's directory — but it expands differently depending on what expands it and which sandbox the session is in, and one expansion names a location the other cannot reach. Reading your plugin's files through the file tools with the token is reliable only where the runtime replaces it for you, which it does in the files it loads as definitions — your skill's own body, a command, a hook. There the token is swapped for a real path before the model ever sees it. It is not replaced in a file your skill merely reads later while it runs: a reference file that instructs the model to use the token hands over the token itself, which names nothing. This is the difference between text the runtime loads and text your skill reads, and it has cost a real skill a silent fallback to a degraded path. Executing a script by that path is not: in the local sandbox the substituted value names a host staging directory the shell cannot see. And do not test for the obvious failure — it is not the one you get. The token is often *present* in a shell, holding a different plugin's directory: hooks can write environment for later shell commands, each hook is given its own plugin's location, and the last one to write wins. So a shell reading it typically fails as a plausible wrong path, not as a blank — checking whether it is empty comes back clean and sends you looking in the wrong place. For anything you execute, do not read the token at all: run the script by the absolute path your skill body was given, and where the shell cannot open that path, locate the plugin's directory from the shell's side at run time.

Caveat: Resolution differs between the two sandboxes; this rule is written to be correct in both.
Caveat: The reliable case is narrower than it reads: substitution happens in the files the runtime loads, not in files your skill reads at run time. If your skill points the model at a reference file, put a real located path in it rather than the token, and have the step that cannot find what it needs stop and say so — a resolution that fails quietly is indistinguishable from a feature that was never shipped.

As of the 2026-08-14 capture · Desktop 1.30096.1 · not individually re-checked since this rule last changed

Do not ship a launcher in your plugin's bin/ if the plugin may go through an organization; run your scripts by path.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.MeasuredObserved live in a real session, with a control where noted.

Claude Code does put every enabled plugin's bin/ directory on the shell's command path, in the shell's own terms, and on a GitHub or local command-line install a launcher you commit there works as a bare command. It is still not a channel to build on. Organization distribution on claude.ai rejects any plugin that has a top-level bin/ — through marketplace sync and direct upload alike — because those entries reach the command path without appearing on the administrator's approval screen. The plugin validator does not warn about it, even in strict mode, and the administrator sees only a generic "Marketplace sync failed" message; the real reason is in the desktop app's log. And in Claude Cowork on the desktop, no launcher an author committed has been seen on the shell's command path: the only populated directory there was one the runtime wrote itself. So run bundled scripts by path: use the absolute path your SKILL.md body is given, and where the shell is a different mount of the same files (Cowork on the desktop), locate the script from the shell's side with a search.

Caveat: The rule reads the directory beside your plugin manifest. A bin/ inside a skill directory, or at the outer root of a marketplace repository, is not what it checks, and an ordinary project CLI elsewhere in a repository is fine where it is.
Caveat: If you ship a launcher anyway for a plugin that will only ever be installed from GitHub or locally, keep a fallback that finds your files without it, and commit the execute bit: the plugin directory is read-only at run time.
Caveat: If your plugin's manifest declares a command-line tool of its own, the runtime writes a launcher of that exact name into that same directory in Cowork; whether declaring one clears organization intake has not been tested.

Checked live 2026-09-01 · Desktop 1.40609.1

${CLAUDE_SKILL_DIR} works in your SKILL.md body and nowhere else.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised. Local sandboxApplies to the sandbox on the user's own computer. Not established for the cloud one.

It is the variable most skill authors reach for to point at their own bundled files, and in the body of SKILL.md it does exactly what you want: it is replaced with a real absolute path before the model ever reads the text, so a file tool opens the right file. There are four places it will quietly do nothing instead. In a shell command it is not set at all, so it becomes empty and the command addresses a path at the root of the filesystem. Written without braces it is not recognised and survives into the loaded text as the literal characters you typed. And in a reference file your skill opens while it is running — as opposed to the definition the runtime loads at startup — it is never substituted, so it arrives as the characters you typed. And in a command file — which is definition text the runtime loads, and where the other tokens are replaced normally — this one alone is skipped. One rule covers all four: it belongs to the body of a skill specifically, not to definition text in general, and not to anything that can be looked up later. Once a path has to reach a shell, the model passes an absolute path it has already resolved — and where the shell is a different mount of the same files, a path it located from the shell's side.

Caveat: It is also skipped when the skill has no directory of its own to resolve, in which case the token survives unchanged rather than raising anything.
Caveat: The other tokens replaced nearby do NOT hold on the same terms, and assuming they do is the trap. The session id, the effort level, your plugin's directory and the project directory are all replaced without that restriction — including inside a command file, where this one is not. So the answer is specific to the token, never to the kind of file: a command is a file where substitution demonstrably happens and this token is still left alone.
Caveat: All of this describes Claude Code on your own computer. In a cloud conversation it depends on whether the conversation's session exists when the skill is invoked. Once it does, Claude Code's own plugin loader expands the skill and fills the tokens in, as it does locally. Before it does, for example when the skill is the first message of a new conversation, the body arrives with ${CLAUDE_SKILL_DIR}, ${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA} and $ARGUMENTS left exactly as written, together with a 'Base directory' line naming a folder that does not exist (measured on 2026-10-02 on the web and the desktop app). Only the skill's own text is affected: the plugin's agents, hooks and files work as usual in that same turn. If your skill runs scripts in the cloud, give the script path a fallback that finds the skill's folder when the token comes through unreplaced.

As of the 2026-08-14 capture · Desktop 1.30096.1 · not individually re-checked since this rule last changed

Your skill's description is the only thing that decides whether it gets used.

FRICTIONIt works, but costs the user an extra prompt or step.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised.

Everything else you write is read after that decision, if it is read at all. Two frontmatter settings can stop a skill being reachable before any of it is considered: one restricts it to loading only when the model touches files matching a pattern, so a question that touches no files quietly gets nothing; another tells the model to ask you to run the skill rather than using it. There is also a second field that looks made for describing when to reach for a skill, and it does feed the decision — but only in the terminal. The desktop listing has no handling for it at all, and other tools that read skills ignore it. So put what makes your skill findable in the description, keep it self-sufficient, and treat the other field as reinforcement you could delete without loss.

Caveat: Being selected is not the same as being read: a model can pick up a skill and still answer from what it already believes without opening the body, so anything load-bearing needs to survive a shallow read.
Caveat: The desktop finding is from a single application build, the newest available when it was checked; older builds were not examined.

As of the 2026-08-14 capture · Desktop 1.30096.1 · not individually re-checked since this rule last changed

If your plugin ships an MCP server, make it a stdio command with no user-configuration placeholders, and expect it to run on the user's machine rather than in the sandbox.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised. Remote sandboxApplies to the cloud sandbox. Not established for the one on the user's computer.

In the remote sandbox the session itself does not start plugin MCP servers. The Claude Desktop app on the user's machine starts them — the same bridge it uses for servers in its own MCP configuration — and announces their tools into the session, so every call travels back to the user's machine and is gated by the Desktop app's own tool-approval prompt, not by anything in your skill or your plugin's permission rules. Only a stdio command qualifies: a server declared by URL is skipped, so an HTTP-only endpoint needs a stdio-to-HTTP shim (the mcp-remote package is the one observed working); a server whose configuration references plugin user-configuration placeholders is skipped; and one bundled Anthropic plugin is excluded from the bridge and served from inside the sandbox instead. The Desktop app must stay open for the whole session — close it and the tools vanish mid-conversation — and after a server outage the bridge's own reconnect decides when they come back, measured once at seventeen minutes.

Caveat: The plugin-server path is read from the shipped application and has not been exercised live. The same bridge's path for servers in the Desktop app's own configuration was observed live — a LAN-only device driven from a cloud session on 2026-09-18 through a stdio shim, in another project's run.
Caveat: The plugin half sits behind a server-side switch that was on at the last capture, and behind organisation policy that can disable local MCP bridging entirely or restrict it to an allowlist. Either can turn it off with no version change.
Caveat: In the local sandbox plugin MCP servers are started differently and this rule's constraints were not measured there.
Caveat: Measured: in the remote sandbox the desktop application fills in only a few standard variables in a plugin server's configuration and leaves every other placeholder as written, and it leaves a server that uses a plugin-setting placeholder unstarted, reporting why only to the session's device-information tool.

As of the 2026-09-05 capture · Desktop 1.46388.4 · not individually re-checked since this rule last changed

Do not name a server memory in the desktop application's MCP configuration; a plugin's own memory server is safe.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised. Local sandboxApplies to the sandbox on the user's own computer. Not established for the cloud one. Can change without a version bumpServed from Anthropic's side, so it can change without an app update.

Current Cowork desktop builds install their own server under the name memory, backed by the user's cloud memory. A server configured under that same name in the desktop application's MCP configuration — a common name for the reference memory server — is replaced by it, and never starts. The only trace is a line in the desktop application's log. A server that ships inside a plugin is not affected, because plugin servers are loaded under a name that includes the plugin's, so it runs next to the cloud one. Its tools are distinct from the built-in memory tools, but the model now sees two sets of memory tools; a distinctive name avoids that confusion.

Caveat: The replacement happens only when the cloud memory server is switched on, which is gated server-side (on at the 2026-09-23 through 2026-09-29 captures, off at the 2026-10-07 capture) and requires a first-party account that is not HIPAA-restricted.
Caveat: The plugin case is established from the shipped code, not a live run.

Re-read from the shipped code 2026-09-23 · Desktop 2.7032.0 · agent 2.1.280

Do not build an Artifact that calls the user's local MCP servers. It cannot today, even inside the desktop application.

LOUDYou get an error straight away. Annoying, but you'll notice.MeasuredObserved live in a real session, with a control where noted. Can change without a version bumpServed from Anthropic's side, so it can change without an app update.

The desktop application contains a channel that lets a rendered Artifact call the servers in its MCP configuration, and its switch is on. But publishing refuses an Artifact that declares a local server, and the Artifact viewer refuses any call to a local server the Artifact did not declare, before the desktop application is involved. To reach the user's machine, use a skill or a plugin's own server. Collect a secret through the server's own prompt to the user (elicitation), never through a form in an Artifact.

Caveat: Measured on 2026-09-27 on two accounts. Both refusals come from Anthropic's service and viewer, so this can change without an app update.
Caveat: When it opens, the desktop application offers every configured local server to the Artifact and does not check what the Artifact declared. Only Anthropic's side would limit an Artifact to its declared tools.

Checked live 2026-09-27 · Desktop 2.9939.2

Expect a server in the desktop application's MCP configuration to run as two separate processes, and keep no state only in memory.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.MeasuredObserved live in a real session, with a control where noted. Local sandboxApplies to the sandbox on the user's own computer. Not established for the cloud one.

The desktop application starts a server listed in its MCP configuration once for each of its clients: one copy for chat and one for Cowork, each with its own connection and its own memory. A value one copy holds in memory is invisible to the other, and which copy answers depends on where the call came from. Keep shared state on disk or in a service, and make startup safe to run twice at the same time.

Caveat: Measured once, on Desktop 2.9939.2: three starts at launch, two of which completed a connection, one per client. The third never connected and was not explained.

Checked live 2026-09-27 · Desktop 2.9939.2

Ship Cowork skills in a plugin, not in a project's .claude/skills folder.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised. Can change without a version bumpServed from Anthropic's side, so it can change without an app update.

A skill left in a folder's .claude/skills does not load in a local session, as far as the shipped code shows: the desktop starts the agent with user settings only, and project skills load from project settings. In a cloud session that the user grants the folder to, the desktop does upload those skills, but by default only as stubs: the model sees your frontmatter, the body is replaced by a note to run the skill on the user's device, and your scripts never run in the cloud. Past 100 skills in one folder, or 32 MiB in total, skills are dropped with little or no notice. A plugin is the only route that delivers the whole skill on both lanes.

Caveat: The cloud behaviour is controlled by a server-side mode that was set to stubs at the 2026-09-23 capture; it also supports off and full copies.
Caveat: The local-lane conclusion rests on how the desktop configures the agent, not on a live run.

Re-read from the shipped code 2026-09-23 · Desktop 2.7032.0 · agent 2.1.280

Expect your plugin's local MCP server to work in Cowork, and a remote one to be replaced when the user already has that service connected.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.MeasuredObserved live in a real session, with a control where noted. Can change without a version bumpServed from Anthropic's side, so it can change without an app update.

A plugin's MCP server that runs locally over stdio gets its real tools in a local session: the desktop application starts it on the user's machine and hands its tools to the session. Two things replace a server with an empty stand-in that lists no tools. An organisation's MCP policy can do it to local servers and packaged extensions. And a remote server, one reached over HTTP, is replaced when the user already has the same service connected through a claude.ai connector, because the connector then serves it. Earlier desktop releases replaced every remote plugin server while that behaviour was switched on. The desktop application's log records each replacement by name, which is the quickest way to tell which case you are in.

Caveat: The remote-server replacement is switched on server-side; on one account it was observed replacing connector-covered plugin servers daily from 2026-09-15.
Caveat: Observed on one machine: a local stdio plugin server connected with its tools, and several remote plugin servers replaced by matching connectors.

Checked live 2026-09-23 · Desktop 2.7032.0 · agent 2.1.280

Do not write exact MCP tool names for connectors or a plugin's remote servers into your skill.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.MeasuredObserved live in a real session, with a control where noted. Can change without a version bumpServed from Anthropic's side, so it can change without an app update.

In Cowork, the tools of a claude.ai connector are named after the organisation's own record for that connector, an identifier that differed between the two organisations observed, not after the service. A plugin's own server has names built from the plugin's name, but when the organisation also has the same service connected, the plugin's copy is replaced and those names disappear. A skill that spells out either form is tied to one organisation's setup. Describe the tool by what it does and let the model pick the matching tool.

Caveat: Observed on one machine across two organisations of the same user. That a hard-coded name then fails elsewhere is inferred from the names present, not from a skill run, and whether the action part of a tool's name matches between the two routes could not be checked.

Checked live 2026-09-23 · Desktop 2.7032.0 · agent 2.1.280

When testing a plugin you also have installed, check which copy actually loaded.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised.

A copy passed on the command line with --plugin-dir silently replaces the installed plugin of the same name — the whole plugin, skills, hooks and servers alike — and nothing tells you. The installed copy wins instead if the command-line copy is disabled, if an administrator has locked that plugin, if side-loading is turned off, or if the names differ, even only in letter case, in which case both load. The session's list of loaded plugins shows each copy's path; that path, not the plugin's source label, tells you which one you are testing.

Caveat: Cowork itself delivers every plugin this way, and sometimes lists the same plugin twice; which copy then serves a skill was not traced.

Re-read from the shipped code 2026-09-23 · Desktop 2.7032.0 · agent 2.1.280

Put your skill's trigger words in its name, and keep its description short.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised.

The list of skills the model sees has a size budget: about 30,000 characters on current models with their standard 1M context, but only 6,000 to 8,000 when the model runs a 200k context. When a user has more skills than fit, bundled skills keep their descriptions and the rest are ranked by how recently and often they were used; those that do not fit are listed by name alone. A new or rarely used skill is the first to lose its description, and a long description can lose to a shorter one even when it ranks higher. A skill whose name says what it does still gets picked.

Caveat: Most users stay under the 1M-context budget; the limit bites for users with many plugins or on 200k-context models. Users can raise it in settings.

Re-read from the shipped code 2026-09-23 · Desktop 2.7032.0 · agent 2.1.280

Do not mark a hook to run for cloud sessions unless you accept that the session may have written what it runs.

LOUDYou get an error straight away. Annoying, but you'll notice.From binaryRead from a shipped artifact (the app, the agent or the sandbox image). The behaviour itself was not exercised. Can change without a version bumpServed from Anthropic's side, so it can change without an app update.

When a user drives a cloud session from their own machine, the command-line tool can offer that session some of the user's hooks, run on the user's machine. By default only a hook whose script can be fingerprinted and lies outside anything the cloud session can write is offered. A hook marked to run on the device anyway can end up running a script, or a helper it loads, that the cloud session itself wrote. The user still has to consent on that machine, and a fingerprinted script that changes afterwards is refused.

Caveat: Switched off for the account checked on 2026-09-23; this describes the shipped mechanism, not a live run.

Re-read from the shipped code 2026-09-23 · Desktop 2.7032.0 · agent 2.1.280

Do not conclude your hook did not fire because a local session's records do not show it.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.MeasuredObserved live in a real session, with a control where noted.

The agent reports hook activity in its output stream only for session-start hooks unless it is started with an option the desktop application never passes. So a local session's records show session-start hooks and nothing else, even when your other hooks ran. Check your hook's own side effects instead, or reproduce outside Cowork with hook events switched on. Cloud sessions report every hook.

Caveat: Across one machine's local session records every recorded hook event was a session-start event; that other hooks ran unseen is read from the shipped code, not observed.

Re-read from the shipped code 2026-09-23 · Desktop 2.7032.0 · agent 2.1.280

Do not rely on a forked skill's exact output reaching the user.

SILENTNothing errors. Your skill carries on with wrong results, and you only find out by checking.MeasuredObserved live in a real session, with a control where noted.

A skill set to run as a separate sub-task hands back only its final message, and the main conversation then writes its own answer from it — shorter, reworded, and in measured runs usually without the skill's links and caveats. Asking the main model, from inside the skill, to pass something through helps only partly and can make it warn the user about a possible prompt injection. For output whose exact wording matters, run the skill inline, which has no relay step, or write the essential content to a file the skill produces.

Caveat: Measured in 129 headless runs on one machine with one skill; behaviour differs by model — one kept links far more often than another.

Checked live 2026-09-23 · Desktop 2.7032.0 · agent 2.1.280

What is not established

  • Plugin-root resolution differs between the two sandboxes. The plugin-root rule is written to be correct in both, at the cost of being less direct than a sandbox-specific answer would be.