v0.5.0
Breaking changes
-
coop buildremoved; creation flags moved offcoop start(#269,
#281) —coop startno longer accepts--profile,--git-repo,
--vcpus,--mem,--disk,--mount,--image, or--exclude-git;
those flags now live oncoop up. clap reports them as unexpected
arguments instead of accepting them and bailing at runtime. The
standalonecoop buildcommand and thescripts/fetch-kernel.sh
helper are removed —coop setupandcoop up --profilecover image
creation end-to-end.scripts/build-rootfs.shstays as a documented
manual fallback. Restart still works against existing instances; the
flags above only take effect at create time (viacoop up). -
--git-repomoved fromcoop starttocoop up(#264) —
coop up --git-repo <url>clones a remote repository into
/workspaceand is idempotent across re-runs, matching the rest of
up: an instance already associated with the URL is reused if
running, restarted if stopped, and created otherwise. The instance
name is derived from the repo basename when--nameis not given. -
tmux session wrapping removed (#183, #184) —
coop shell,
coop claude, andcoop codexno longer wrap the remote session in
tmux, and the--session <name>/--no-tmuxflags are gone. The
tmuxpackage is also dropped from the guest base image. Claude
Code'sclaude agentsdaemon (coop claude-agents) already
provides session persistence without a terminal multiplexer; users
who still want detachable terminals can install tmux themselves via
coop setup --extra-packages tmuxand start it manually inside
coop shell. -
Devcontainer auto-discovery prompts on
up/setup --workspace
(#129, #130, #242) — When the workspace contains
.devcontainer/devcontainer.jsonand no escape hatch flag is set,
coop prompts before applying it. Non-TTY callers must pass
--devcontainer <path>,--no-devcontainer, or--dry-run; the
prompt never silently chooses. Precedence is
CLI > devcontainer.json > defaults, and the report column makes
overrides explicit.
New features
-
coop up— project-oriented environment command (#230, #264) —
coop up [DIR]ensures an environment for a project directory
exists and is running, idempotently.DIRdefaults to the current
directory and is canonicalized for affinity; a matching instance is
reconnected if running, restarted if stopped, or created otherwise.
--git-repo <url>uses a remote repository as the workspace instead
of a local directory. Creation-time flags (--vcpus,--mem,
--disk,--image,--profile,--extra-mount,
--devcontainer,--exclude-git) only take effect when the
instance is created and are rejected against existing instances with
acoop destroyhint; runtime flags (--forward-port,
--post-start,--env) take effect on create or restart but are
ignored when reconnecting to a running VM. -
coop quickstart— one-shot setup + start + claude (#74, #213)
— Chainsensure-image → ensure-instance → launch-claude,
short-circuiting any step that's already done. Setup runs only when
thedefaulttemplate image is missing; workspace affinity
reconnects to a running instance for the current directory (or
restarts a stopped one) instead of allocating fresh.--no-workspace
opts out of the cwd mount; sensitive directories ($HOME,/)
prompt default-no on a TTY and bail non-interactively. -
devcontainer.jsonsupport (#129, #130, #131, #134) — A new
translator maps recognised keys to coop's existing primitives:
hostRequirements→--vcpus/--mem/--disk,containerEnv→
guest env,forwardPorts→--forward-port,postStartCommand→
post_start,features→ built-in profiles,mounts→
--mount,remoteUser→--guest-user. New flags on
setup/up:--devcontainer PATH(opt in to a specific file),
--no-devcontainer(opt out entirely), and--dry-run(print the
per-key report and exit before any side effects). JSONC (//,
/* */comments, trailing commas) parses cleanly. CLI--envand
devcontainercontainerEnvare persisted toguest_env.jsonso
coop shell/coop execsee the same values without re-parsing
config. -
OCI devcontainer features (#245) — Supported public
ghcr.io/devcontainers/features/*entries declared in
devcontainer.jsonare resolved atcoop setupand baked into the
image alongside the existing built-in profiles.coop up --profile
continues to compose the built-in profile set. -
coop devcontainersubcommands (#234, #247, #287) —
coop devcontainer check <path>translates adevcontainer.json
and prints the report without running any setup or VM work
(--stage setup|start|both).coop devcontainer ignore <project>
persistently opts a project out of discovery;... statuslists
current opt-outs;... clearremoves one. Symlinked project
prefixes are resolved when clearing, so a directory that was moved
or unlinked doesn't leave a stale opt-out behind. -
coop setup --guest-userandremoteUserhandling (#217, #218)
— Bake a configurable guest username (defaultubuntu, validated
against POSIX rules and notroot) into the image at setup time.
start/shell/execread the value from the image's
template_config.json. Adevcontainer.jsonthat declares a
differentremoteUseris honored when its value matches the
persisted setup user; mismatches surface an actionable diagnostic
instead of silently writing a wrong home path. Backward-compatible
for pre-existing images. -
--forward-port/forward_portsconfig (#125, #128) — Forward
guest TCP ports to the host for the lifetime of the VM.
coop up --forward-port 3000exposes guest 3000 on host 3000;
3000:18080remaps to a different host port. The flag is
repeatable, supported by a config-levelforward_ports = [...]
default, persisted acrossstop/start, and torn down cleanly on
coop stop. Collision with an already-bound host port fails fast
before the VM is created. -
post_starthook (#123, #126) —post_startinconfig.toml
(or--post-start <cmd>oncoop up/coop start) runs a shell
command in the guest after SSH is ready and before any interactive
shell or agent launch. Maps topostStartCommandin
devcontainer.json. Failure is logged at WARN and does not fail
the start, so a transient hook problem can't strand the VM. -
[guest_env]config block +--env KEY=VALUE(#127, #131,
#134) — Set literal env vars inside the guest without forwarding
from the host. CLI overrides win over config and devcontainer
values;--envis persisted toguest_env.jsonso
coop shell/coop execsee the same values without rerunning
start.coop start --envand--devcontainer-derived
containerEnvsurvivestop/startcycles. -
Build profile images on demand from
coop up(#235) —
coop up --profile <list>derives an image name from the sorted
profile list, runs the same stale-image check ascoop setup, and
builds or rebuilds that image if needed. Explicit named images
(--image) are unchanged. -
Submodule discovery in
coop github setup-pat(#219, #221,
#223) — The wizard pre-discovers submodule repositories via the
localghinstall (no network round-trip per submodule) and offers
to set up a PAT for each one. -
Guest PATH set via
/etc/environment(#248, #249) —
~/.local/binis now reliably onPATHfor non-interactive SSH
sessions, includingcoop claudeand Claude Code's Bash-tool
subshells. Previously these sessions skipped~/.profileand
~/.bashrc's PATH export, hidinguv/pipx/user tools and
triggeringclaude doctorwarnings.pam_envreads
/etc/environmentfor every PAM session regardless of shell mode,
so the prepend reaches remote commands, their subshells, and
VS Code remote sessions. -
Docker buildx in guest images (#288) —
docker buildxis
installed in every coop image so multi-arch andbuildkit-driven
builds work without manual setup. -
Secrets redacted from
Debugoutput (#79, #121, #262) — A new
Secret<T>newtype with redactingDebug, transparent serde, and
an explicitexpose()accessor wrapsClaudeConfig.api_key,
CodexConfig.api_key, andPatEntry.token. MCP header secrets
and forwarded env values render as<redacted>in error chains,
tracing events,dbg!, and panics. -
Hint at
--workspace/--mountwhenstartname looks like a
path (#226) — Runningcoop start ./my-projectnow surfaces a
hint to usecoop up(which interpretsDIRcorrectly) instead of
failing with an opaque "instance not found". -
Warn when
--mountsource is a live git repo (#102, #122) —
Live mounts share filesystem state with the host, so in-guest
changes affect the host checkout immediately. coop now warns when
the mount source looks like a working tree.
Fixes
-
Survive mid-session SSH exit 255 in interactive sessions (#224,
#227) — A network blip that produced SSH exit 255 mid-session no
longer kills the wrapping coop process; the session reattaches. -
Interactive SSH escape path hardened (#232) — Closes a regression
in escape-sequence handling on the interactive path. -
workspace.jsonparse errors surfaced (#225) — Three call sites
previously swallowed parse errors; all now report them with context. -
Bare
coop start --mountallocates a fresh instance (#214,
#215) — Earlier this collided with the auto-resolve path and
produced a confusing error. -
coop upworkspace sync no longer drops--extra-mounton
Firecracker (#264) — The workspace-sync block now always syncs
extra mounts; only mount-only instances record the workspace
identity. Also fixes a tar-pipe fallback that extracted to
/workspaceregardless of the mount's guest path. -
coop startdevcontainer lifecycle ordering (#241) — Devcontainer
translation now runs before the SSH-ready probe, so per-key
reporting reflects what will actually be applied. -
Skip redundant
is_running()in stop / stream-logs (#195,
#205) — These were probing the running state twice on the
SSH-readiness path; collapsed to a single probe. -
Direct-probe
resolve_instancefast path for by-name lookups
(#202, #203, #211, #212) — DefersCoopConfig::validateuntil
commands that touch probed paths actually need it, and skips the
generic search when an instance name is supplied. -
Lima SSH-readiness probe via
ssh.configinstead of
limactl list(#201, #210) — Cuts a 0.4–1.5 slimactl
invocation off the resolve fast path on macOS. -
Firecracker CI kernel fallback (#290) — When the latest
Firecracker CI minor has no assets published yet (transient
release-pipeline gap), setup falls back to the previous minor
instead of failing. -
Integration tests — Forward-port test uses a
python3listener
instead ofnc(Firecracker CI rootfs ships no netcat) (#132).
test_list_emptytolerates pre-existing instances on a shared
host (#133).--forward-portcollision test reads$HARNESS_ERR
instead of an outer redirect (#135). Devcontainer opt-out test
assertions match the actual message text (#286). Stop idempotency
test reuses a stopped instance (#240). Barecoop start --mount
pipefail+grep -qrace fixed (#220).
Internal
-
VM lifecycle type-state (#153, #180, #275) —
RunningInstance
andStoppedInstancecarry lifecycle state in the type, eliminating
runtime state checks on operations likeresize_disk,status,
andstop. TheRunningInstanceproof extends across both
backends. -
Newtype / enum survey across module boundaries —
InstanceName(#192, #199),RepoSlug(#149, #171),
ImageName(#157, #181),HostnameandSshUser(#154, #187),
GuestUser(#217, #218),ToolNameandAccountName(#165,
#190),EnvVarName(#151, #177, #196, #206),MemorySpec(#163,
#188),MiB/GiB(#194, #207),Sha256Hash(#161, #189),
Architecture(#169, #173),PortForwardandMount(#179),
HostInterface(#159, #186),SubnetMask(u8)(#150, #176),
InstanceIndex0..=252 bound (#162, #175),TmuxSessionName+
RemoteCommand(#166, #182, later removed with #183),
GuestPathround-trips dropped (#193, #204). Profile names
resolved at the config boundary (#156, #172).
--mount/--env/--forward-portand disk size / devcontainer
path / repo slug parsed by clap value-parsers (#179, #285).
redacted_args: Vec<usize>replaced with typedArgvariants
(#160, #185).McpServerDefreplaced with a transport-tagged
enum (#148, #178).WorkspaceStateoptionals collapsed into
WorkspaceSourcevariants (#170).restart: booland
follow: boolreplaced with two-variant enums (#174).
Correlatedboolfields replaced with enums (#266, #280).
cmd:string reverse-parsing replaced with a structured
CmdToken(#277). Guest-env precedence merge unified (#276).
PAT repo probe typed with aProbeErrorenum (#282). Dead
wrappers removed and over-broad visibility tightened (#283).
Near-identical function pairs extracted into shared helpers
(#284). Newtypes threaded through boundaries instead of decaying
toString(#279). A four-PR survey of smaller newtype / struct
refactors (#191). -
Mutation-testing baseline on
config.rs(#136, #137, #138,
#139, #140, #141, #143, #144, #145, #146) — Adds the
mutation-testing guidance now inCLAUDE.md, pins
CoopConfig::validate,Instance::is_running,
is_firecracker_process, andMiB::as_gib_f64with tests, and
annotates equivalent mutants (fmt::Displayimpls, default-value
getters, serdeVisitormethods) with#[mutants::skip]and a
one-line justification. -
Port-forward integration test covers restart re-establishment
(#260). -
[guest_env]config block integration phase (#263). -
OCI devcontainer feature install integration phase (#261).
Documentation
-
coop up,coop quickstart, and the devcontainer workflow
documented indocs/commands.md,docs/getting-started.md, and
docs/devcontainer.md(#213, #230, #234, #257). -
Guest user, guest PATH, and startup flag pointers (#259).
-
Submodule discovery in setup-pat wizard (#258).
-
Mutation-testing guidance (#136) added to
CLAUDE.md.