Delete session state on exit
CHANGELOG · original
Added claude self-hosted-runner --remove-session-state (default off): delete each session's per-session directories under <base-dir>/_sessions/ when the session ends Open official changelog ↗ Documentation
Documentation excerpt
Reuse a pre-warmed checkout
For large repositories, the clone can dominate session startup. At --capacity 1 with no checkout hook, the runner keeps one canonical clone per repository at <base-dir>/<repo-owner>/<repo> and reuses it across sessions: it fetches the requested ref, detaches HEAD, and resets hard to it, which is near-instant when little has changed. To skip the cold clone, supply the clone in one of two ways:
- Clone in the image: build the clone into your runner image at that path. Every fresh container then starts with the warm clone without reusing a disk.
- Clone on a persistent volume: on runners you pre-lock to one user's account with
--lock-to-account, point--base-dirat a persistent volume, so the disk only ever serves that account. A pre-locked runner never picks up Claude Tag channel sessions, so this option doesn't apply to runners that serve them.
What the reuse path does and doesn't guarantee:
-
Any clone shape works: a full, shallow, or single-branch clone at the path is used as-is. The runner never passes
--depthwhen fetching into an existing clone, so a full pre-warm keeps its full history and a shallow one stays shallow.CLAUDE_RUNNER_FETCH_DEPTH(full,0, or a number; default 50) controls only the cold clone the runner makes when no clone exists yet. -
Tracked changes reset, untracked files persist: each session starts from a hard reset that wipes the previous session's tracked modifications, but the runner never runs
git clean, so untracked files from the locked owner's earlier sessions stay in the tree. -
Per-session directories persist too: alongside the checkout, the runner creates per-session entries under
<base-dir>/_sessions/for every session it runs. The session's Claude config directory holds a local copy of the conversation transcript. Next to it sit the session's uploaded files, when the session has any. The session directory sits there too: it holds any per-session worktrees andcheckouthook checkouts while the session runs, and it keeps whatever else Claude wrote in it.By default the runner leaves these in place when the session ends, so on a disk that outlives the runner process they accumulate. Every session runs as the runner's own user, so any later session that disk serves can read them. If you keep a persistent
--base-dir, size the volume for that growth. The same applies to any setup that restarts the runner on the same filesystem, including the Docker Compose recipe. -
With
--remove-session-state, per-session directories don't persist: start the runner with--remove-session-stateto have it delete each session's per-session directories as the session ends. The deletion is best-effort: the directories stay when the runner is killed before its cleanup runs. The canonical clone and files a session wrote elsewhere on the host, such as the temporary directory, stay regardless. -
With the git proxy, the reset becomes a checkout: with
--use-anthropic-git-proxy, the runner sanitizes the clone's.git/before each session, keeping the object store, refs, and shallow state but deleting the index, so each session pays a full working-tree checkout instead of a near-instant reset; it still never re-clones. Submodule pre-warms aren't supported under the proxy. -
Long clones need no workaround: the runner bounds each git operation with a 120-second no-progress watchdog and a 30-minute hard cap, not a flat timeout, so a slow cold clone that keeps reporting progress completes.
Documentation snapshot · 2026-09-23