Verified 2026-08-05
Filesystem · 9 rules

Why did my skill's output never reach the user?

Cowork runs in two different sandboxes, and they disagree about what 'delivering a file' means. In one, writing the file to the workspace location is itself the delivery. In the other, the location delivers nothing and the file only reaches the user if your skill presents it — and the session filesystem is discarded when the session ends, so an undelivered file is simply lost. Write the deliverable to a stated path, then present it to the user, and say the path either way.

Do not rely on writing a file to a directory as the act of delivering it.

SILENTMeasured

In one sandbox the workspace location is itself the delivery channel and writing there is enough. In the other it delivers nothing: the file exists only inside a session filesystem that is discarded when the session ends, and the user never sees it. A skill written against the first behaviour loses its deliverables silently in the second — no error, no warning, just nothing.

Write the deliverable to a stated path, then present it to the user. If you cannot present it, say where it is.

Producing the file and delivering it are two separate acts. Instruct your skill to present the finished file to the user rather than naming a specific tool to do it with: which delivery mechanism exists depends on the sandbox and on the product your skill is running in, and a skill that names one tool breaks everywhere that tool is absent. Always state the path as well, so the work is recoverable if presentation is unavailable.

Caveat: Which delivery tools exist in a session is served at spawn time and has changed across releases; the capability check is what makes this durable, not the tool names.

State the path of what you produced even when a delivery tool worked.

FRICTIONInference

It costs one sentence and it is the only part of your output that survives every sandbox, every tool-surface change, and a user reading a transcript later. If delivery silently failed, the stated path is the difference between a recoverable result and a lost one.

Caveat: Reasoned from the two-sandbox split rather than measured; the cost is one sentence either way.
Caveat: State the path as plain text, not as a link. In the remote sandbox a bare absolute path in the final message rendered as a link to a claude.ai address that does not exist, and the file-link scheme that works locally rendered as inert text — observed once, relayed from another project's run.

Never call a tool that writes to the user's own disk from inside a skill.

Committing files onto the user's machine is infrastructure the agent and the user negotiate through folder-connection consent, not something a skill should reach for. No first-party skill names such a tool. Present the file and let the delivery path be decided outside your skill.

Do not deliver by starting a local server and opening it. Write a self-contained file and present that.

A skill that generates something to look at — a report, a viewer, a chart — is tempted to serve it and open a browser at the address. That does not work here, and the reason survives whichever surfaces happen to be available: anything your shell starts runs where the shell runs, which is not where a viewer would be looking, and a plain file path on disk is not reachable that way either. Write one self-contained file with everything inlined, put it where deliverables belong, and present it. If the surface offers a way to publish a rendered page, that is the better target than a server. Build the interaction so it does not need a round trip back to your skill: a page that asks the person to copy something back into the conversation works, a page that expects to POST its results somewhere does not.

Do not hand-copy files into the outputs location before presenting them.

The tool that presents files to the user copies what you give it out of the scratch space for you. Copying first and then presenting the copy produces duplicates and obscures which file is the deliverable.

Caveat: Behaviour of the presenting tool is served per session; check that it exists before relying on it.

Assume the session filesystem does not outlive the session.

SILENTMeasured Remote sandbox

In the remote sandbox the filesystem is discarded at session end. In the local sandbox undelivered files are hidden from the user rather than destroyed, and can in principle be surfaced on a later turn — but a skill should not depend on that difference. Deliver within the turn that produces the file.

Assume your skill may run in either sandbox, and never assume which one.

A Cowork session runs in one of two sandboxes: a local one on the user's own machine, or a remote one on Anthropic's infrastructure. They differ in where the filesystem lives, whether it survives the session, and how a file reaches the user. Which one a session gets depends on how it was created and on account configuration, neither of which a skill can control or reliably detect — so write for both.

Caveat: Sandbox availability is rolled out per account, so two users running the same skill can be in different sandboxes.

Nothing will report progress for you — if a long run should say anything mid-way, your skill has to say it.

Plain text you write between tool calls does reach the user here, and the shipped instructions do ask for it in general terms: say what you are about to do before the first tool call, and give brief updates when you find something load-bearing or change direction. But the wording of that instruction is chosen per model by the service rather than by anything on the machine, so you cannot count on a particular version of it, and in a long tool loop it measurably does not hold. Measured across every session on one machine, work in this environment produces user-visible text on roughly a third of its turns, against about half in the terminal, and silent runs of well over a hundred consecutive turns occur. There is a mechanism meant to catch exactly that — a nudge injected once a run has gone several turns without saying anything — and across every stretch long enough to qualify, it never fired once. Treat it as absent. If a phase boundary, a decision, or a surprise is worth the user knowing about while the work is still running, write the instruction to say so into the skill itself, at the step where it matters. A useful shape to copy: acknowledge in one line, work, then report — and between those, only when something happened that carries information, not as filler.

Caveat: The measurement is one machine and one account. The rate is a property of how this account's models behave under the instruction they were given, not a guarantee about anyone else's.
Caveat: The nudge is present and complete in the shipping agent — it is reachable code, not a stub. What was observed is that it never fired, which points at the per-account switch that enables it rather than at the mechanism. That switch is delivered with the account, so it can turn on without any version changing.
Caveat: The instruction the model receives is selected per model family, so a session on a different model may be told to narrate in noticeably different terms — including a much terser form. Do not write a skill that depends on the wording of any one of them.
Caveat: The rule holds even if the nudge is switched on: it only acts after several consecutive silent turns and stops after a few, so it is a floor on the worst case, not a substitute for saying anything useful.
Caveat: In the remote lane the agent build is not pinned by anything on your machine, and it moves: every build observed through August 2026 predated the release that added the nudge, and a September 2026 probe dated it at or after a release that carries it. So whether the backstop exists there depends on the day, and whether it fires depends on the same per-account switch as everywhere else. Treat it as absent on that lane too — for a different reason than before: not because the code is missing, but because nothing you can check locally tells you which build you got.

What is not established

  • Which sandbox a given session runs in is decided by the surface it was created from and by account-level configuration. There is no reliable way for a skill to force one, and no documented way to read it directly — check for the tools you need instead.