◉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, 45 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, 45 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-06-23Advanced

An Unattended Agent That Wakes Up to a Blank Machine Every Time

A scheduled, unattended agent wakes up on a fresh disposable machine every run: paths it cannot write to, a filesystem that has not finished booting, a working directory that has vanished. Here is how to design state recovery into the first thirty seconds, drawn from real operational logs.

Claude Agent SDK13unattended agentsstate recoveryidempotency6operations30

✦ Premium Article

The morning the scheduled agent woke up, the first thing it returned was neither success nor failure. It was a single line: could not create work tree dir: Permission denied.

The /tmp/repos path it had written to yesterday was not writable today.

The cause was quick to find. The machine the agent runs on is rebuilt from scratch every time — a disposable sandbox — and the directory's owner happened to be assigned to a different user that run. As an indie developer, I run unattended article generation across four sites at Dolice Labs, and whenever I take the "the environment is different every time" assumption lightly, the nightly task ends quietly with zero output.

The reliability of an unattended agent is decided not by clever core logic, but by the first thirty seconds. This piece is about those thirty seconds: how to design startup and state recovery right after a cold boot.

Compute is disposable; state lives somewhere else

The first mental shift is to abandon the naive expectation that "the same machine remembers where it left off."

Most scheduled runs spin up a new sandbox each time. Files you wrote last run are gone, packages you installed do not persist, and cwd and environment variables do not carry over. The only state you can rely on lives outside the compute.

In my own operation, I split durable state into three layers.

LayerWhere it livesRole
Code / artifactsGit remoteThe work itself. It only counts once pushed
Reference data / configCloud-synced workspacePolicy, keywords, tokens. Treated as near read-only
Run historyPer-date log filesThe only way to tell your future self what today already did

With these three layers in place, the sandbox can be burned down as often as it likes. The agent rebuilds its state from external truth on every run. Conversely, the moment any of these three relies on a local temp directory, that information is lost on the next cold boot.

Write the startup sequence defensively

Before any real work, I always run a short sequence to prepare the environment. Cut corners here and the later stages fail silently.

The order matters: (1) wait for the filesystem to come up, (2) discover the working directory dynamically, (3) secure a writable work area, (4) retrieve credentials.

#!/usr/bin/env bash
set -euo pipefail
 
# (1) Wait for the mount — right after a cold boot, files are invisible for a few seconds
WS=""
for i in $(seq 1 10); do
  WS="$(ls -d /sessions/*/mnt/Workspace 2>/dev/null | head -1)"
  [ -n "$WS" ] && [ -d "$WS" ] && break
  sleep 3
done
[ -n "$WS" ] || { echo "FATAL: workspace not mounted after 30s"; exit 1; }
 
# (2) Check the first-choice work dir is writable; fall back to $HOME if not
WORK_BASE="/tmp/repos"
if ! mkdir -p "$WORK_BASE/.probe" 2>/dev/null; then
  echo "WARN: $WORK_BASE not writable, falling back to \$HOME/repos"
  WORK_BASE="$HOME/repos"
  mkdir -p "$WORK_BASE"
fi
rmdir "$WORK_BASE/.probe" 2>/dev/null || true
 
# (3) Read credentials from synced storage, not env vars (survives reboots)
TOKEN="$(grep -A1 '^MySite' "$WS/_tokens/tokens.txt" | tail -1 | tr -d '[:space:]')"
[ -n "$TOKEN" ] || { echo "FATAL: token missing"; exit 1; }
 
echo "ready: WS=$WS WORK_BASE=$WORK_BASE"

The key is to never assume /tmp is writable. Actually create a small directory; if it fails, quietly escape to $HOME. The Permission denied I hit on this run was exactly the kind of failure that one .probe test absorbs.

Searching with a wildcard like ls -d /sessions/*/mnt/... is also deliberate. The session name changes every run, so a hardcoded path points at a directory that will not exist next time. Dynamic discovery is the cheapest insurance against an environment that keeps changing.

✦

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
✦A copy-pasteable bash startup sequence built for disposable environments: mount detection, boot waiting, dynamic working-directory discovery, and fallback paths
✦An idempotency guard that asks durable storage whether today's work is already done, plus the two-signal check that stops duplicate output
✦The three traps I hit running Dolice Labs unattended — write permissions, cloud-synced files, and boot latency — written up as field-tested operational guidance
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

⬡ API & SDK2026-09-09
The agent I built in the Console could not be adopted by ant apply
ant apply refuses some drift and silently duplicates other resources. Here is where that line sits, why claude-lock.json belongs in your commits, and the five doors a second copy walks through.
⬡ API & SDK2026-08-30
Read the Catalog Your CLI Already Ships Before Bulk-Replacing Model IDs
A newer generation makes older model IDs look stale. Here is how to pull the catalog embedded in your installed binary, sort every reference into matched, date-mismatched, and malformed, and stop the replacement that would break working IDs.
⬡ API & SDK2026-07-28
The Line That Disappears at 100K: Measuring What Tool-Output Spill Actually Keeps
When agent tool output passes 100,000 characters, the full text spills to a file and the model sees only a head-truncated preview. Here are measured survival rates from a real repository, and the output envelope I built to push decision-relevant lines to the front.