CLAUDE LABJP
FORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as OctoberFORK — Claude Code 2.1.212 changes what /fork does: it copies your conversation into a new background session with its own row in claude agents, so you can keep working. The old in-session subagent is now /subtaskLIMITS — WebSearch calls are now capped at 200 per session by default, and subagent spawns get the same 200 ceiling, so a runaway search or delegation loop stops on its ownMCPBG — MCP tool calls running past two minutes now move to the background automatically, keeping the session usable. Tune the threshold with CLAUDE_CODE_MCP_AUTO_BACKGROUND_MSPLANFIX — Fixed plan mode auto-running file-modifying Bash commands such as touch and rm without a permission prompt or an SDK canUseTool callbackSONNET5 — Claude Sonnet 5 is running on introductory pricing of $2 per million input tokens and $10 per million output. After August 31 it moves to $3 and $15IPO — Bankers are reportedly lining up investor meetings for Anthropic ahead of a possible public listing as soon as October
Articles/Claude Code
Claude Code/2026-05-27Intermediate

When Claude Code's Bash Tool Hits Permission Denied on /tmp — A $HOME/repos Fallback Pattern

A practical look at why git clone inside Claude Code's sandboxed Bash sometimes fails with Permission denied on /tmp, and how a tiny $HOME/repos fallback keeps unattended schedules alive across four indie sites.

Claude Code196Bash4sandbox7git16troubleshooting87

The morning log opens with a single line: fatal: could not create work tree dir '/tmp/repos/claudelab.net': Permission denied. The runner is healthy, the GitHub token is valid, the disk has room. What is actually happening is that a leftover subdirectory under /tmp/repos from a previous session is still owned by nobody:nogroup, and the new session's user can neither write into it nor remove it.

As Dolice Labs I run four sites — Claude Lab, Gemini Lab, Antigravity Lab, and Rork Lab — on autonomous Claude Code schedules. Each site publishes four articles a day from a sandboxed VM where the run-as user can shift between sessions. Over half a year of operation I have hit this /tmp ownership edge case more than a dozen times. Below is what I have settled on: a minimum $HOME/repos fallback that keeps unattended runs alive without any human intervention.

What the failure looks like

The clone fails with Permission denied, not No space left on device:

$ git clone --depth 1 https://...@github.com/masakihirokawa/claudelab.net.git /tmp/repos/claudelab.net
Cloning into '/tmp/repos/claudelab.net'...
fatal: could not create work tree dir '/tmp/repos/claudelab.net': Permission denied

And the directory listing reveals foreign ownership:

$ ls -la /tmp/repos/
drwxr-xr-x  3 nobody nogroup 4096 May 25 07:34 .
drwxrwxrwt 16 nobody nogroup 4096 May 27 01:34 ..
drwxr-xr-x  7 nobody nogroup 4096 May 25 07:34 antigravitylab.net

The current user is no longer nobody, so writing into the existing tree is rejected. rm -rf fails for the same reason.

Why it happens

In the sandboxed VM, the executing user can rotate between sessions. A previous session created /tmp/repos/{site} while running as nobody:nogroup and then shut down without cleaning up. When a fresh session boots under a different user, ownership of those leftover paths cannot be inherited. /tmp itself is sticky-bit world-writable (drwxrwxrwt), so new top-level directories are fine; only the orphaned subtree from the prior run is locked.

This is easy to confuse with ENOSPC. The distinguishing signal is the exact error string: No space left on device versus Permission denied. The first one calls for cache cleanup; the second calls for switching where you write to.

The minimum fallback

The three lines I have baked into every site's SKILL.md are these:

WORK="/tmp/repos/${SITE}"
 
# Verify writability for real (handles both permissions and capacity)
if ! mkdir -p "$WORK" 2>/dev/null; then
  WORK="$HOME/repos/${SITE}"
  mkdir -p "$WORK"
fi
 
echo "WORK=$WORK"

The key detail is exercising mkdir -p and checking its exit code instead of relying on test -w /tmp/repos. The simple test passes when the parent directory is writable, even if the child path you actually need is owned by someone else. I spent two consecutive mornings of empty publishing logs before I learned to verify by doing rather than by asking.

Clone vs. pull

Once the working path is settled, branch on whether a checkout already exists:

WS="$(ls -d /sessions/*/mnt/Dolice\ Labs 2>/dev/null | head -1)"
TOKEN=$(grep -A1 "^${SITE_LABEL}" "${WS}/_documents/_github_tokens/github_tokens.txt" \
        | tail -1 | tr -d '[:space:]')
 
if [ -d "$WORK/.git" ]; then
  cd "$WORK" && git pull --rebase origin main 2>&1 | tail -5
else
  git clone --depth 1 \
    "https://${TOKEN}@github.com/masakihirokawa/${SITE}.git" "$WORK" \
    2>&1 | tail -5
