◉CLAUDE LABJP
●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 44 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…●2.1.301 — Adds the vim % motion, LaTeX math rendering in the VS Code panel, and a retry-watchdog limit for usage-limit waits (10/15)●2.1.299 — Conversations over 200K tokens left idle about 54 minutes get compacted; MCP allow rules now match only the server they name●SONNET 4.5 — Retires on the Claude API on 11/30, 44 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●FAST MODE — Opus 5.5 fast mode is out of research preview and no longer needs the beta header (10/15)●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-10-17Intermediate

The Morning WS Came Back Empty in a Scheduled Task, and I Learned There Were Two Shells

Step 1 of a scheduled task left my workspace variable empty. The cause was a second shell. Here is what the same probe returned in both, and a tool_result guard that turns the silence into an error.

Messages APItool_resultCowork49Scheduled Tasks15Ops Log

✦ Premium Article

On a Saturday evening, as an indie developer running a few scheduled tasks of my own, I ran step 1 of one of them. It is the usual single line that puts the location of the workspace into a variable called WS.

What came back was an empty string. Not one line of error. The exit code was 0.

I happen to have a habit of echoing a variable before I trust it, so I noticed on the spot. Had I not, the next step, cat "${WS}/_skills/…", would have answered "no such file". And that message would have pointed at a path under the root directory, not at the real cause.

This article is a record of what I learned that day. When an agent has two shells, a mix-up does not show up as a failure. It shows up as nothing. Below are the measures I added to turn nothing into a failure, together with what I actually ran.

Step 1 left the variable empty, and I almost kept going

What happened is simple. The task began by finding the workspace with bash and reading a settings file. The first thing I called was the standard bash tool, which runs on the cloud side.

But this environment has two shells: the cloud-side bash, and a shell running in a virtual machine on my own computer. Only the second one has the workspace folder attached.

When I searched from the cloud side, ls -d found nothing. Because 2>/dev/null | head -1 was attached at the end, the fact that nothing was found simply flowed on as an empty string.

What makes this setup frightening is that even the wrong shell runs most commands normally. The next section puts the two side by side.

The two shells look almost identical, but they see different things

I ran the same probe script in both shells and compared. Here it is. I have replaced the workspace folder name with Workspace; the real runs used the actual name.

#!/bin/bash
# probe.sh — find out which shell you are in. Run the same file in both.
echo "host=$(hostname)"; echo "home=$HOME"; echo "pwd=$(pwd)"
echo "sessions_dir=$([ -d /sessions ] && echo yes || echo no)"
echo "ws_glob=[$(ls -d /sessions/*/mnt/Workspace 2>/dev/null | head -1)]"
echo "git=$(git --version 2>&1)"; echo "node=$(node -v 2>&1)"; echo "py=$(python3 --version 2>&1)"
s=$(date +%s%N)
curl -s -o /dev/null -m 10 -w 'github_api=%{http_code}' https://api.github.com/zen
echo " ($(( ($(date +%s%N)-s)/1000000 ))ms)"

The results are below.

ItemCloud-side shellDesktop-side shell
Hostnamevmclaude
Python3.13.163.10.12
git2.43.02.34.1
Node.jsv22.22.0v22.23.2
/sessions directoryabsentpresent
Workspace foldernot found (empty string)found
curl to api.github.com/zen403 (1,352 ms)200 (98 ms)

git, Python, and Node run in both. What differs is the versions, the folders you can see, and how outbound traffic gets through.

I did not investigate why the 403 appeared. I am only recording the observation that a bare, unauthenticated curl did not get through. I wrote up the quiet-outbound-failure case separately in When a Scheduled Task Keeps Saying SUCCEEDED but Produces Nothing, Check Outbound Traffic in the First Minute.

One more detail that quietly matters is the Python version: 3.13 on the cloud side, 3.10 on the desktop side. The same script can work in only one of them depending on how it is written.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦You can now write into your agent's tool definitions which shell sees which files, and stop a mix-up before the command runs
✦You can now catch the failure that returns an empty string with exit code 0, using is_error on tool_result
✦You can now prevent the accident where an empty path variable sends a command to the root directory, with one line placed before it
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◈ Cowork2026-10-17
When a Scheduled Task Keeps Saying SUCCEEDED but Produces Nothing, Check Outbound Traffic in the First Minute
The run history is green, but the output file is still yesterday's. A short probe and a freshness check that tell you in the first minute whether outbound calls are being refused with a 403 on CONNECT.
◈ Cowork2026-10-01
Before You Trim a Scheduled Task's Runbook, Count How Many Times It Gets Re-read
I cut 13 KB from a scheduled task's preloaded docs, and recounted by re-reads it was worth about 1.3 tool calls. Here is the ledger that counts re-reads, and a shell helper that records bytes during a run.
◈ Cowork2026-09-24
Will Your Cowork Scheduled Task Still Run Next Saturday?
A definition that runs once and stops, two fire times packed into one task, a weekday that quietly emptied after an edit. Cowork schedules can go missing without leaving a single log line. Here is a working audit that compares what you defined with what you meant.