Skip to content
← Back to release · 2.1.268
New features / v2.1.268

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-dir at 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 --depth when 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 and checkout hook 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-state to 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

Change details