fi

--depth 1 is plenty for an article-only workflow. Cloudflare's CI runs generate-content.mjs from a prebuild hook, so there is no need to npm install or boot Next.js locally just to push one MDX file. The on-disk footprint of a working repo can stay under a few tens of MB.

Disk pressure on the fallback path

Falling back to $HOME/repos only helps if home has room. I keep a sanity check up front and drop sibling site repos before they start trampling on each other:

FREE_MB=$(df "$HOME" --output=avail -m 2>/dev/null | tail -1 | tr -d ' ')
if [ "${FREE_MB:-0}" -lt 500 ]; then
  for OTHER in "$HOME/repos"/*; do
    [ "$OTHER" != "$WORK" ] && rm -rf "$OTHER" 2>/dev/null
  done
fi

Because the same VM is sometimes reused between my 02:00 premium slot and my 15:00 daily slot, a stray node_modules from an earlier task could swallow 800MB on its own. Running the cleanup logic first made the rest of the pipeline noticeably more stable.

Why I have needed this since 2014

I have been shipping indie iPhone and Android apps since 2014 — Beautiful HD Wallpapers and a healing-focused catalog among them — and the lifetime download count crossed 50 million somewhere along the way. Sustaining AdMob revenue while also maintaining a contemporary-art practice means automating the blog pipeline as far as it will go. Trading hand-written posts for a Claude Code schedule was what made 4 sites × 4 posts a day feasible.

That said, an unattended pipeline that goes dark for a day directly affects the monthly Stripe membership numbers. The first time a single Permission denied killed two mornings in a row, I ended up patching it from my iPhone at a Kichijoji café. Since then, not trusting /tmp and verifying writability on the fallback path have been the two non-negotiables of the runbook.

A reusable snippet for multi-site setups

Across the four sites I keep the snippet identical, with the repo name as the only parameter:

# Shared snippet — same shape across all four sites
SITE_REPO="$1"   # e.g. claudelab.net
WORK="/tmp/repos/${SITE_REPO}"
mkdir -p "$WORK" 2>/dev/null || WORK="$HOME/repos/${SITE_REPO}"
mkdir -p "$WORK"
touch "$WORK/.write_test" 2>/dev/null || {
  echo "write test failed even on fallback: $WORK" >&2
  exit 1
}
rm "$WORK/.write_test"
echo "WORK=$WORK"

The trailing .write_test step is what catches the storage-driver class of bugs — the rare case where the kernel reports permission granted but the actual write returns EPERM. I hit one such case on Antigravity Lab and have kept the touch-and-remove pair in place ever since.

Triage checklist

When the schedule does go red, this is the order I work through:

  1. ls -la /tmp/repos/ to see who owns the subtree.
  2. whoami and id to confirm the current execution user.
  3. df -h /tmp and df -h $HOME to compare available capacity on both candidates.
  4. mkdir -p "$WORK" && touch "$WORK/.write_test" && rm "$WORK/.write_test" for a real write test.
  5. If that still fails, switch WORK=$HOME/repos/${SITE} and run the same test again.

Step 4 is the one that earns its keep. A passive test -w would have told me the path was writable in the very session that later failed mid-clone.

Treating $HOME/repos as the source of truth

Because four sites are wired into Cowork-side schedules, the unwritten contract is "do not stop on error, take the next action automatically." Even when the fallback succeeds, I write the chosen path to the log so that task_error.log makes it obvious afterwards which morning's article was generated from home rather than from /tmp.

If you are running a similar unattended pipeline, treating /tmp as a hint rather than a contract is the single change that buys you the most reliability. $HOME/repos lives only as long as the session, but a one-line fallback and a shallow clone are enough to keep yesterday's failure from becoming today's missing post.

Thanks for reading — I hope this is useful for anyone running a parallel indie pipeline of their own.

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 →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-05-30
Why git Says detected dubious ownership in repository — and How to Get Past It
An automation that ran fine yesterday suddenly dies on detected dubious ownership in repository. Here is what actually triggers it, the safe.directory fix, and how it differs from a real Permission denied.
Claude Code2026-05-03
How to Commit and Push via GitHub REST API When git CLI Fails in VM Environments
A practical guide to using GitHub REST API (blobs→trees→commits→refs) to push files when git CLI is blocked by index.lock, ownership errors, or permission issues in VM and sandbox environments.
Claude Code2026-07-01
My Claude Code Hooks Stopped Firing After an Update — the Hyphenated Matcher Exact-Match Change in v2.1.195
In Claude Code v2.1.195, hook matchers containing a hyphen switched from partial match to exact match, silently disabling an existing PreToolUse hook. Here is how I isolated the cause and how to write matchers that won't break.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →