v0.4.4
Breaking changes
coop push/coop pull/coop exectake the instance name as a
positional argument (#90) — these three commands previously accepted
--name <name>while the other eleven subcommands tookname
positionally. The flag is removed; pass the name positionally instead.
Becausepushandpullalready had[DIR]as a positional, the
directory is now a--dirflag. BecauseexechadCOMMAND...as a
positional, the command must follow--. Examples:
coop push my-vm --dir ./src --force,
coop pull my-vm --dir ./out --force,
coop exec my-vm -- ls -la.
New features
-
coop ca/coop claude-agentsshortcut (#80, #82, #99, #100, #101) —
Runsclaude agentsinside the VM in one command. Defaults to no
tmux (Claude Code manages background-session lifetime itself);
--session <name>opts in. The guest is now bootstrapped with a
managed~/.claude/settings.jsonthat pre-accepts
bypassPermissions, so dispatched sessions no longer prompt for
tool permissions;coop claude --askexplicitly opts back into the
prompting default. -
coop github setup-patwizard (#85, #88) — Walks the user
through creating a fine-grained personal access token scoped to one
repo, stores it in the user's preferred secret store (Keychain,
Secret Service, 1Password, or file), and forwards it to the guest as
GITHUB_TOKENkeyed off the resolved repo slug. Adds a new
github = "pat"config mode. -
coop list/coop ls(#89, #94) — Local-only enumeration of
instance name + state. Reads on-disk metadata andbe.is_running
without SSH probes so it stays fast even when VMs are unreachable.
coop statuskeeps its richer per-instance and resource-usage
output. -
coop uninstall(#93, #96) — Reverses whatinstall.shdoes:
removes the running coop binary and, with confirmation, the data
directory (~/.coop) and XDG update-check state. Flags
--yes/--keep-data/--purge. Refuses to delete
target/{debug,release}/coopand surfaces EPERM with a
sudo coop uninstallhint. Bails on non-TTY stdin without--yes
so CI misuse fails loud. -
Shell completion (#92, #98) —
coop completions <shell>prints
a static completion script for bash, zsh, fish, powershell, and
elvish. Addingsource <(COMPLETE=<shell> coop)to a shell rc
additionally fills in live values via clap_complete's dynamic
engine — instance, image, and profile names are read from~/.coop
on each TAB. -
--git-repoclones authenticate against private GitHub repos
(#78, #119) — On the host, resolve a token in order: a configured
[github.pat."<slug>"]entry for the repo, thengh auth token,
thenGITHUB_TOKEN. Forward it to git in the guest via stdin and
a one-shotcredential.helper. The token never appears on argv,
stays out of/proc/<pid>/cmdlineand the ssh debug log, and is
not persisted in the cloned repo's.git/config. Opportunistic:
GitHub HTTPS URLs only; non-GitHub and SSH-style URLs pass through
untouched. A misconfigured PAT entry fails at start time rather
than silently substituting a broader identity. -
.git/included in workspace transfers by default (#95) —
coop start --workspace,coop push, andcoop pullpreviously
hardcoded.git/into the default exclusion list, breaking
in-guest git history and renderingcheck_guest_dirtya no-op.
Now transferred by default, with an--exclude-gitopt-out on
start/push/pullfor repos large enough that the transfer
cost dominates.check_guest_dirtyalso now detects unpushed
commits (@{u}..HEAD) so in-guest commits aren't silently
destroyed by a host push.
Fixes
-
integration-uninstall.shstate path on macOS (#106) —
dirs::state_dir()returnsNoneon macOS, sostate_path()in
src/update.rsfalls back to~/Library/Application Support/coop/.
The test seeded$XDG_STATE_HOME/coop/update-check.jsonbut the
binary never wrote there, so the--purgeassertion failed. The
test now uses the same platform branching the binary does. -
SIGPIPE flake in bash completion integration check (#105) —
echo "$HARNESS_OUT" | grep -q "coop,$sub"against the ~48 KB
completion script flaked underset -o pipefail: whengrep -q
matched early it closed the pipe, bash'sechobuiltin exited 141
(SIGPIPE), and the pipeline status maskedgrep's success (~60%
of trials on Linux 6.17 / bash 5.2.21). Replaced the pipe with a
here-string. -
destroy --allintegration phase gated behind
COOP_TEST_DESTRUCTIVE=1(#104) —coop destroy --allremoves
every coop-managed instance on the host, not just the ones the
test created. The phase is now skipped by default; remote mode
forwards the opt-in env var explicitly.
Internal
open_ssh_sessionhelper extracted (#81, #83) — The five-line
resolve_running+prepare_env_forwarding+SshSessionsetup
repeated across Claude, ClaudeAgents, Codex, pluscmd_shelland
cmd_exec, collapses to a singleopen_ssh_sessioncall.
SshSessionis now owned rather than borrowing target/env;
removing the lifetime parameter dropsSshSession<'_>from nine
downstream signatures. Incidental behavior change: with
--no-agents, a misconfiguredcmd:secret source no longer
fails the boot path.
Documentation
- Rust authoring + review guidance in CLAUDE.md (#84) —
Project-specific patterns for using the type system to eliminate
error states: parse-don't-validate, smart constructors, type-state
for the VM lifecycle, newtypes that earn their keep, and error
design. Includes prioritized review and authoring checklists.
Dependencies
cargo-bins/cargo-binstall1.18.1 → 1.19.1 (#86)