Manage independent local workspaces for parallel coding agents, with one worktree per task, explicit native/container runtimes, managed processes and safe cleanup. Use for parallel task worktrees, port allocation, starting or inspecting task services, running tests inside a workspace, or reclaiming workspace state.
Installs into .claude/skills of the current project.
Are you the author of Berth?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mrjwj34-berth)
---
name: berth
description: Manage independent local workspaces for parallel coding agents, with one worktree per task, explicit native/container runtimes, managed processes and safe cleanup. Use for parallel task worktrees, port allocation, starting or inspecting task services, running tests inside a workspace, or reclaiming workspace state.
---
# berth
berth gives each task its own Git worktree, private data directory, reserved ports
and supervised processes, so parallel agents stop colliding. It is a workspace
manager, not an adversarial security sandbox and not a Git replacement. Replace
berth lifecycle operations only with berth commands — raw `rm -rf`, `git worktree
remove` and guessed PID/port cleanup corrupt the registry instead of releasing a
workspace.
## Prerequisites
This skill is instructions, not the tool: every command in it needs the `berth`
executable on `PATH`. Always start with `berth version`. If the command is
missing, install it, verify, and only then plan any work:
```sh
go install github.com/Mrjwj34/berth/cmd/berth@latest # Go 1.25 or newer
```
```sh
brew install Mrjwj34/tap/berth # macOS, Linux
```
```powershell
scoop bucket add berth https://github.com/Mrjwj34/scoop-bucket; scoop install berth # Windows
```
With no package manager available, download the archive for the platform from
`https://github.com/Mrjwj34/berth/releases`, put the `berth` (or `berth.exe`)
executable on `PATH`, and re-run `berth version`. Installing the skill without
the binary is a normal state — for example when the skill arrived through a skill
installer rather than through a package manager.
If the binary genuinely cannot be installed, say so and stop. Never emulate a
berth lifecycle with raw `git worktree`, `rm -rf` or hand-picked ports: the
machine registry, the workspace locks and the recorded state would be left
inconsistent, and the next real berth command would have to clean up after you.
## Onboard
Read the project's startup scripts, toolchains, storage paths and listen ports
before writing configuration, then choose the runtime deliberately:
- `native` (default) for programs that accept a port and a data path through
configuration or flags. Fastest, no container overhead.
- `container` for unchanged hardcoded listen ports or a uniform Linux
environment. It needs an existing `runtime.image` plus a `listen.<port>` entry
per declared port.
Commit `berth.yaml` before creating a workspace: each workspace loads the
configuration from its own branch, so uncommitted edits never reach a workspace
created from it. When the container engine or the prepared image is missing,
report it and stop — the service runs on the host only when the configuration
says `native`.
Keep mutable state in `$BERTH_DATA_DIR` (`<workspace>/.berth/data`). `copy_dirs`
copies writable dependency trees with CoW where the filesystem supports it and an
independent copy otherwise.
## Execute
| Command | Purpose |
| --- | --- |
| `berth new <slug> [--base <ref>] [--up] [--print-path] [--json]` | Create a workspace and its branch-local setup; `--up` also starts processes |
| `berth ls [--json]` | List workspaces with their phase, running state and ports |
| `berth status [<slug>] [--json]` | Processes, readiness and ports of a workspace |
| `berth ports [<slug>] [--json]` | Allocated host ports and container listen mappings |
| `berth plan [<slug>]` | Print the execution contract as JSON without starting anything |
| `berth up [<slug>] [--json]` / `berth down [<slug>]` | Start / stop workspace processes, preserving data; `up` prints ports and readiness |
| `berth logs <proc> [<slug>]` | Logs of one managed process |
| `berth run [--] <cmd> [args...]` | Run one command inside the workspace runtime and environment |
| `berth attach [<slug>] [--json]` | Print path, branch and shell exports for the workspace |
| `berth open [<port-name>]` | Open a workspace port's host URL in the browser |
| `berth reset [<slug>]` | Wipe `$BERTH_DATA_DIR` and rerun setup |
| `berth done [<slug>]` | Tear a workspace down once its work is preserved |
| `berth gc [--dry-run] [--json]` | Reclaim vanished, idle, merged or excess workspaces |
| `berth doctor [--fix] [--json]` | Check git, engine, image, process-compose and state |
### Where a workspace is
- Path: `<repo>.berths/<slug>` beside the repository by default, or
`<worktree_root>/<slug>` when `worktree_root` is set. Branch: `berth/<slug>` by
default. The branch is not identity: a workspace stays usable after
`git checkout -B fix/<name>` or `hotfix/<name>`, and `BERTH_BRANCH` follows HEAD.
- Slug rules: lowercase ASCII letters, digits, `-` and `_` only, no slashes.
- The primary checkout is never a workspace (`berth adopt` refuses it). `berth run`
and `berth open` accept no slug, so they resolve the workspace from the current
directory; commands that do take `[<slug>]` also work from anywhere with an
unambiguous slug. Run slug-less commands from inside the path `berth new`
printed (its last stdout line, or `path` in `--json`).
`berth run` joins the same runtime and environment as services and hooks, passes
argv verbatim without reparsing, and propagates the exit code. Request a shell
explicitly: `berth run -- sh -c '<script>'` (Linux/container) or the matching
native shell on Windows, since a script that works in `sh` need not work in
Windows `cmd`. Cancelling a container `berth run` stops that workspace's runtime,
including its sibling services.
### Environment contract
- `BERTH_PORT_<NAME>` is the port inside the execution context;
`BERTH_HOST_PORT_<NAME>` is the host publication to hand to browsers and other
host tools. Container publications are TCP over IPv4 loopback.
- `plan.gateway_ports` are internal forwarder reservations; application listeners
must not use those values, and undeclared internal ports are isolated but not
published.
- Identity: `BERTH_WORKSPACE`, `BERTH_ROOT`, `BERTH_DATA_DIR`, `BERTH_SLUG`,
`BERTH_BRANCH`, plus `GIT_DIR`, `GIT_WORK_TREE`, `HOME` and `XDG_CACHE_HOME`
inside a container.
- On Windows with `native` and a non-empty `processes:`, `berth ports` also lists a
`pc` port: the supervisor control port, not an application port.
- Read readiness from `berth status --json` (`ready`, `healthy`) instead of sleep
loops, and failures from `berth logs <proc>`.
## Configure
Write `berth.yaml` from the project's own startup commands. A minimal native
service:
```yaml
version: 1
base: main
ports: [web]
processes:
web:
command: npm run dev -- --port "$BERTH_PORT_WEB"
readiness_probe:
http_get: {host: 127.0.0.1, port: "${BERTH_PORT_WEB}", path: /}
```
Read `references/berth-yaml.md` before writing or debugging a `berth.yaml`: it
carries the full field list, the container recipe, and the traps that make a
render fail.
## Harnesses
berth installs one skill. A bare `berth skill install` writes the shared
`.agents/skills/berth/` copy, which twelve harnesses read: Codex, Cursor, GitHub
Copilot, Gemini CLI, opencode, Windsurf/Devin Desktop, Roo Code, Kilo Code, Zed,
JetBrains Junie, Google Antigravity and pi. Claude Code and Cline do not read that
convention and need their own copy, so name them explicitly:
| Command | Writes |
| --- | --- |
| `berth skill install` | `.agents/skills/berth/` |
| `berth skill install --agent claude` | `.claude/skills/berth/` |
| `berth skill install --agent cline` | `.cline/skills/berth/` |
| `berth skill install --agent claude,cline` | both of the above |
| `berth skill install --all` | every path above |
| `berth skill install --scope user` | the same paths under the user's home directory |
| `berth agents [--json]` | which harness reads which path, and what is installed here |
Worktree adoption is a separate, opt-in step. berth installs only hooks that fire
when a harness creates a worktree; it installs no session-end, interrupt or
per-tool hook, because those sit on a budget or on the critical path and would
cancel a `berth down` rather than complete it.
| Command | Effect |
| --- | --- |
| `berth hook install` (or `cursor`) | appends `berth adopt --setup` to the `setup-worktree*` arrays in `.cursor/worktrees.json`, preserving every command already there |
| `berth hook install windsurf` | appends the same command to `post_setup_worktree` in `.windsurf/hooks.json` |
| `berth hook install --agent claude` | adds a `WorktreeCreate` hook to `.claude/settings.json`. Its command always exits 0 — a non-zero exit aborts Claude's worktree creation — so a failed adoption prints on stderr and its checkout is left to `berth gc` |
Hooks are project-scoped; `berth hook install --scope user` says so and writes
nothing. Every install is repeatable: running it twice leaves the files
byte-identical and reports `already installed`.
A harness may hand you a Git worktree berth does not know about; give it a berth
workspace rather than improvising — `berth adopt --setup` inside it when it is on
a branch, `berth new <slug>` when it is detached or when the task needs its own
ports and data. Junie, Antigravity, Cline and Gemini CLI create worktrees with no
repository-side setup hook, so those worktrees are always registered by hand.
## Cleanup and recovery
`berth done` requires preserved commits (merged, patch-equivalent in the base, or
present upstream) and a clean worktree for berth-owned checkouts. A squash merge
whose resulting tree matches the base counts as preserved; otherwise `--force`.
`--force` cannot bypass ownership, worktree identity, primary-worktree or shutdown
checks. `adopt` is only for linked worktrees, and `berth done` on an adopted
checkout stops and unregisters its runtime while preserving checkout, data and
branch even with `--force`. `down` preserves data; `reset` wipes it deliberately
and only after a verified shutdown. A workspace whose `runtime`/port contract
changed still refuses in-place `up`, but `berth done` and `berth gc` reclaim it.
A supervisor that exits on its own does not wedge a workspace: berth reclaims its
stale control files once the recorded endpoint stops answering and no supervisor
process is alive. Automatic GC never forces, and an unknown process or engine
state means preserve the data and inspect the logs.
Read `references/recovery.md` when a berth command fails: error → meaning →
action, plus what `berth gc` collects and when it refuses.