cleat
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Fail
- rm -rf — Recursive force deletion command in coverage.sh
- rm -rf — Recursive force deletion command in docker/entrypoint.sh
- rm -rf — Recursive force deletion command in install.sh
- rm -rf — Recursive force deletion command in test/mutation_regressions.sh
- rm -rf — Recursive force deletion command in test/setup.bash
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Run anything. Break nothing. Docker sandbox for Claude Code — full autonomous permissions, per-project isolation, zero risk to your host.
Cleat
Run anything. Break nothing.
Run AI coding agents with full autonomous permissions — safely sandboxed in Docker.
One command. Per-project isolation. Zero risk to your host.
curl -fsSL https://cleat.sh/install | bash
cd ~/your-project && cleat
That's it. First run pulls the prebuilt image from GHCR (~30s), starts an isolated container for your project, and drops you into Claude Code with full permissions, all sandboxed. If the prebuilt image is unavailable for your CLI version, it falls back to a local build (~2 min) automatically.
┌─────────────────────┐ ┌─────────────────────────────────┐
│ Your machine │ │ Docker container │
│ │ │ │
│ ~/my-project ───────────> │ /workspace │
│ ~/.claude ──────────────> │ /home/coder/.claude │
│ │ │ │
│ Everything else │ │ Claude Code runs free here: │
│ is untouched. │ │ install, build, delete, run │
│ │ │ anything. Fully sandboxed. │
└─────────────────────┘ └─────────────────────────────────┘
Requirements
- Docker -- must be installed and running
- macOS or Linux (Windows support via WSL2)
- An Anthropic account -- Pro, Max, Team, or Enterprise plan, or an API key
- git -- used by the installer
Why Cleat?
The problem
Claude Code with --dangerously-skip-permissions is the fastest way to build software with AI. No confirmation dialogs, no permission prompts. Claude just does what you ask. But on your actual machine, that means:
- System files and configs can be modified or deleted
- Packages can be installed, upgraded, or removed system-wide
- Dotfiles, SSH keys, or credentials can be read or overwritten
- Other projects on your machine can be accessed or changed
- A single bad command can render your OS unbootable
The solution
Cleat gives you the best of both worlds:
| Without isolation | With Cleat | |
|---|---|---|
| Claude can edit project files | Yes | Yes |
| Claude can install packages | Yes (on your system) | Yes (in container) |
| Claude can run any command | Yes (on your system) | Yes (in container) |
| Claude can access other projects | Yes | No |
| Claude can modify your system | Yes | No |
| Claude can read ~/.ssh, credentials | Yes | Opt-in (via cleat config) |
| Safe to leave running overnight | No | Yes |
| File ownership issues | N/A | None (UID/GID mapped) |
| Copy to host clipboard | Yes | Yes (via clipboard bridge) |
Key features
- One command --
cleatpulls (or builds) the image, starts a container, and launches Claude Code - Per-project isolation -- each project gets its own container, run multiple projects in parallel
- Session persistence -- stop and resume sessions without losing context, each project's history is isolated
- Safe for unattended use -- let Claude work overnight without risking your system
- Zero file permission issues -- container user matches your host UID/GID automatically
- Shared auth -- log in once, all containers use the same credentials
- Clipboard support --
pbcopy,xclip, andxselshims route to your host clipboard via a file bridge -- no X11 or special terminal features needed - Lightweight -- Node.js-based image with Python, Git, GitHub CLI, jq, and socat
- Capabilities -- opt-in access to host git identity (
--cap git), SSH keys (--cap ssh), env var passthrough (--cap env), host hook execution (--cap hooks), GitHub CLI auth (--cap gh), and host Docker daemon for testing dockerized apps (--cap docker); all disabled by default - Pre-built image --
cleat startpulls fromghcr.io/cleatdev/cleat(~30s) instead of building locally (~2-5 min), with automatic local-build fallback - Hook execution on host -- your Claude Code hooks (global and project-level) run on the host, not in the container
- Browser bridge --
openandxdg-openinside the container forward URLs to your host browser (auth, OAuth, docs) - Host connectivity --
host.docker.internalalways available, user-defined hooks and MCP servers work out of the box - Configuration drift detection -- notifies when config has changed since container creation
- Clean terminal output -- braille spinners for slow operations, suppressed Docker noise, canonical startup/exit sequences
- Auto-upgrade notifications -- checks for updates every 10 minutes and notifies you before launching Claude
- Release highlights -- a one-time, non-blocking note on the first run after an update tells you the new version's headline feature
The story behind this
I was deep into vibe coding. Shipping features fast, letting Claude Code run with --dangerously-skip-permissions so it could execute anything without interrupting my flow. It was incredible. I'd kick off tasks, step away, come back to working code. I was running multiple projects on my Mac, sometimes leaving Claude running overnight while it worked through larger refactors.
Then one morning I opened my laptop and nothing worked. The system was completely broken. Apps wouldn't launch, the terminal was unusable, core system files had been modified. Claude had been working autonomously through the night, and somewhere along the way it had started making changes outside the project directory. It tried to fix a dependency issue by modifying system-level configs, which cascaded into more "fixes" across the filesystem. By the time it was done, macOS was unrecoverable.
I had to restore my entire machine from a Time Machine backup. Hours of setup, re-authenticating everything, recreating local state that wasn't backed up. All because I gave an AI unrestricted access to my actual system.
The thing is, I didn't want to stop using --dangerously-skip-permissions. The productivity gain is real. Claude Code without permission gates is a different experience entirely: it moves fast, installs what it needs, runs builds and tests, iterates on errors, all without waiting for you to click "allow" fifty times. Going back to the default permission mode felt like putting the brakes back on.
So I built Cleat. Same unrestricted power, but inside a Docker container where the blast radius is zero. Claude can rm -rf / inside the sandbox and my Mac won't even notice. Each project gets its own container, my host system is completely untouched, and I never have to worry about what Claude does when I'm not looking.
I haven't restored from a backup since.
Install
Quick install (recommended)
curl -fsSL https://cleat.sh/install | bash
This clones the repo to ~/.cleat, checks out the latest stable release tag, and symlinks cleat into your PATH. The short URL resolves to the same install.sh served from the latest tagged release on GitHub.
Dev install (from local clone)
git clone https://github.com/cleatdev/cleat.git
cd cleat
./install.sh --local
This symlinks your working copy into PATH. Edits to bin/cleat take effect immediately — no reinstall needed. Switch back to the official release at any time with ./install.sh (without --local).
Update
Releases are published as git tags (e.g. v0.1.0). The updater fetches tags and checks out the latest one:
cleat update
To update just the Claude Code build bundled in the image — without a full rebuild:
cleat upgrade-claude # latest (default)
cleat upgrade-claude stable # stable channel
cleat upgrade-claude 2.1.156 # pin a version
This re-runs the official installer in the image and commits it back, then offers to recreate the current project's container so the new version takes effect immediately. The change is local-only — cleat rebuild/update/nuke reset the image to a fresh release build (which already bundles a current Claude Code).
You don't have to remember to run it: when you start cleat interactively, it checks (at most once every 10 minutes) whether a newer Claude Code is out and offers to upgrade before starting. The check is skipped for non-interactive runs, never blocks on a slow network, defaults to the latest channel (CLEAT_CLAUDE_CHANNEL=stable to change it), and can be turned off with CLEAT_NO_CLAUDE_UPDATE_CHECK=1.
To rebuild the whole image from scratch instead:
cleat rebuild
Getting started
1. Authenticate (first time only)
cd ~/your-project
cleat # starts the container + launches Claude
# Claude will prompt you to log in on first run
Or authenticate separately:
cleat start # start the container
cleat login # opens a browser URL to sign in
Credentials are saved to ~/.claude on your host and shared across all containers automatically. Log in once, every container picks it up.
2. Use it
cd ~/your-project
cleat
That's it. You're inside Claude Code with full autonomous permissions, sandboxed in Docker.
Usage
Daily workflow
# Start a new session
cd ~/my-project
cleat
# Resume your last session
cleat resume
# Check what's running
cleat ps
# Stop when done (keeps container for resume)
cleat stop
# Remove the container when you want a fresh environment.
# Session history lives on the host at ~/.claude/projects/<key>/
# and is NOT touched by cleat rm — `cleat resume` after rm
# auto-creates a fresh container and picks up where you left off.
cleat rm
cleat resume
Multiple projects at once
Each project gets its own isolated container:
# Terminal 1
cd ~/backend && cleat
# Terminal 2
cd ~/frontend && cleat
# See all running containers
cleat ps
Cleat containers:
● cleat-backend-1a2b3c4d
Up 12 minutes
/Users/you/backend
● cleat-frontend-5e6f7a8b
Up 3 minutes
/Users/you/frontend
Boxes — multiple sandboxes per project
A box is a named, isolated container scoped to the current directory. Every
box mounts the same live files, but each has its own capabilities, writable
layer, and Claude session — so a locked-down dev box can run beside a
cloud-capable az box over the same repo, and the agent in dev can't reach the
Docker socket or cloud token that az holds.
cleat start # the default box (main)
cleat start az --desc "cloud box" # a separate az sandbox
cleat config az --enable docker # give just the az box the docker cap
cleat resume dev # resume the dev box's last session
cleat status # list this project's boxes
The token after a verb is a box name (lowercase letters, digits, -, _),
never a path — cleat always operates on the current directory. The default box
is byte-identical to the pre-boxes container, so existing projects keep working
unchanged. Per-box caps come from .cleat.<box> (replace, not merge — a box can
have fewer caps than .cleat). One caveat: ~/.claude (your Anthropic auth)
is shared across boxes — a box isolates host capabilities and the writable layer,
not your Claude login.
Command reference
Quick start
| Command | Description |
|---|---|
cleat |
Build + run + launch Claude Code (all-in-one) |
cleat resume |
Resume the most recent session (recreates the container if cleat rm was run since — sessions persist on the host) |
Lifecycle
| Command | Description |
|---|---|
cleat stop [box] |
Stop this project's container (keeps it for resume) |
cleat rm [box] |
Stop and remove container permanently (session history on the host is preserved) |
cleat stop-all |
Stop all Cleat containers |
cleat build |
Build the Docker image |
cleat rebuild |
Force rebuild the image from scratch |
cleat upgrade-claude [stable|latest|VERSION] |
Update the bundled Claude Code in place (default latest); offers to recreate the current container |
cleat clean |
Stop everything and remove the image |
cleat nuke |
Remove all containers, images, and build cache |
Capabilities
| Command | Description |
|---|---|
cleat config |
Interactive capability picker (keyboard TUI) |
cleat config --list |
List capabilities and their status |
cleat config --enable <cap> |
Enable a capability (e.g. git, ssh, env) |
cleat config --disable <cap> |
Disable a capability |
cleat config --project --enable <cap> |
Project-level config (saved to .cleat) |
cleat config <box> --enable <cap> |
Per-box config (saved to .cleat.<box>; replaces .cleat for that box) |
Workspace trust
| Command | Description |
|---|---|
cleat trust [path] |
Record approval for a project's .cleat capabilities |
cleat trust --list |
List trusted projects (yellow = .cleat changed since approval) |
cleat untrust [path] |
Remove a project's trust entry |
Flags (apply to start, run, resume, claude, shell, login)
| Flag | Description |
|---|---|
--cap <name> |
Enable a capability for this session only |
--env KEY=VALUE |
Pass environment variable to container |
--env KEY |
Inherit from host environment |
--env-file PATH |
Load env vars from file |
--trust-project |
Auto-approve the current project's .cleat without prompting |
--desc <text> |
Set the box's description at start (host-side; never recreates) |
Interact
| Command | Description |
|---|---|
cleat claude [box] |
Attach Claude Code to a running container |
cleat shell [box] |
Open bash inside the container |
cleat login [box] |
Authenticate with Anthropic (OAuth) |
cleat logs [box] |
Tail container logs |
Info
| Command | Description |
|---|---|
cleat status |
Show this project's boxes, image, and auth status |
cleat describe [box] [text] |
Show or set a box's description (host-side; never recreates) |
cleat ps |
List all Cleat containers (running and stopped, with a box column) |
cleat update |
Check for updates and install the latest version |
cleat version |
Show current version |
All commands operate on the current working directory. The optional [box] is a
named sandbox for the project (default: main) — see Boxes above.
How it works
Architecture
┌──────────────────────────────────────────────────────────────┐
│ Your machine │
│ │
│ ~/.claude ──────────────┐ (auth, sessions, settings) │
│ ~/.claude.json ─────────┼── (config) │
│ ~/my-project ───────────┼──────────────────────┐ │
│ │ │ │
│ ┌────────────────────────┼──────────────────────┼───────┐ │
│ │ Docker container │ │ │ │
│ │ v v │ │
│ │ /home/coder/.claude /workspace │ │
│ │ /home/coder/.claude.json │ │
│ │ │ │
│ │ Claude Code (--dangerously-skip-permissions) │ │
│ │ │ │
│ │ Can: read/write project, install packages, run cmds │ │
│ │ Cannot: touch host system, access other projects │ │
│ └───────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Components
| File | Purpose |
|---|---|
bin/cleat |
CLI script (symlinked as cleat) |
docker/Dockerfile |
Node.js bookworm-slim image with Claude Code (native installer) |
docker/entrypoint.sh |
Maps host UID/GID into the container so files are owned by you |
docker/clip |
Clipboard shim -- writes to file bridge (primary) or OSC 52 daemon (fallback). Symlinked as pbcopy, xclip, xsel |
docker/clip-daemon |
Background daemon -- relays clipboard data to the host terminal via OSC 52 (fallback for terminals that support it) |
docker/CLAUDE.md |
User-level instructions for Claude Code (clipboard usage, paste limitations) |
install.sh |
One-line installer (curl | bash) |
What happens when you run cleat
- Pulls or builds the Docker image (first run only) -- pulls pre-built image from registry (~30s), falls back to local build if unavailable. Image includes Node.js, Python, Git, GitHub CLI, jq, socat, and Claude Code CLI
- Starts a container named
cleat-<dirname>-<hash>(hash derived from the full project path) with your project mounted at/workspace - Maps your UID/GID into the container so files created by Claude are owned by you on the host
- Mounts
~/.claudefor shared authentication across all containers - Starts the clipboard bridge -- a host-side watcher and a shared file mount so
pbcopy/xclip/xselrelay to your host clipboard - Launches Claude Code with
--dangerously-skip-permissionsinside the sandbox
Security hardening
Containers run with these protections by default:
--pids-limit 4096-- prevents fork bombs from affecting the host- A per-box memory ceiling (a quarter of your Docker VM's memory, clamped to 2-8 GB) with swap disabled -- a runaway process OOMs inside its own box instead of swap-thrashing every session at once. Override with a
[resources]section (memory = 4g, optionallycpus = 2) in~/.config/cleat/configor<project>/.cleat; repo-supplied values are capped (8g memory, your core count for cpus). CPU is unlimited unless you set it -- an idle core costs nothing --init-- a real PID 1 reaps orphaned processes, so long sessions can't wedge on zombie buildup andcleat stopis instant- Numeric UID/GID validation in the entrypoint to prevent injection attacks
- Node.js bookworm-slim base image with minimal attack surface
Images are published multi-arch (amd64 + arm64): Apple Silicon runs natively, never under emulation. cleat prune clears cleat's own stale images (cleat also offers this automatically when they pile up); boxes and other projects' images are never touched.
Capabilities
Capabilities are opt-in features that extend what the container can access from the host. They are disabled by default — the baseline container is locked down, and each capability explicitly widens the boundary.
Enable capabilities
# Interactive wizard
cleat config
# Direct mode
cleat config --enable git
cleat config --enable ssh
cleat config --enable env
# One-off (session only, no config change)
cleat --cap ssh start
Available capabilities
| Capability | Category | What it does |
|---|---|---|
git |
mount | Mounts ~/.gitconfig (read-only). Commits inside the container use your host identity. |
ssh |
mount | Mounts ~/.ssh (read-only). SSH agent forwarding if SSH_AUTH_SOCK is set. |
env |
mount | Auto-loads env vars from ~/.config/cleat/env (global) and .cleat.env (project). |
hooks |
mount | Runs your Claude Code hooks on the host (global and project-level). |
gh |
mount | Mounts ~/.config/gh (read-write). gh auth login inside container writes tokens to host. |
docker |
sandbox | Mounts /var/run/docker.sock. docker, docker compose, and anything that talks to the daemon run against your host — sibling containers, zero overhead. Sandbox-escaping — see security note below. |
Cloud CLI caps (
az,aws,gcloud) and the lazy-install framework that backed them shipped in v0.11.0 / v0.12.0 and were removed after v0.12.3 — they bloated first-run time without earning their weight. Install the CLI on the host and pass credentials via theenvcap.
Display categories
The post-launch summary and cleat status group active caps by behavior:
- mount (green):
git,ssh,env,hooks,gh— bind-mount auth/identity, no install. - sandbox (amber):
docker— mounts the host socket, breaks isolation.
When only one category is active the line collapses to a single coloured row. With caps in both categories, the renderer prints a labeled block — same UI on the landing page mockups so the CLI and the marketing copy stay in lockstep.
Workspace trust — project .cleat approval
A project's .cleat file lives in the repo — whoever controls the repo controls that file. Cleat won't silently apply a .cleat's capabilities on first run. Instead, on first launch inside a project with a .cleat, you'll see:
┌─────────────────────────────────────────────────────────────────────┐
│ This project's .cleat file requests capabilities │
│ that extend what the sandbox can access on your host. │
│ │
│ Requested: │
│ │
│ docker Host Docker socket (breaks sandbox) to test Docker apps │
│ env Load env vars from ~/.config/cleat/env and .cleat.env │
│ │
│ Project: /Users/you/proj │
└─────────────────────────────────────────────────────────────────────┘
Trust this project's .cleat? [y/N]:
Say yes and the approval is stored at ~/.config/cleat/trust. Next launch, nothing to see — Cleat silently applies the caps.
Approval is keyed on the canonical list of capabilities declared in .cleat, not the raw file. Comment edits and cap reordering don't invalidate trust. Adding, removing, or changing a cap triggers a re-prompt with an "…has changed since you trusted it" framing.
Scripting & CI
Non-interactive contexts (pipes, CI, cleat … | tee log) can't answer a prompt, so they default-deny: project .cleat caps are silently dropped, global config and --cap flags still apply. To opt in explicitly:
cleat --trust-project # one-off session flag
CLEAT_TRUST_PROJECT=1 cleat # env var (same effect)
cleat trust # persist for this project, once
Subcommands
cleat trust # trust the current dir's .cleat
cleat trust ~/proj # trust a specific project
cleat trust --list # show all trusted projects
cleat untrust ~/proj # remove a project's trust entry
What trust covers
| Source | Trusted? |
|---|---|
~/.config/cleat/config (global) |
✔ always — user's own file |
--cap <name> CLI flag |
✔ always — affirmative typed action |
<project>/.cleat |
requires approval per-project, per-cap-set |
cleat status never prompts — it's read-only and silently omits untrusted project caps when displaying.
Docker capability — testing dockerized apps
When docker is enabled, the container mounts the host Docker socket and can build, run, and manage containers against the host daemon. Containers you launch from inside Cleat run as siblings on the host — not nested — so there's zero virtualization overhead:
cleat config --enable docker # persistent
cleat --cap docker # one-off session
# Then, inside the sandbox:
docker compose up -d
docker compose exec app npm run test:ci
docker build -t myapp .
docker run -v $(pwd):/app node:24 npm install
Cleat also bind-mounts your project at its host path inside the container (in addition to /workspace) and sets workdir there, so $(pwd) returns a host-valid path. This makes docker run -v $(pwd):/app … and relative paths like -v ./data:/data in docker-compose.yml resolve correctly on the host daemon.
The CLEAT_HOST_PROJECT environment variable is exported with your project's host path for scripts that want it explicitly.
Security note. The Docker socket grants root-equivalent access to your host. Any process inside the container that can reach
/var/run/docker.sockcan create a container that mounts/from the host and escape the sandbox (this is a property of Docker, not Cleat). When the capability is active, Cleat prints an amber warning on startup:! Docker socket mounted — container can create host-level processesEnable this capability only in projects you trust — and disable it when you don't need it. It's off by default and every activation is explicit (
cleat config --enable dockeror--cap docker).
Known limitations in v0.10.0:
- Literal
/workspace/…paths in-varen't translated — Docker errors cleanly that the source doesn't exist. Use$(pwd)or the host path instead. - Paths created inside Cleat at locations that don't exist on the host (e.g.
/tmp/scratchaftermkdir -p /tmp/scratchinside Cleat) will be created on the host as empty directories. Keep bind-mount sources under your project path.
Environment variables
The env capability controls automatic loading of env files. The --env and --env-file flags always work, regardless of whether the capability is enabled:
# These always work (bypass capability gate)
cleat --env GH_TOKEN=abc123 start
cleat --env GH_TOKEN start # inherit from host
cleat --env-file .env.local start
# These require the env capability
# ~/.config/cleat/env ← global
# .cleat.env ← project-specific
Configuration drift detection
When you change capabilities or env keys after a container was created, Cleat detects the mismatch the next time you run cleat, cleat resume, or cleat claude. On a TTY it prompts you to recreate (a plain-text line, no box):
▸ Config changed since cleat-<project> was created — caps or env keys differ from the running setup
Recreate cleat-<project> now? [Y/n]
Accepting removes the container and rebuilds it with the new caps/env. Sessions persist on the host (~/.claude/projects/<key>/) and are never touched. Declining keeps the existing container.
A Cleat version bump on its own does not trigger this — the drift check looks only at caps and env keys. When a new CLI ships container changes, the separate image-rebuild prompt offers a rebuild (the only thing that actually applies them).
Non-TTY runs (CI, scripts) print the notice and continue with the existing container — they never auto-destroy.
Config files
~/.config/cleat/config ← global capabilities
~/.config/cleat/env ← global env vars
<project>/.cleat ← project-level capabilities (extends global)
<project>/.cleat.env ← project-level env vars
Terminal output
Cleat uses a clean, consistent output format with no Docker noise.
Startup
✔ Image ready (cached)
✔ Container started
✔ Auth shared
✔ Claude launched
Container: cleat-backend-a1b2c3d4
Project: ~/backend → /workspace
Caps: git, ssh
Slow operations (image build, container start) show animated braille spinners that resolve to checkmarks. When stdout is not a TTY (piped, CI), spinners degrade to static lines.
Exit
✔ Session ended — resume with: cleat resume
Docker's "What's next?" promo text and clipboard watcher cleanup messages are suppressed.
Hooks
When the hooks capability is enabled, your Claude Code hooks run on the host — exactly as if you weren't using a container. Hooks from all three settings locations are supported:
~/.claude/settings.json(global).claude/settings.json(project, committed).claude/settings.local.json(project, local)
cleat config --enable hooks # enable persistently
cleat --cap hooks start # enable for one session
How it works
- Cleat creates a settings overlay that replaces hook commands with an event forwarder inside the container
- Project-level hook settings are also overlaid to prevent double-execution
- A host-side bridge reads forwarded events and executes the original hook commands on the host
- Event JSON is piped to stdin, matchers are respected, 30s timeout per command
- Commands like
osascript, local scripts, and anything host-specific work transparently
Browser bridge
When Claude Code or any tool inside the container calls open or xdg-open with a URL, it opens in your host browser. OAuth callbacks are automatically proxied back to the container — cleat login and any auth flow work seamlessly without manual URL copy-paste. No capability needed.
Host connectivity
Containers can always reach services on the host via host.docker.internal — no capability needed. User-defined hooks, MCP servers, and HTTP endpoints on the host work out of the box.
# In .cleat.env (with env capability enabled)
CLAUDE_VISUAL_URL=http://host.docker.internal:3200
On Linux (Docker Engine), Cleat adds --add-host host.docker.internal:host-gateway automatically. Docker Desktop (macOS/Windows) provides this natively.
Auto-upgrade notifications
Cleat checks for new release tags at most once every 10 minutes via git ls-remote --tags (a lightweight network call that fetches no objects). When a newer version is available, you'll see a notice before Claude Code launches:
┌──────────────────────────────────────────────────────┐
│ Update available v0.4.0 → v0.5.0 │
│ Run cleat update to install the latest version. │
└──────────────────────────────────────────────────────┘
- The check runs at most once every 10 minutes — it will not slow down subsequent launches.
- The result is cached in
.update_checkinside the installation directory (~/.cleat). - The notification is informational only — it will never interrupt or block your workflow.
- To upgrade, run
cleat update. To also update Claude Code inside containers, follow up withcleat rebuild.
Clipboard support
Clipboard works out of the box. When Claude Code (or any tool) calls pbcopy, xclip, or xsel inside the container, the text is copied to your host machine's clipboard -- no X11, display server, or special terminal features required.
How it works
A host-side clipboard watcher starts automatically alongside every Claude Code session. The container writes clipboard data to a shared file via a bind mount, and the watcher detects changes and copies the content to your real clipboard using pbcopy (macOS), xclip, xsel, or wl-copy (Linux).
┌─────────────────────────────┐ ┌──────────────────────────────┐
│ Docker container │ │ Host │
│ │ │ │
│ Claude Code │ │ │
│ └─ echo "text" | pbcopy │ │ │
│ └─ writes to ────────────> │ /tmp/cleat-clip-*/clipboard │
│ /tmp/cleat-clip/ │ │ └─ watcher detects change │
│ │ │ └─ pbcopy / xclip │
│ │ │ └─ ✔ clipboard! │
└─────────────────────────────┘ └──────────────────────────────┘
An OSC 52 fallback is available for terminals that support it, used automatically when the file bridge is not active.
# These all work inside the container -- including from Claude Code:
echo "hello" | clip # dedicated helper
echo "hello" | pbcopy # macOS-style
echo "hello" | xclip -selection clipboard # Linux-style
echo "hello" | xsel --clipboard # Linux-style (alternative)
git log -1 --format=%B | clip # copy last commit message
Limits: Payloads are capped at 100KB. Paste (xclip -o, xsel --output, pbpaste) is not supported -- clipboard is copy-only.
Troubleshooting
Clipboard not working
If pbcopy/xclip/xsel inside the container doesn't copy to your host clipboard:
- Check the bridge is active -- inside the container, run
ls /tmp/cleat-clip/.host-ready. If the file exists, the host watcher is running. - Check clipboard commands on the host -- the watcher needs
pbcopy(macOS),xclip,xsel, orwl-copy(Linux) available on your PATH. - Rebuild the container -- if you upgraded from an older version, run
cleat rm && cleat startso the new clipboard mount is created. - Large payloads -- clipboard is capped at 100KB. For larger content, write it to a file in
/workspaceand copy from the host.
Docker not running
Cannot connect to the Docker daemon
Start Docker Desktop or the Docker daemon, then retry.
Permission denied on install
# If /usr/local/bin is not writable, the installer uses sudo automatically.
# You can also install to a custom location:
ln -sf "$(pwd)/bin/cleat" ~/.local/bin/cleat
Container naming
Each container is named cleat-<dirname>-<hash> where the hash is derived from the full absolute path of the project directory. This means two projects with the same directory name (e.g. ~/code/client-a/api and ~/code/client-b/api) get separate containers automatically. The container name is printed before every session so you always know which sandbox you're in.
Rebuilding after Claude Code updates
The Claude Code CLI is baked into the Docker image. To get the latest version:
cleat rebuild
Files created as root
This shouldn't happen. The entrypoint maps your host UID/GID. If it does, check that Docker is passing through HOST_UID and HOST_GID correctly:
cleat shell
id # should show your UID/GID
Uninstall
cleat clean # remove all containers + image
cleat uninstall # remove CLI symlinks
rm -rf ~/.cleat # remove the repo clone
Your project files and ~/.claude credentials are never touched.
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
git clone https://github.com/cleatdev/cleat.git
cd cleat
# Make your changes on main, test locally
./bin/cleat start ~/some-test-project
Releasing
Releases are cut by tagging a commit on main:
git tag v0.3.0
git push --tags
The installer and updater both resolve the latest semver tag automatically. No release branch is needed.
License
Cleat — Run anything. Break nothing. | Docker sandbox for AI coding agents | cleat.sh
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found