CHANGELOG
All notable changes to this project will be documented here.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.9.0 - 2026-04-28
Section titled “0.9.0 - 2026-04-28”- Workspace configuration via
[workspace]section in both~/.config/crib/config.tomland.cribrc. Sets environment variables, mounts, and runtime arguments applied to every container. Precedence on conflicts:devcontainer.json>.cribrc[workspace]> global[workspace]. Mounts concatenate across all three layers. Run args are honored in single-container mode only. - Plugin configurability. Bundled plugins can now be disabled globally
(
~/.config/crib/config.tomlunder[plugins]withdisable = ["ssh", ...]ordisable_all = true), per-project (.cribrcwithplugins.disable = ssh, dotfilesorplugins = false), or per-command (--disable-plugin sshoncrib up,crib rebuild,crib restart). Unknown names in the disable list log a warning. Closes #37 (users can now skip the SSH plugin on macOS + Colima where virtiofs breaks agent socket bind mounts). - Go fuzz tests for config parsing (
ParseBytes,ParseMount,SubstituteString) and Dockerfile handling (Parse,RemoveSyntaxVersion,EnsureFinalStageName). Runs in CI with a 10s per-target budget. - Cosign keyless signing for release artifacts.
checksums.txt.sigstore.jsonis now attached to each GitHub release. SECURITY.mdwith responsible disclosure instructions.- pi support in the coding-agents plugin. pi behaves identically to Claude
Code under the shared
credentialsmode: in host mode,~/.pi/agent/auth.jsonis copied into the container when it exists on the host; in workspace mode, a persistent state directory is bind-mounted so credentials created inside the container survive rebuilds.
Changed
Section titled “Changed”.cribrcparser migrated to TOML. The hand-rolled line-by-line parser is replaced with proper TOML parsing viainternal/globalconfig. Both the TOML array syntax (plugins.disable = ["ssh", "dotfiles"]) and the legacy comma-separated string (plugins.disable = "ssh, dotfiles") are accepted for list values, and theplugins = "false"/dotfiles = "false"kill-switch scalars continue to work.- Plugin setup logic moved from
cmd/into a newinternal/pluginsetup/package.cmd/keeps the cobra flag glue; pluginsetup owns the disable-list merging, kill-switch handling, and bundled-plugin wiring. crib up,crib rebuild,crib restart, andcrib statusnow display the actual container name, including user overrides viarunArgs: ["--name", "foo"], instead of always showing the defaultcrib-<ws-id>. Resolves the stale display surfaced by #35. The chosen name is persisted onworkspace.Result.ContainerName; empty for compose backends and workspaces created before this change, with fallback to the default.- Config parsing now rejects
runArgsalongsidedockerComposeFile. Per the devcontainer spec,runArgsapplies to image/Dockerfile configs only; compose workspaces set container runtime options in the compose YAML.
- SSH plugin now verifies that
SSH_AUTH_SOCKpoints at an actual Unix socket before bind-mounting it. Defense-in-depth for environments like macOS + Colima, where virtiofs exposes the socket as a regular file that crashes the container runtime when bind-mounted. When the path is not a socket the plugin logs a warning and skips agent forwarding.
Spec-compliant remoteUser / containerUser resolution
Section titled “Spec-compliant remoteUser / containerUser resolution”crib shell,crib exec, andcrib runnow re-readremoteUserfrom the livedevcontainer.jsonon each invocation. Changes take effect without a rebuild.remoteUserandcontainerUserare now inferred from the image whendevcontainer.jsondoes not set them explicitly. Resolution follows the spec: config wins, thendevcontainer.metadatalabel (last-wins merge), then DockerfileUSER/Config.User. Pre-built images that carry adevcontainer.metadatalabel (e.g.mcr.microsoft.com/devcontainers/javascript-nodesetsremoteUser: "node") now correctly use the label user for feature context and plugin dispatch.containerUserandremoteUserare resolved independently — neither falls back to the other exceptremoteUser→containerUser(one direction only, per spec).- Resume and restart recreate paths preserve a previously-inferred
remoteUserinstead of silently falling back torootwhen image inspection yields no metadata.
Security
Section titled “Security”- GitHub Actions pinned to commit SHAs across all workflows.
contents: writepermission scoped to the GoReleaser job inrelease.yml(was workflow-level).
0.8.0 - 2026-04-07
Section titled “0.8.0 - 2026-04-07”crib stopcommand: non-destructive container stop that preserves hook markers. The nextcrib upresumes with only start-time hooks.- Dotfiles plugin: clones and installs a dotfiles repository inside the
container on creation. Configured via global config (
~/.config/crib/config.toml). Supports custom target path, install command override, and auto-detection of common install scripts. Per-project overrides and opt-out via.cribrc(dotfiles.repository,dotfiles.targetPath,dotfiles.installCommand,dotfiles = false). See #17. - Global config (
~/.config/crib/config.toml, respects$XDG_CONFIG_HOME): user-level settings applied across all workspaces. Currently supports[dotfiles]configuration. PostContainerCreateplugin hook: runs betweenpostCreateCommandandpostStartCommandduring fresh container creation. ProvidesExecandStreamExeccallbacks for running commands inside the container.- Workspace file lock prevents concurrent state-mutating commands from racing.
- Dead code detection (
go tool deadcode) as a CI gate.
Changed
Section titled “Changed”stopis no longer an alias fordown.crib stoppauses the container (preserving state),crib downremoves it.crib.homecontainer label only applied whenCRIB_HOMEis explicitly set.- Compose override generation uses compose-go types instead of string concatenation.
- Compose stop/down reuse the persisted
compose-override.ymlfromcrib up. LoadProjectnow threads caller-supplied environment variables through to compose-go’s loader for${VAR}substitution.
crib down,crib stop, andcrib removenow return a clear error immediately when a compose workspace is detected but compose is not installed, instead of silently falling through to single-container cleanup.- Invalid port numbers in container inspect output are now logged at
WARNlevel instead ofDEBUG, making them visible incrib statusoutput without--debug. - Docs website now deploys automatically on
stablebranch push. - Compose backend captures stderr for diagnostics when container is not found
after
compose up. - Compose override no longer produces duplicate mount destinations when user compose files already define a volume for the same target path.
crib doctor --fixno longer deletes containers belonging to a differentCRIB_HOME.- Integration and e2e tests no longer interfere with active workspaces.
runArgs: ["--name", "..."]indevcontainer.jsonno longer causes a duplicate--nameflag error. The user-specified name now overrides crib’s default container name. See #35.
0.7.1 - 2026-03-25
Section titled “0.7.1 - 2026-03-25”- Feature
containerEnvvalues (e.g.PATH=/nvm/bin:${PATH}from the node feature) were applied twice: correctly asENVinstructions in the Dockerfile (where Docker expands${PATH}at build time), then incorrectly as-eflags at runtime (where${PATH}stays literal viadocker run, or gets interpolated from the host in compose). Either way the runtime PATH diverges from the image’s correctly-expanded PATH, breaking command resolution on macOS. - Dispatch feature-declared lifecycle hooks. Features can declare
onCreateCommand,updateContentCommand,postCreateCommand,postStartCommand, andpostAttachCommandindevcontainer-feature.json. These now execute before user-defined hooks at each stage, in feature installation order (per the spec). Feature hooks are stored inresult.jsonso they persist across restarts without re-resolving features from OCI registries. Also parses the previously-missingupdateContentCommandfield from feature configs. crib restartnow detects changes inside Docker Compose files (volumes, ports, environment, etc.) and recreates the container. Previously, only changes todevcontainer.jsonfields were detected, so editing a compose file’s volumes had no effect until a fullcrib rebuild.
0.7.0 - 2026-03-10
Section titled “0.7.0 - 2026-03-10”crib prunecommand removes stale and orphan workspace images. Shows a dry-run preview with sizes before prompting for confirmation.--allincludes orphan images from workspaces that no longer exist.--force/-fskips the prompt.crib removenow shows a preview of what will be deleted (container ID, images, state directory) and prompts for confirmation. Use--force/-fto skip.- All crib-managed images are now labeled with
crib.workspace={wsID}, enabling label-based discovery without name-pattern heuristics. Applied via--labelondocker buildand--change "LABEL ..."ondocker commit(snapshots). - Build images are automatically removed when a new build replaces them (hash change). The old image is deleted after the new one is successfully built.
crib removenow deletes all labeled images for the workspace in addition to the container and workspace state.
Changed
Section titled “Changed”- Breaking: Workspace IDs now include a 7-character hash of the absolute
project path:
{slug}-{hash}(e.g.my-app-a1b2c3d). Workspaces created before this change will not be recognized. Runcrib remove(if still accessible) or delete~/.crib/workspaces/manually, then runcrib upin each project to create a new workspace with the updated ID.
0.6.3 - 2026-03-09
Section titled “0.6.3 - 2026-03-09”Security
Section titled “Security”- OCI feature archives containing symbolic links are now rejected outright. Previously, crib rejected symlinks whose static target appeared to escape the extraction directory, but a chain of individually-safe symlinks could still compose into a directory escape: an earlier symlink pointing to the extraction root could be overwritten via the chain, redirecting subsequent file writes outside the extraction directory. Feature archives contain scripts and JSON and have no legitimate need for symlinks.
0.6.2 - 2026-03-09
Section titled “0.6.2 - 2026-03-09”crib stopfollowed bycrib upnow resumes from a snapshot when available, skipping the full image build and running only resume-flow lifecycle hooks (postStartCommand, postAttachCommand). Previously, stopping and starting a workspace re-ran the entire creation flow. Both single-container and compose paths are covered.crib rebuildstill forces a full rebuild as before.- SSH agent socket is now mounted at
/run/ssh-agent.sockinstead of/tmp/ssh-agent.sock. Docker-in-Docker features remount/tmpas a fresh tmpfs, which hid the bind-mounted socket and broke SSH agent forwarding in DinD containers.
0.6.1 - 2026-03-08
Section titled “0.6.1 - 2026-03-08”- Compose container lookup fallback now filters by service name. Previously,
when the primary service failed to start, crib could pick up the wrong
container (e.g. postgres instead of rails-app) and show misleading logs in
the error message. The fallback now uses
compose ps --format jsonwith service label matching, which also works on podman-compose (unlikecompose ps -q <service>which podman-compose doesn’t support).
0.6.0 - 2026-03-08
Section titled “0.6.0 - 2026-03-08”crib cache clean --force/-fflag to skip confirmation prompt.- DevContainer Feature entrypoints are now applied. Features that declare an
entrypoint(e.g. docker-in-docker startingdockerd) now have their entrypoint baked into the image. Multiple feature entrypoints are chained via a wrapper script. Feature runtime capabilities (privileged,init,capAdd,securityOpt,mounts,containerEnv) are now applied at container creation time for both single-container and compose paths.
Security
Section titled “Security”- Reject OCI feature archives containing symlinks that escape the extraction directory (absolute targets or relative traversal). Previously, a malicious feature archive could write to arbitrary host paths via symlinks.
- CI workflows now use explicit
permissions: contents: readinstead of GitHub’s default read-write.
Changed
Section titled “Changed”- Breaking: The
-Vshorthand for--verbosehas been removed. Use--verboseinstead. The-vshorthand remains reserved for--version, matching CLI conventions. - CLI now exits with code 2 for usage errors (bad flags, missing arguments) instead of code 1, making it easier to distinguish user mistakes from runtime failures in scripts.
- Noisy host-specific environment variables (
LS_COLORS,DISPLAY,WAYLAND_DISPLAY,XDG_SESSION_*,DBUS_SESSION_BUS_ADDRESS,TERM_PROGRAM,COLORTERM,DESKTOP_SESSION, etc.) are now filtered from the probed environment. These are meaningless inside containers and cluttered the output ofcrib run -- env. Users can still force any filtered variable viaremoteEnvindevcontainer.json.
- SSH
known_hostscould not be written inside the container. The SSH plugin created~/.ssh/as root but only chowned individual files, leaving the directory root-owned. The container user could not create new files in it. - Plugin environment variables (e.g.
BUNDLE_PATH,CARGO_HOME,HISTFILE) now survivecrib restart. Previously, simple restart paths only preserved pluginPathPrependentries but silently dropped pluginEnvvalues. The values survived only because they were present in the stored result from a previouscrib up, but a plugin that changed an env value between restarts would have the old stored value win over the fresh one. crib deletenow removes named volumes declared in compose files (e.g. database data). Previously,docker compose downran without--volumes, leaving orphaned volumes behind.crib restartno longer loses software installed by lifecycle hooks (e.g. mise-managed ruby/node) on compose workspaces. The compose override now references the snapshot image, so even if the container is recreated, the hook-installed state is preserved.- Compose restart paths (
crib restart,crib upon stopped containers) now preserve the storedImageNamein workspace state. Previously, these paths saved an emptyImageName, causing subsequent operations to lose track of the feature image. - Preserve Docker image PATH entries (e.g.
/usr/local/bundle/binin ruby images) that login shells drop during the env probe. Previously,crib execcould lose these entries, requiringbundle execor similar wrappers. - Plugin PATH additions (e.g.
~/.bundle/binfor bundler cache) now work with zsh. Previously relied on/etc/profile.d/scripts which zsh doesn’t source. PATH additions are now injected directly via remoteEnv. crib cache cleanandcrib cache listno longer require workspace state to exist. They now work even ifcrib upwas never run or the project was deleted.crib cache clean --allnow prompts for confirmation since it removes volumes from other projects.- Sensitive env var values (tokens, keys, passwords) are now redacted in
--debugoutput. Previously, values likeGITHUB_TOKENappeared in plaintext in exec command logs. crib restartand redundantcrib upno longer lose probed environment (mise, rbenv, nvm PATH entries) or plugin PATH additions. Previously, restart paths that skip env re-probing overwrote the savedremoteEnv, socrib runcould not find tools likerubyornodeafter a restart.- Plugin dispatch in the already-running container path now passes arguments in the correct order. Previously, the remote user was passed as the image name, causing plugins to see an incorrect image and potentially resolve wrong home directory paths.
0.5.0 - 2026-03-05
Section titled “0.5.0 - 2026-03-05”crib runcommand: runs commands through a login shell so tools installed by version managers (mise, asdf, nvm, rbenv) are available on PATH. Use instead ofcrib execwhen the command depends on shell init files.crib cache listandcrib cache cleancommands for inspecting and removing package cache volumes.--allflag operates across all workspaces.- Package cache volumes are now per-workspace (
crib-cache-{workspace}-{provider}) instead of shared globally. This prevents cross-contamination between projects. crib logscommand with--follow/-fand--tailflags. Shows container logs for single-container workspaces; shows all service logs for compose workspaces.crib doctorcommand to detect and fix workspace health issues. Checks runtime availability, compose availability, orphaned workspaces (source directory deleted), dangling containers (crib label but no workspace state), and stale plugin data. Use--fixto auto-clean.- Package cache sharing plugin: shares host package caches (npm, pip, go,
cargo, maven, gradle, bundler, apt, downloads) via named Docker volumes.
Configure in
.cribrc:cache = npm, pip, go. Thedownloadsprovider is a general-purpose cache directory at~/.cache/crib(exposed viaCRIB_CACHEenv var) for ad hoc file caching. Thebundlerprovider setsBUNDLE_BINand adds~/.bundle/binto PATH via/etc/profile.d/, so gem executables likerspecwork directly incrib shellandcrib run. - Build-time cache mounts: when package cache providers are configured, crib
attaches BuildKit
--mount=type=cachedirectives to DevContainer Feature install steps. This speeds up feature installation across rebuilds by reusing cached packages (especially apt). - Auto-snapshot: after
crib upcompletes create-time hooks, the container is committed to a local snapshot image. On subsequentcrib restartrecreations, the snapshot is used so hook effects are already baked in. If hook definitions change, the snapshot is considered stale and full setup runs instead.crib rebuildalways starts fresh. - Each plugin now emits a progress line (“Running plugin: <name>”) during
crib upandcrib rebuild, visible without any flags.
bundlercache provider: mount volume at~/.bundleinstead of~/.bundle/cacheto avoid permission errors creating~/.bundle/bin(Docker created the parent directory as root).crib cache list: compose workspaces showed the full volume name (including compose project prefix) in the PROVIDER column. Also fixed compose override to declare volumes with explicitname:so new volumes use the expected name.- Installation
curlcommand now works in zsh (URL was missing quotes, causing a parse error with the embeddedsedexpression). Archive filenames no longer include the version, soreleases/latest/download/crib_linux_amd64.tar.gzis a stable URL that always points to the latest release. .envfiles with quoted values (KEY="value with spaces",KEY='value') and inline comments (KEY=value # comment) now parse correctly. Previously only bareKEY=valuesyntax was supported.workspaceMountstrings with a missingtargetfield now produce an explicit error instead of silently creating an unusable mount.- Compose workspaces: stderr noise from
compose upandcompose down(e.g. “No container found”, SIGTERM warnings) is now suppressed in normal mode. Use-V/--verboseto see full compose output. - Compose workspaces:
crib upandcrib rebuildnow detect when the primary container exits immediately aftercompose up(e.g. port conflicts) and report a clear error with container logs, instead of cascading into confusing plugin copy and lifecycle hook failures. - Plugin file copies now bail out on the first exec failure instead of logging identical errors for every remaining copy.
- Compose workspaces:
crib restartnow includes plugin-injected env vars and mounts (package cache, SSH agent, shell history, etc.) in the compose override. Previously the simple restart path skipped plugin dispatch, so these were missing until a fullcrib down && crib up.
0.4.1 - 2026-03-02
Section titled “0.4.1 - 2026-03-02”- Plugins (coding-agents, ssh, shell-history) now run for Docker Compose
workspaces with the correct container user. Previously, plugins only ran for
single-container devcontainers, and the initial fix defaulted to root when
remoteUser/containerUserweren’t set indevcontainer.json, causing permission errors (e.g.zsh: locking failed for /root/.crib_history/.shell_history). The engine now resolves the user from the compose service and image before dispatching plugins. - Compose override files are now written to a system temp directory instead of
inside
.devcontainer/. Previously,chown -Rduring container setup could change ownership of the override file, making it unremovable by the host user.
0.4.0 - 2026-03-01
Section titled “0.4.0 - 2026-03-01”- Plugin system with bundled plugin support for
pre-container-run. Plugins can inject mounts, environment variables, extra run args, and file copies into containers. Fail-open error handling (one broken plugin doesn’t block container creation). Seedocs/plugin-development.md. - coding-agents plugin: automatically injects Claude Code credentials into
containers so AI coding tools work without re-authentication. Detects
~/.claude/.credentials.jsonon the host and copies it into the container. Supports a workspace credentials mode for teams that need org-specific accounts: setcustomizations.crib.coding-agents.credentialsto"workspace"indevcontainer.jsonto persist container-created credentials across rebuilds instead of injecting host credentials. - ssh plugin: shares SSH configuration with containers. Forwards the SSH
agent socket, copies
~/.ssh/configand public keys, and extracts git SSH signing config (gpg.format=ssh) into a minimal.gitconfig. Private keys stay on the host; commit signing works via the forwarded agent (OpenSSH 8.2+). - shell-history plugin: persists bash/zsh history across container
recreations. Bind-mounts a history directory from workspace state and sets
HISTFILE. - Automatic port publishing for single-container workspaces.
forwardPortsandappPortfromdevcontainer.jsonare now translated into--publishflags ondocker run, so ports work without manualrunArgsworkarounds. - Published ports shown on
crib ps,crib up,crib restart, andcrib rebuild. For compose workspaces, ports are parsed fromdocker compose psoutput. - Plugin customizations: plugins now receive
customizations.cribfromdevcontainer.json, enabling per-project plugin configuration. - Documentation website at fgrehm.github.io/crib,
with
llms.txtfor AI tool discovery. -V/--verbosenow prints each lifecycle hook command before running it (e.g.$ npm install), making it easier to diagnose hook failures.build.optionsfromdevcontainer.jsonis now passed todocker build/podman buildas extra CLI flags (e.g.--network=host,--progress=plain).- Object-syntax lifecycle hooks now run their named entries in parallel, matching the devcontainer spec. String and array hooks are unchanged (sequential).
waitForis now respected: a “Container ready.” progress message is emitted after the specified lifecycle stage completes (default:updateContentCommand).
Changed
Section titled “Changed”--debugnow implies--verbose: subprocess stdout (hooks, build, compose) is shown when debug logging is active.crib shellnow rejects arguments with a helpful error suggestingcrib exec.
crib restartno longer fails with “cannot determine image name” on Dockerfile-based workspaces where the stored result had an empty image name. Falls back to rebuilding the image instead of erroring.crib rebuildno longer fails when no container exists (e.g. first build or after manual container removal).- Container deletion is faster (skips stop grace period).
make installnow uses correct permissions.
0.3.1 - 2026-02-28
Section titled “0.3.1 - 2026-02-28”down/stopon rootless Podman no longer fails with “no pod with name or ID … found”. Thex-podman: { in_pod: false }override was only passed duringup, socompose downtried to remove a pod that never existed.rebuildnow actually rebuilds images. Previously it passedRecreate: falsetoUp, which took the stored-result shortcut and skipped the image build.- Environment probe now runs after lifecycle hooks (in addition to before), so
the persisted environment for
shell/execincludes tools installed by hooks (e.g.mise installinbin/setup). - Filter mise internal state variables (
__MISE_*,MISE_SHELL) from the probed environment. These are session-specific and confused mise when injected into a new shell viacrib shell.
0.3.0 - 2026-02-27
Section titled “0.3.0 - 2026-02-27”Changed
Section titled “Changed”- Rename
stoptodown(alias:stop). Now stops and removes the container instead of just stopping it, clearing lifecycle hook markers so the nextupruns all hooks from scratch. - Rename
deletetoremove(aliases:rm,delete). Removes container and workspace state. - Add short aliases:
list(ls),status(ps),shell(sh). rebuildno longer needs to re-save workspace state after removing the container (usesdowninstead of the olddelete).- Display crib version at the start of
up,down,remove,rebuild, andrestartcommands. Dev builds include commit SHA and build timestamp. - Suppress noisy compose stdout (container name listings) during up/down/restart.
Use
--verbose/-Vto see full compose output. status/psnow shows all compose service statuses for compose workspaces.
- Lifecycle hooks (
onCreateCommand,updateContentCommand,postCreateCommand) now run afterdown+upcycle. Previously, host-side hook markers persisted across container removal, causing hooks to be skipped. restartfor compose workspaces now usescompose upinstead ofcompose restart, fixing failures when dependency services (databases, sidecars) were stopped.upafterdownfor compose workspaces no longer rebuilds images. When a stored result exists with a previously built image, the build is skipped and services are started directly.
0.2.0 - 2026-02-26
Section titled “0.2.0 - 2026-02-26”crib restartcommand with smart change detection- No config changes: simple container restart, runs only resume-flow hooks
(
postStartCommand,postAttachCommand) - Safe changes (volumes, mounts, ports, env, runArgs, user): recreates container without rebuilding the image, runs only resume-flow hooks
- Image-affecting changes (image, Dockerfile, features, build args): reports error
and suggests
crib rebuild
- No config changes: simple container restart, runs only resume-flow hooks
(
RestartContainermethod in container driver interfaceRestartmethod in compose helper- Smart Restart section in README
- New project logo
Changed
Section titled “Changed”- Refactor engine package: extract
change.go,restart.gofromengine.go - Deduplicate config parsing (
parseAndSubstitute) and user resolution (resolveRemoteUser)
0.1.0 - 2026-02-25
Section titled “0.1.0 - 2026-02-25”- Core
cribCLI for managing dev containers - Support for Docker and Podman via single OCI driver
.devcontainerconfiguration parsing, variable substitution, and merging- All three configuration scenarios: image-based, Dockerfile-based, Docker Compose-based
- DevContainer Features support with OCI image resolution and ordering
- Workspace state management in
~/.crib/workspaces/ - Implicit workspace resolution from current working directory
- Commands:
up,stop,delete,status,list,exec,shell,rebuild,version - All lifecycle hooks:
initializeCommand,onCreateCommand,updateContentCommand,postCreateCommand,postStartCommand,postAttachCommand userEnvProbesupport for probing user environment (mise, rbenv, nvm, etc.)- Image metadata parsing (
devcontainer.metadatalabel) with spec-compliant merge rules updateRemoteUserUIDwith UID/GID sync and conflict resolution- Auto-injection of
--userns=keep-id/userns_mode: "keep-id"for rootless Podman - Container user auto-detection via
whoamifor compose containers - Early result persistence so
exec/shellwork while lifecycle hooks are still running - Version info on error output for debugging
- Container naming with
crib-{workspace-id}convention - Container labeling with
crib.workspace={id}for discovery - Build and test tooling (Makefile, golangci-lint v2, pre-commit hooks)
- Debug logging via
--debugflag