The compatibility release. The docker capability now binds the daemon socket where your engine actually runs, so rootless Linux and Colima work instead of pointing at a socket that is not there.
Engine-aware docker capability - the socket bind follows the daemon. A VM-backed engine (Docker Desktop, OrbStack, Colima, Rancher Desktop and Lima, on macOS or Linux) gets the in-VM /var/run/docker.sock. A host-local daemon gets its real socket, rootless included (/run/user/<uid>/docker.sock). A remote endpoint gets DOCKER_HOST forwarded. DOCKER_HOST always wins over the active docker context.
Loopback tcp counts as local - a daemon reached over tcp://127.0.0.1 (any 127/8 address), localhost in any letter case or [::1] runs on your machine, so the cap binds the host socket instead of handing the box an address that would point at the box itself.
TLS remotes warn instead of failing silently - client certificates are host credentials and never enter the cage. When DOCKER_HOST points at a TLS-secured daemon, cleat says the handshake will fail and why, instead of leaving the box to a quiet dead end.
A missing socket is never bound - the docker cap used to mount /var/run/docker.sock unconditionally. On engines whose socket lives elsewhere, Docker would materialise a phantom directory at that path on the host. The bind is now guarded on a live socket, closing that hazard for good.
DOCKER_HOST trailing-slash tolerance - a unix:// endpoint ending in a slash (common from an env file) now resolves to the real socket instead of missing the liveness guard on a running daemon.
bash 3.2 strict-mode hardening - the install-shadow scan and cleat status guard BASH_SOURCE under set -u on macOS bash 3.2, with lint-clean quoting in bin/cleat and install.sh.
CI now exercises the whole host matrix - 16 jobs per push: macOS on Apple Silicon and Intel, Ubuntu 22.04, 24.04 and arm64 (the arm64 image is built and booted), Windows via WSL2, real Colima and rootless Docker engines, GNU coreutils on macOS. The macOS suite runs sharded across four parallel runners.
The README gained a compatibility matrix and nav badges linking the site.
Test infrastructure hardening: the runner trusts bats exit codes so a killed run cannot pass, supports sharding, roots its temp dirs safely on macOS and closes a lock-reclaim race.
2190 (+21) behavioral tests across 56 files. 582 (+20) mutations caught, 0 missed, 0 skipped (583 on a host with inotify-tools).
The release highlight shows again - the on-start "what's new" note is shown only for the version it was written for. 1.4.1 shipped with that gate left a version behind, so the note stayed silent for the whole release. The gate now tracks the release, so the flagship-feature note (forks, image paste and Homebrew) surfaces again on upgrade.
A second install is surfaced on start - the one-install guard only covers cleat's own installers, so brew install could add a keg over a curl install with nothing flagging it, the split behind a stale cleat --version or an update that seemed to do nothing. When more than one cleat is on the machine, every interactive start now warns, names which one is running and which is shadowed, then points at the one-command fix: brew uninstall cleatdev/tap/cleat for a keg, or an rm of the shadow's path for a script install. It only warns, never blocks, since Homebrew installs its keg without cleat's guard and cleat can surface the split but not prevent it. cleat status shows the same list on demand. Silence the startup warning with CLEAT_NO_INSTALL_CHECK=1.
Forked workspaces are the headline: several agents work one project at once, each in its own copy of the tree, plus native ctrl+v image paste and a hardening pass that keeps every other project out of a box.
The largest release in months. It ships forked workspaces for real parallel work, native ctrl+v image paste inside a box, per-box config in a single .cleat and a ~/.claude containment pass, on top of a 32-defect security audit of the whole CLI.
Forked workspaces - cleat start <box> --fork (or cleat run <box> --fork) gives a box its own copy of the project at /workspace instead of the live tree, so several agents can work one project without touching each other or your working directory. It is a copy rather than a git clone, so it carries uncommitted work, untracked sibling repos and even a project with no git at all. On APFS cp -c clones blocks copy-on-write, so the copy is close to free. Symlinks are copied as symlinks, so a keys -> ~/.ssh in the tree never materializes real key bytes into the cage. The launch summary prints the copy's age because a fork is a point-in-time snapshot. [fork] exclude in .cleat prunes paths from the copy and [fork] dir in the global config relocates where copies live.
cleat fork verbs - cleat fork start and cleat fork run are discoverable aliases for --fork, alongside cleat fork to list every copy with its size and age, cleat fork path to print a copy's path, cleat fork refresh to re-copy from the live tree, cleat fork rm to delete one copy and cleat fork prune to reclaim copies whose box is gone and clear any stale marker. cleat rm keeps a box's copy on purpose, because for a project with no git it can hold the only version of hours of work.
Native ctrl+v image paste - copy a screenshot on the host, click into a box's Claude prompt and press ctrl+v. About a second later Claude shows [Image #1], exactly as it does outside Docker. It is images only and enforced host-side, because clipboard text is where passwords and tokens live. There is no Dockerfile change, so no image spec bump and no box is recreated: every existing box gets it on its next session. Use ctrl+v rather than cmd+v on a Mac, since the terminal intercepts cmd+v. CLEAT_NO_CLIPBOARD_IMAGE=1 turns it off.
Per-box config sections - a box's own capabilities, resources, [setup] and fork settings now live in the project's one .cleat as [box.<name>.<kind>] blocks, replacing the old one-file-per-box .cleat.<box>. A declared section replaces the project default, an absent one inherits it and a declared-but-empty section is a real value, so a box can be locked to zero capabilities. cleat config <box> --list marks each value declared or inherited and names the box. Trust is per box and the box name is required on every edit, so no config change can silently apply to main.
~/.claude containment - a box now sees only the project you gave it plus your Claude login and that project's history. Other projects' transcripts and edited-file history are no longer reachable from inside a box, where before a caged agent could read the full history of every repo on the machine. ~/.claude/skills and ~/.claude/plugins are masked read-only so a caged agent cannot plant an auto-loaded skill or write your host plugins. ~/.claude/hooks becomes a per-box empty directory so the cage cannot overwrite a script the host later runs. These are all run-argument changes, so no box is recreated. Installing a plugin from inside a box now fails by design: install it on the host.
32 verified defects fixed in a post-fork hardening audit - an adversarial audit of the CLI after forks landed confirmed and closed 32 bugs, each with a test and a mutation that proves the test cannot be quietly disarmed. The worst were data loss: cleat fork prune deleted every workspace copy on the machine when Docker was stopped, an unvalidated box name walked rm -rf out of the fork root through x/../../../Documents and cleat nuke wiped fork markers while keeping the copies so the next --fork deleted the surviving work. Also closed: config writers that truncated in place now write to a temp file and rename, an indented [caps] or [setup] that was silently duplicated or deleted, a session key that lost every snake_case project's history under the docker cap, a caps trust hash computed after the prompt so a .cleat rewritten mid-prompt was recorded as approved, a hook event name interpolated into a jq program and a UTF-8 BOM that voided the first config section.
Clipboard and browser bridges no longer read through a symlinked payload - the box's clip directory is a read-write mount, so the box could drop a symlink where a payload belongs and have the host read any file the host user can read, then pipe it into the host clipboard or hand it to the URL opener. Both bridges now drop a link unread at the gate and again after the rename. They claim the file in a directory outside the mount so it cannot be swapped between the rename and the read.
A crashed session no longer latches the clipboard bridge on - a watcher marker left by a session that died without running its trap held the .host-ready sentinel on forever, so a later plain copy wrote to a bridge nobody was reading and the OSC 52 fallback that would have delivered it never fired. Dead watcher markers are now swept by their stamped pid at watcher start and at cleanup.
A Homebrew install is left to brew - a brew keg has no .git, so cleat update used to fall through to the generic "not a git installation" advice and tell the user to re-install with the curl script. On an Intel Mac that overwrites Homebrew's own cleat binary and leaves brew tracking a stale keg. cleat update now detects a keg and runs brew upgrade cleatdev/tap/cleat for you instead, replacing its own process so nothing below the guard can touch a git checkout that isn't there. cleat uninstall offers brew uninstall, asking first: on a git install it drops a PATH symlink and leaves the clone alone, while brew uninstall deletes the whole keg. It is terminal-only and default No, so no script or wrapper removes an install unattended. Doing that removal by hand was the older hazard, since rm -f /usr/local/bin/cleat deletes Homebrew's own symlink on an Intel Mac and leaves brew believing cleat is installed while the command is gone from PATH. cleat install refuses, because brew already linked the keg. The on-start upgrade offer now checks too: it was gated only on finding a .git, which is not enough where readlink -f is missing (macOS before 12.3). The install root resolves to the Homebrew prefix. On Apple Silicon /opt/homebrew is Homebrew's own git repository, so the offer fired and would check out a Cleat tag inside brew's checkout. Detection reads where the binary physically lives, never the directory it was invoked through and never by shelling out to brew, so where brew is off PATH the command is printed rather than run.
One cleat per machine, from cleat's own installers - the curl script and cleat install now refuse to create a second install (a brew install is Homebrew's own path and does not run this check). Two cleats on one machine is a state you cannot see from the inside: a bare cleat resolves to whichever comes first on your PATH. The loser is invisible until it bites, as a stale version that "fixes itself" after a shell restart or an update that appears to do nothing. curl | bash, install.sh --local and cleat install all scan the machine first and stop if a cleat already exists at a path they are not about to write. Re-installing over the path you already own is untouched, since that is an upgrade rather than a second install. A conflict is replaced with --force (curl -fsSL https://cleat.sh/install | bash -s -- --force), except two cases that are never replaced. A Homebrew keg, because that would leave brew tracking an install it no longer owns. A regular file, because that is your own copy rather than a link Cleat created. The scan covers every PATH entry plus /usr/local/bin, ~/.local/bin, /opt/homebrew/bin and /home/linuxbrew/.linuxbrew/bin even when those are off PATH, which is what catches an Apple Silicon Homebrew prefix from a shell that never ran brew shellenv. cleat status grew an Install section that names every cleat it finds and marks which one is running, shown only when there is more than one.
Cleat's own state moved out of the install directory - the update throttle, the release-highlight memory and the Docker, disk and Claude check stamps now live in ~/.config/cleat/state/. On a Homebrew install they used to sit inside the keg, which brew upgrade deletes, so every throttle reset and every "don't ask me about this version again" was forgotten on each upgrade. On older macOS they landed at the root of the Homebrew prefix, where nothing ever removed them. Anything left in the old place is carried over on first run, including a script install's ~/.cleat when you move to Homebrew, so a switch needs no action from you at all. This also means switching between the install script and Homebrew loses nothing: config, trust, boxes, fork workspaces and Claude history were always kept outside the install. Boxes are identified by project rather than by where Cleat lives.
A failed image pull no longer ends in a confusing build error - when the prebuilt image cannot be pulled, Cleat falls back to building locally, which needs the docker/ directory that ships beside the script. A packaged install may not have one. Instead of a raw failure about a build context that was never there, Cleat now says so and points at retrying the pull.
The old .cleat.<box> per-box config files are no longer read (the .cleat.<box>.env env sidecar is unaffected). Move their settings into .cleat as [box.<name>.<kind>] sections. Cleat names the file and the sections to move at launch. A Cleat older than this release cannot see [box.*] sections and falls back to [caps], so a per-box reduction reads as the permissive project set on a teammate's older CLI. That fails open, which the first per-box section writes into the file as a comment.
No image spec bump this release. Forks, native paste and the ~/.claude containment pass are all run-argument changes, so no existing box is recreated. Every box picks up the new behavior on its next session.
Test infrastructure only, no user-facing change: an automated host verifier now drives the manual release scenarios end to end, the integration harness runs on macOS and the mutation harness lost a GNU-only sed address plus a mutation the host could satisfy.
2161 (+388) behavioral tests across 55 files, covering forks, the fork verbs, per-box config sections, ~/.claude containment, native ctrl+v paste, the symlink read-through fixes and the Homebrew guard on all four of its call sites.
558 (+174) mutations caught, 0 missed, 0 skipped (559 on a host with inotify-tools).
Project provisioning can put a toolchain on PATH again. On macOS it could not, and the failure took the whole [setup] payload down with it.
A patch release for one image bug found while writing the provisioning docs. Cleat will offer to refresh your box image on the next start, because the fix lives in the image rather than the CLI.
[setup] provisioning failed on macOS whenever a step touched a shell rc file - the box remaps its user to your host account, then takes ownership of the paths that need it. The shell rc files and ~/.config were not in that set, so on any host whose user id is not the image's own (which is every Mac) a [setup] step that appended to ~/.bashrc or ~/.profile, or created a directory under ~/.config, was refused. A payload runs under bash -e, so that refusal aborted the entire provisioning run. This is what rustup, the .NET install script and the uv installer all do by default. The box now takes ownership of those paths too. ~/.config is claimed non-recursively on purpose, so the gh capability's mount of your real ~/.config/gh keeps host ownership.
The bundled image spec moved to 4, so existing boxes are offered a refreshed image on the next start. Accepting it recreates the box: your project files and Claude state are untouched, ad-hoc installs inside the box are not. Any approved [setup] re-runs automatically on the new box.
The examples/setup starters no longer depend on that fix landing, so they also work on a box created before it. rustup runs with --no-modify-path, uv installs unmanaged into ~/.local/bin, and the .NET and Rust examples expose their binaries by symlinking into ~/.local/bin instead of editing a shell rc file. The box already puts ~/.local/bin first on the session PATH, and a provisioning payload never sources an rc file anyway.
The Rust example installs build-essential and pkg-config, and the .NET example installs libicu72 on arm64. The slim base ships neither, so rustc could not link and dotnet --info aborted with exit 134.
1773 (+3) behavioral tests across 52 files, including one that fails if the ~/.config ownership fix ever becomes recursive and starts rewriting your real ~/.config/gh.
See your Docker disk, reclaim it safely and never get blindsided by a full store, plus durable project setup, a config editor and a launch that stops when Docker is misconfigured.
A stability and maintenance release that bundles several unreleased improvements. The headline is a disk-aware cleanup system that answers a real field report (a fresh box reading 100% right after cleat rm), alongside trust-gated project provisioning, an interactive .cleat editor and a launch gate for a misconfigured Docker.
cleat storage - a read-only, cross-OS breakdown of the shared Docker store: a cap-relative fill bar, per-type totals (images, containers, volumes, build cache with reclaimable), then your attributable share (cleat image and box counts plus reclaimable images), with the shared build cache and everything else honestly labeled other projects.
Disk-fill advisory and gate - keyed on the store's fill percentage read from the just-started box, so a 60 GB Docker Desktop disk and a 1.8 TB one trip at the same fullness. At 85% full with little free space a daily read-only advisory names the fill and the levers. At 95% with under 10 GB free it holds the interactive launch on a keypress, the way the Docker memory gate does. CLEAT_NO_DISK_GATE=1 skips the hold. An ENOSPC backstop covers a store too full for a box to start at all.
cleat prune --cache - clears the shared, regenerable Docker build cache that bare cleat prune never touched (the heavy-rebuild hog). It is a typed flag behind a loud default-No confirm, runs reclaimable-only (never a cold wipe), and --yes skips the prompt for scripts.
Cross-OS reclaim guidance - the disk advisory, gate and cleat storage write their fix steps for your actual engine: the Docker Desktop disk slider, OrbStack auto-reclaim, Colima disk resize and fstrim, a native-Linux prune, a WSL disk compaction or .wslconfig for a WSL2 memory fix.
Project provisioning with [setup] - a trust-gated [setup] section in .cleat (plain shell lines plus a script <path> directive) that runs once per container, as coder, before the session starts, so a project's real dependency stack survives every recreate. Consent is a second, separate class from capability trust (CLEAT_TRUST_SETUP / --trust-setup, deliberately not granted by project trust). Box-aware cleat trust and cleat untrust. A new cleat setup [box] verb runs the provisioning on demand against a running box, and cleat setup [box] --show previews the payload, the trust state and the run-once marker without executing anything.
.cleat editor - cleat config is now an arrow-key editor for capabilities and [resources] (memory, cpus) in one screen, with a row to generate a project .cleat. New --memory and --cpus flags, and --list now prints the resources block alongside capabilities. Both rings are built from your actual machine: cpus from the core count Docker reports (never more cores than you have), memory in real stops up to the size of your Docker VM, so a 24 GB VM can be asked for all 24 GB. Past 8 GB the editor annotates rather than blocks, with a note that escalates as the number climbs and a full explanation at the whole-VM value, where a box that grows into everything leaves nothing for the daemon and the VM's own OOM killer starts firing. A project .cleat still stops at 8 GB, matching the clamp the runtime applies to repo-shipped values.
Docker-config launch gate - when the Docker VM memory is undersized or swap is at the default, the interactive launch holds on a prominent banner until you press Enter, so a misconfiguration cannot scroll past unread. Interactive-only, CLEAT_NO_DOCKER_GATE=1 skips it.
Prune offered a cleanup that removed nothing - the on-start prune offer counted images a stopped box still pins, which docker rmi refuses to remove, so a "N stale images" prompt was followed by "Nothing to prune". The offer now counts only images a prune can actually reclaim.
A full host process table corrupted the Claude TUI - during a heavy many-agent run, host fork-exhaustion errors from Cleat's background watchers leaked into the terminal and scrambled Claude Code's interface. Watcher output now goes to a capped per-box log, and a one-line diagnostic at session end explains the host limit and the fix.
WSL2 got a launch hold it could not act on - on the Docker Desktop WSL2 backend the memory advisory named the Resources slider (absent there) and the new gate held the launch on it. Cleat now names .wslconfig on WSL and downgrades that hold to a non-blocking advisory, since WSL memory is elastic.
cleat nuke no longer runs a global dangling-image prune that would delete other projects' dangling images. It scopes the sweep to cleat-labeled images and discloses that it clears the shared build cache. The false "and volumes" wording was dropped (Cleat creates no volumes).
cleat rm now prints a one-line note that the box's disk layer is freed but its image and the shared build cache remain, pointing at cleat storage.
One hardened Docker size parser now backs every reclaimable figure the CLI reports, fixing a latent size misparse on mawk and under a comma-decimal locale.
1770 (+318) behavioral tests across 52 files - the disk parser, the two-tier gate, the engine classifier, cleat storage, cleat prune --cache, the nuke narrowing, [setup], the config editor, the machine-sized resource rings and the Docker gate each pinned by tests.
Your clipboard stops getting clobbered: a box never replays an old copy onto the host again.
Starting cleat on an existing box could instantly overwrite the host clipboard with whatever was copied inside that box last session. This release fixes the replay at its root and hardens the whole bridge teardown path.
Stale clipboard redelivery - the bridge's payload file survived between sessions and every new session's watcher treated it as a fresh copy on its first poll tick, pushing a previous session's clipboard over whatever you had on the host. The watcher now consumes a payload the moment it delivers it (an atomic claim, so it can never be replayed in any watch mode) and sweeps any leftover older than 5 seconds at startup before the bridge comes up.
Same-second copies no longer swallowed - the old polling dedup compared mtimes with one-second resolution, so a second copy within the same wall-clock second was silently dropped. Consume-on-read retires the dedup entirely.
Backward clock steps can't resurrect the bug - a leftover whose mtime sits ahead of the clock (an NTP correction, a VM clock catching up after sleep) now reads as stale and is swept instead of redelivered.
Concurrent sessions can't eat each other's copies - in-flight claims live outside the namespace a sibling session's exit cleanup sweeps, so closing one terminal never deletes a copy another session is mid-delivering.
Watcher orphans on inotify hosts - on Linux hosts with inotify-tools installed, session exit left the clipboard watcher and its inotifywait blocked forever as orphan processes (a TERM never lands while the watch tool blocks). Cleanup now kills the blocking child directly.
The Linux CI leg now installs inotify-tools so the event-driven watcher branch runs for real on every push, while the macOS leg keeps covering the polling fallback. That coverage is what surfaced the orphan leak above.
1452 (+5) behavioral tests across 47 files - stale redelivery, future-dated leftovers, claim consumption, claim namespace and the orphan reap each pinned by a test
The kit learns from a real job: sequential dispatch, honest worker reports and a built-in way to verify it is working.
A deep re-audit against Anthropic's coordinator-pattern cookbook, plus one big real-world build, turned into a second generation of the flagship kit's delegation policy.
Kit delegation policy v2 - the plan-big-execute-small fragment now teaches dispatch craft: a dispatch names the files, the exact change, the constraints and the test to run, so the worker executes instead of re-deriving context. It prefers fewer, larger chunks because every dispatch pays a fixed overhead. A dispatch that dies with an error is re-dispatched unchanged instead of absorbed into the expensive main loop. Plans build on scout-verified findings, not on memory. Each rule ports a caveat from Anthropic's cookbook.
Worker reports open with a status - done as dispatched, done with deviations, partial or blocked. Large changes report per-file line ranges instead of pasting whole diffs, so the planner's context stays lean and review happens in git diff.
cleat kit show tells you how to verify routing - after a heavy session /usage should show the bulk of tokens on the worker model. A heavy session showing only your session model means delegation is not happening.
Parallel worker dispatch is out - launching several workers in one message scrambled briefs, spawned duplicates and clobbered files on a real build. The failure classes match open upstream Claude Code bugs (duplicated parallel fan-out, concurrent edit clobbering), unfixed as of this release. The fragment now dispatches one worker at a time: dispatch, review, then dispatch the next. The ban lifts once upstream ships deduplication and edit protection.
The confirm screen stops overstating scout - scout structurally lacks Edit and Write but its Bash stays inspection-only by instruction, so the screen now says read-only by contract instead of stating it flat. When your own agent shadows a kit agent by name, the collision warning now says the kit's policy will steer your agent with its model and its tools.
1447 (+8) behavioral tests across 47 files - every new fragment rule, the worker report contract, the sequential-dispatch ban, the confirm-screen wording, the collision note and the kit show verification note each pinned by a test
Kit picker: detail pane, chevrons, arrow-key models - the kit list now shows the highlighted kit's full description in a detail pane, so nobody has to enable one to find out what it does. The model rows show the active value in ‹ › chevrons and ←/→ (or space) cycles it, ⏎ or → selects and the hint lines name the keys. The non-TTY text picker lists each kit's description too.
The kit carries its provenance - the enable screen credits the pattern to Anthropic's cookbook by its own title (big models for planning, small models for execution), cleat kit show prints the notebook URL and the description was rewritten around it: worker and scout each run in their own context window so the main session stays lean, the mechanical bulk bills at the worker model's rate and heavy work burns your rate limit far slower.
Arrow keys no longer close the pickers - every unrecognized escape sequence (left and right arrows, PgUp, function keys) decoded as Escape and the picker loops cancel on Escape, so pressing the right arrow on a model row (the most natural key there) closed the picker instead of changing the model. The caps picker shared the decoder and the bug. Only q or a bare Escape cancels now. Unknown keys are ignored.
The clipboard rules reach the box at last - since v0.1.0 the image baked in-box guidance (copying with xclip/pbcopy/clip reaches your host clipboard, paste-back is not supported, exit 0 means the copy worked) at ~/.claude/CLAUDE.md and since v0.1.0 the host ~/.claude mount shadowed it, so no box ever saw it. The generated CLAUDE.md every box mounts now composes three marked layers: your global content first, byte for byte, then the box notes, then the kit section when one is enabled. Existing boxes pick it up on their next start with no recreate.
1439 (+15) behavioral tests across 47 files - the keypress decoder pinned key by key, the picker driven through arrows, chevrons, pane and cancel paths on a real pty, the three-layer CLAUDE.md compose and its ordering, a byte-identity guard between the box notes and the image bake and a pane-fit guard so no future kit description silently truncates
The kit's scout subagent loads again. Every mode of working now routes to the kit's cheap models.
A live probe of the plan-big-execute-small kit (dispatch each agent, ask for its model) showed the worker answering on Sonnet 5 and the scout never registered at all. The follow-up audit found that even a healthy kit let exploration and multi-agent fan-outs ride expensive models.
Kit scout agent registers again - since v1.2.0 the generated kit-scout.md carried a colon-space inside its unquoted YAML description ("Use for all exploration: finding"), which is invalid YAML and Claude Code drops an agent whose frontmatter fails to parse without a word. Every scout dispatch errored with "agent not found" and the planner quietly fell back to searching in the main loop, so the kit ran at half strength with nothing visibly broken. The description is reworded, the no-colon constraint is documented at the source and a regression test now validates every generated kit agent frontmatter as strict plain-scalar YAML. Existing kit boxes heal on their next cleat start with no recreate (the overlay regenerates in place). The scout appears in the first session started after that.
Kit routing covers every mode of working - the kit's delegation policy only governed the planner's own loop, so exploration could ride the built-in Explore agent, multi-agent workflow fan-outs defaulted to the expensive session model and sizable single-file work never triggered the worker. The policy now names worker and scout as the only subagents the planner dispatches (built-ins are banned by name), routes workflow stages through them explicitly, widens the dispatch trigger to sizable single-file changes, keeps the locating step of judgment reads on the scout and requires the session to report a missing worker or scout as a broken kit instead of quietly substituting a built-in. The worker also gains a tools pin (Read, Edit, Write, Grep, Glob, Bash) mirroring the scout's.
1424 (+6) behavioral tests across 47 files - a frontmatter class check that generates the real overlay and fails on any unparseable kit agent, plus five policy-content tests (workflow routing, built-in ban, single-file trigger, scout-locates carve-out, worker tools pin), each mutation-verified
Sessions on a root-only host launch again. The memory advisories stop calling a native Linux engine a VM.
A first run on a stock Ubuntu VPS surfaced both: the box came up green and Claude immediately exited and the readiness nod above it claimed a 4 GB "VM" with "room for many parallel sessions" on a host that has neither.
Sessions on a root-only host no longer die at launch - Cleat maps the box user to your host UID so workspace files come back owned by you and on a root-only host (a stock VPS image, or sudo cleat) that makes the box user root, which Claude Code itself refuses to run with permissions skipped (its root/sudo guard, upstream issue 9184). Every session now rides IS_SANDBOX=1, Claude Code's own escape hatch for sandboxed containers, on root hosts only: ordinary boxes keep an unchanged environment and existing root boxes heal on their next session with no recreate.
Memory advisories name the real pool on a native Linux engine - the readiness nod on a 4 GB VPS read "Docker tuned for Cleat (4 GB VM, room for many parallel sessions)": on a native engine no VM exists, nothing was tuned and four 1 GB ceilings is not room for many. Where the daemon does run in a VM (Docker Desktop anywhere and every macOS backend) the wording is unchanged. A native engine now reads "Docker ready for Cleat (N GB RAM, ...)" and drops the parallel-headroom claim below 8 GB and the overload warning and the cleat status overcommit line say the host has only N GB of RAM instead of naming a VM you don't have.
1418 (+7) behavioral tests across 47 files - the root-host IS_SANDBOX injection pinned on both branches at source time, the engine pinned in every readiness, overload and status wording test with refutes covering both readings and a strict-mode smoke pass over the native small-host path
Kits: one command enables a tuned Claude Code setup inside the box. Cleat now starts Docker for you when the daemon is down.
The release that makes a Cleat box the nicest place to run Claude, not just the safest: maintainer-curated kits bring the plan-big-execute-small orchestration pattern to any box with one command. The Docker tax shrinks with an autopilot that launches your engine, waits and continues, plus a consent-first install offer on machines that have no Docker at all.
Kits: curated Claude Code pre-configurations, per box - cleat kit plan-big-execute-small enables the flagship kit, Anthropic's coordinator pattern (big models plan, small models execute) adapted into one command: run your session on Fable 5 and it holds the plan and every review while worker and scout subagents (Sonnet 5 by default) carry the mechanical bulk in their own context windows, so multi-file work burns your rate limit far slower. The kit merges on top of your own config instead of replacing it: your global CLAUDE.md comes first with the kit's delegation policy appended under a marked header, your agents win name collisions and everything applies inside that box only. Enabling, switching and turning a kit off never recreates the container and takes effect next session (boxes created before kits existed lack the mount points, so enabling there offers an explicit rebuild first). Bare cleat kit opens an interactive picker (kit, then per-agent models). cleat kit show prints the kit's full contents before you trust it. cleat kit off returns to vanilla. Pin or swap the agent models under a [kits] section in the global config (worker_model = haiku). The planner is always your session's model and a hint after enabling names the knob (/model inside the session). Adapted from the pattern in Anthropic's cookbook, with its guardrails encoded as behavior: workers report distilled results instead of raw logs, scouts never guess when a search comes up empty and the planner reads subtle code itself instead of trusting a summary.
Read-only masks close the host config write channel - a caged agent could previously write to the user-level ~/.claude/CLAUDE.md, agents and commands that your native host Claude then obeys. Those three surfaces are now read-only masks inside every new box (kit or not): the box sees your real content merged with the kit's, but cannot write a byte of it back. Project-level .claude files in your repo keep working through /workspace. Boxes created before the masks show a one-line recreate note on every start until rebuilt.
Docker autopilot: a down daemon is started for you - cleat on a stopped engine used to die with a raw "Cannot connect to the Docker daemon". Session commands now detect the engine you actually use and start it: Docker Desktop, OrbStack, or Colima (named profiles included) on macOS, Docker Desktop or a rootless engine on Linux and Docker Desktop from inside WSL2, then wait with a spinner and continue. Root-owned Linux engines get the exact sudo command printed instead of a privileged attempt, remote daemons (tcp://, ssh://) are never started for you and a daemon that is up but unreachable because your user lacks the docker group gets the real diagnosis and the usermod remedy instead of a false "Docker is down". Wait is bounded by CLEAT_AUTOSTART_TIMEOUT_SECS (default 90). Opt out entirely with CLEAT_NO_AUTOSTART=1. cleat status now says "Docker isn't running" with the exact start command instead of pretending no boxes exist.
No Docker at all? Cleat offers to install it - when the docker CLI itself is missing, interactive sessions print the exact command and ask before running anything: a three-way Homebrew menu on macOS (Desktop, OrbStack, or Colima, with licensing stated), the official convenience script on Linux (downloaded to a private directory and shown, never piped blind into a shell) and winget under WSL2. Default is No, non-interactive runs never prompt and CLEAT_NO_AUTOSTART=1 suppresses the offer.
Login opens your browser in boxes created before v1.1.1 - the v1.1.1 BROWSER fix landed at container create, but docker exec inherits the container's environment frozen at create time, so existing boxes never saw it and /login stayed on the manual code-paste flow with nothing ever suggesting a recreate. Every session now injects BROWSER at exec time (cleat, cleat shell, cleat login), healing every existing box on its next session with no recreate. A BROWSER= in your .cleat still wins.
A second session can no longer swallow a login link - starting cleat login or a shell next to a running session wiped the browser bridge file unconditionally, so a login URL written moments earlier could vanish before any watcher opened it. The startup sweep is now age-gated: fresh URLs survive and get opened, only stale leftovers from dead sessions are cleared. Every open-or-defer decision in the bridge log now records the URL itself, so a link that "did nothing" can be recovered by hand, including with the bridge set to off.
A fresh machine without ~/.claude/settings.json can create a box - on macOS, creating the very first box on a host where Claude had never written its settings file failed with an opaque Docker error (a nested file mount whose target was missing in the parent bind source). Cleat now pre-creates the file and refuses a broken symlink at any masked path with a clear remedy instead of a raw trace.
cleat describe sees new companions - per-box kit selections live next to box descriptions, are removed with cleat rm and are wiped by cleat nuke.
Total-memory detection fails safe - a garbled /proc/meminfo line could kill a session under strict mode instead of skipping the memory advisory.
The README's top fold now carries a real recorded demo of an agent probing for credentials and wiping the box, not a mockup
1411 (+157) behavioral tests across 47 files - the kit merge and mask semantics (user content first, collisions by agent name, inode-stable regeneration on a running box, the :ro masks, broken-symlink refusal, the pre-mask recreate note), the full autopilot launch matrix (macOS Desktop, OrbStack, Colima profiles, Linux rootless, WSL2 interop gating, remote refusal, bounded timeout, kill switch) and the install offer's consent gates, the exec-time BROWSER injection pinned per exec site, the age-gated bridge sweep from both sides and the picker TUI driven on a real pty
272 (+31) mutations caught, 0 missed, 0 skipped
Note on upgrade: existing boxes keep working untouched. Recreating a box (cleat rm && cleat) is only needed to gain the new read-only masks and each start says so until you do
Logging in to Claude opens your browser again. A second terminal no longer asks you to log in after you have already authenticated.
Two login regressions, both surfacing after a recent Claude Code update: the login link stopped opening the browser and always fell back to pasting a code and a box you had logged out of kept demanding login on every session even after you signed back in elsewhere. Both were traced to changes in how recent Claude builds handle login, then fixed and hardened.
Logging in to Claude opens your browser again - a recent Claude Code update (2.1.191 and later) stopped opening any URL from inside a display-less Linux container unless the BROWSER environment variable is set. In that state it only ever hands its opener the hands-free loopback login URL (the manual code-paste URL is printed, never opened). A Cleat box is display-less Linux and set no BROWSER, so Claude never invoked the container's open shim, the host-side bridge had nothing to open and every login dropped to "paste this code". Cleat now points BROWSER at its bridge shim inside the box, so cleat login (and the first-run login) opens the real login page on your host and completes automatically through the callback proxy, the way it did before. The variable is baked into the image and also passed at container start, so a box created from an older image is fixed without a rebuild and a BROWSER you set in .cleat still wins.
A second terminal no longer re-prompts for login after you have authenticated - logging out inside one box writes hasCompletedOnboarding: false into that box's per-project Claude config and deletes the shared credential file. Recent Claude builds gate the startup login screen on that one flag, so the box you logged out of kept asking you to log in on every session even after you signed back in from another terminal and the shared credential was restored. Cleat only re-asserted that flag when it first created a container, never afterward. It now heals the per-project config whenever you start a stopped box, resume one, or attach to a box that is already running (in place and only when nothing is working inside it) and it carries an identity you established in any box across to boxes that were created earlier. Log in once and every box is logged in.
The browser bridge now covers cleat shell and sweeps its own stale markers - cleat shell runs the same host-side browser watcher a session does, so a login started from a raw shell completes instead of waiting forever on a callback nobody was proxying and the watcher clears expired single-open debounce markers when it starts so a long-lived clip directory cannot accumulate them.
Adopting this release refreshes the image once - the BROWSER shim is baked into the image, so the image content advances to spec 3 and the first start after upgrading offers the usual one-time "refresh the image?" prompt. The fix already works without it (the variable is also passed at container start), so the refresh is optional and version-only releases after it will not prompt again.
1254 (+24) behavioral tests across 45 files - the auth-URL classification truth table and its strict-mode probes, the BROWSER shim present at create and ordered before user [env] so a .cleat override wins, the cross-box identity fallback and the start, resume and running-box attach heal paths (with a live-agent skip and a never-truncate guard), the cleat shell watcher and the stale-marker sweep. Two adversarial-review rounds caught and fixed a real regression in the first cut (a rebuild that could drop a box-only top-level config key for API-key users) before release.
Closed-terminal sessions stop reserving Docker VM memory on their own. A brand-new project no longer asks you to log in to Claude when you are already logged in on the host.
The first two issues after 1.0, both from a single screenshot: a Mac left running for hours where closed-terminal boxes had over-committed the Docker VM and a fresh project that re-prompted for login even though Claude was authenticated on the host and older projects still worked.
Idle sessions are stopped automatically to free Docker VM memory - closing a terminal ends the Claude session but leaves its box running, still holding its memory ceiling, so a day of closed terminals can over-commit the Docker VM and make every session swap. On each interactive start, Cleat now stops other boxes that are safe to stop and tells you what it freed, in one line. A box is only stopped when nothing is running inside it (so a session working unattended, with its terminal left open, is never touched), it is not the box you are launching and it has been idle past a grace window (30 minutes by default). A stopped box is preserved: run cleat in its project to bring it right back. Disable the sweep with CLEAT_NO_IDLE_SWEEP=1, or change the grace with CLEAT_IDLE_GRACE_MINS.
A brand-new project no longer re-prompts you to log in to Claude - on macOS, logging in again on the host rotated the Keychain token and invalidated the older copy in the shared credential file. Sessions already running kept working on a live token, but a freshly created box read the stale one and dropped to a login screen. Cleat now refreshes that shared credential file from the Keychain when its token has expired and the Keychain has a newer, valid one and never overwrites a still-valid in-box token, so a fresh box (and any box on its next resume) starts authenticated. A new project is also pre-trusted inside the cage so a newer bundled Claude does not re-run its first-run onboarding there.
The cleat status memory line can no longer contradict itself - its over-commit warning compared running limits against the kernel's reported VM memory but printed the configured slider size, so a total landing between the two could read as "reserves 23 GB on a 24 GB VM". It now compares and prints in the same whole-GB unit, the same way the on-start advisory already did.
The over-commit notice reads clearly - it now names how many sessions are still running and reminds you that closing a terminal does not stop its box, instead of a bare GB number that looked like an off-by-one against your VM size. "promised X GB" is now "reserve X GB of memory ceilings" and the stale "~4 GB ceiling" wording was corrected (the per-box default is a quarter of the VM, 4 to 8 GB).
1230 (+36) behavioral tests across 45 files - the idle-session sweep (the liveness gate across a live, detached and unreadable process list, the grace window against both mocked and real run-dir timestamps, the unknown-age skip, self-exclusion, multi-box stop with the freed-memory summary and the malformed-argument, terminal and env-var gates), the expiry-aware credential re-seed paths with a pinned clock, the per-workspace onboarding seed, the session-count copy and the status-line contradiction guard and a strict-mode regression for the singular-session notice
The first stable release. A CLI version bump no longer re-downloads the image and recreates your container when nothing in the image actually changed.
Cleat is 1.0. The headline is that upgrades stop being disruptive: the on-start image-refresh prompt is keyed to the image's real content, not the CLI version, so a routine release leaves your running container and its writable layer untouched. This release also pins the base image by digest, fixes two config-parsing bugs and restyles the workspace-trust prompt.
The image refreshes only when the image actually changed - the on-start "refresh the image?" prompt was tied to the CLI version, so every release asked you to re-pull the image and recreate this project's container, discarding its writable layer (anything you installed in the box, caches) even when the image was byte-identical. The decision is now keyed to the image's content, tracked by a spec number baked into the image as a label, so a version-only release leaves your container alone. It still prompts when something that lands in the image genuinely changes: the entrypoint, the clipboard or browser bridge, the Dockerfile, or the base. Images built before this scheme are classified by the version they were built at, so upgrading to 1.0.0 is a single expected one-time refresh to adopt the pinned base, after which version-only releases never recreate again.
The base image is pinned by digest - the container base is pinned to an exact multi-arch image digest instead of a floating tag, so builds are reproducible and a base or security update becomes a deliberate, reviewed change that ships through the same content-aware refresh path instead of drifting in silently.
A hand-edited .cleat no longer drops its last line - a .cleat (or its [resources] block) whose final line had no trailing newline silently dropped that line: a capability listed last was never requested, so the trust prompt never fired and the cap never applied and a final memory = 8g fell back to the VM-derived default instead of your configured ceiling. Both the capability and resource readers now read that final line, matching the env-file parser.
The workspace-trust prompt matches the other startup prompts - approving a project's .cleat capabilities now uses the same single aligned line and [y/N] question as the image-refresh, Claude-update and recreate prompts, with docker flagged amber because it breaks the sandbox, instead of a bordered box. It still defaults to deny: only an explicit yes applies the caps.
1194 (+37) behavioral tests across 44 files - the image content-spec comparison and its legacy version inference (per-field numeric, the equal and just-below boundaries, suffix stripping, leading-zero safety), the no-trailing-newline reads for the capability and resource parsers with mutation-verified regressions, duplicate-key first-wins for resources and the restyled trust prompt (approve, deny, empty-and-EOF default-deny, the no-box style, re-prompt wording and the docker amber rendering)