You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Meta-issue preserving the full audit findings and improvement ideas from the open-source bootstrap (PR #1). Categorized by priority; each item can be split into its own issue when picked up.
Updated 2026-08-17 after the Devin Review engagement on PR #1 (6 rounds, 20 findings: 11 fixed in the PR, 9 dispositioned as intentional/unreachable — see the PR discussion).
Reorganized 2026-08-17: work items now carry RM-n identifiers used by the delivery pipeline, and closely related items have been merged into single grouped items with staged sub-tasks. Nothing was dropped in the merge — the detail from each original bullet is preserved under its group. Sub-tasks are lettered (RM-5a, RM-5b, …) and are individually shippable; a group is done when all of its sub-tasks are.
Must-fix before first public release (v1.0.0)
All code items originally in this category were fixed in PR #1 (module path, doctor where.exe parsing, duplicate registry sections, reserved shim names, lock repo-mismatch fail-closed, version injection). Status of the rest:
Confirm license choice — Apache-2.0 confirmed by the maintainer (2026-08-17).
Required checks in the ruleset — done: main-protection requires 6 contexts (CI ×3, CodeQL analyze ×2, Dependency review). Devin Review is deliberately NOT a required check: there is no free tier for public GitHub projects, it consumes the maintainer's personal quota, and it may become unavailable — so gating merges on it would be gating on a resource that can vanish. It is fired manually when quota allows, and its findings have been substantive (genuine bugs on PR Open-source bootstrap: audit fixes, OSS scaffolding, docs, CI/security workflows #1 and PR Return an error from mountSpec instead of aborting, and validate dst for commas #9). Revisit if a sustainable free/OSS arrangement appears.
Release dry-run: push a v1.0.0-rc tag on a throwaway branch after merge to verify the release workflow (draft release, checksums, attestation) end to end before the real v1.0.0. The ldflags version-injection mechanism itself is now asserted in CI, but the attestation step has never executed.
Should-fix soon
RM-1 — mountSpec aborts the process on comma-containing paths — done in PR Return an error from mountSpec instead of aborting, and validate dst for commas #9. Chose a returned error over a -v fallback: fail-closed, no second Docker argv shape to maintain. mountSpec now returns (string, error) and all seven call sites propagate it. The change also closed a hole found while scoping: the old check inspected only src, but dst can carry a comma via workspaceRootFor (/workspace/<project-basename>, e.g. a directory named My, Project) and via hand-edited project_volumes/shared_volumes, which parseVolumeBinding does not comma-validate — and at the volume call sites src is a generated volume name that never contains a comma, so a malformed --mount string was reaching Docker and being misparsed. Volume creation now also happens after mount validation, so an invalid binding no longer leaves empty cb.managed volumes behind (that last part from Devin Review). Covered by a portable mountspec_test.go. Correction (RM-6c, PR Document that comma-named project roots cannot work under --mount syntax (RM-6c) #18): the workspaceRootFor-via-volume-call-site reachability claim above does not actually occur in practice — the root bind mount (main.go:1115) uses the sameroot as both src and the input to workspaceRootFor, and its src check fires first, so a comma-named project already fails closed there before the stateful volume loop is ever reached. The dst check's real, still-live value is guarding hand-typed project_volumes/shared_volumes TOML, not the workspaceRootFor case. The fix itself remains correct; only the motivating example was wrong. See docs/windows-paths.md row P15.
RM-2 / RM-3 — Registry write durability. Two independent gaps in the same code path (atomicWriteFile and its callers). Related enough to keep adjacent and to sequence deliberately — they touch the same functions, so doing them concurrently would conflict — but they are separate mechanisms with different risk profiles and should ship as separate PRs.
RM-2 — Concurrent cb registry mutations are uncoordinated. Two simultaneous cb expose/cb update runs can interleave read-modify-write (atomic file replace protects integrity, not lost updates). A simple lock file next to the registry would close it. Concurrency-sensitive; the harder of the two.
RM-3 — atomicWriteFile.bak window: crash between the two renames leaves the registry only at .bak. Startup could auto-recover (if registry missing && .bak present → restore). Self-contained; the cheaper of the two.
RM-4 — Test coverage for mapToolArgs/pathMapper on Windows CI — done in PR Add unit tests for mapToolArgs and pathMapper #8 (pathmap_test.go): 7 cross-platform tests plus 14 Windows-gated ones covering in-root mapping, external mount allocation and dedup, path_next/path_equals/path_last semantics, the dangling-option error, and nearest-existing-ancestor mounting. Confirmed genuinely executing on the windows-latest runner, not just skipping. Broader successor: RM-6b.
RM-5 — Environment diagnostics and support tooling.(Merged from six previously separate items: the cb doctor container-mode check, the registry/lock permission check, cb doctor filesystem/path diagnostics, cb self-test --json, cb self-test --release, and cb bugreport + its environment fingerprinting. They are one coherent capability — turning obscure Docker/path/permission failures into actionable, reportable diagnostics — and were duplicating each other: the path-diagnostics item independently listed the container-mode check, and the fingerprinting item was a sub-part of cb bugreport.) Ordered cheapest-first; each sub-task is shippable alone.
RM-5a — cb doctor verifies Docker Desktop is in Linux-container mode (docker info --format {{.OSType}}). Smallest useful increment and a prerequisite signal for the rest.
RM-5b — cb doctor registry/lock/shim permission check: warn if the shim directory or registry is writable by other users (trust boundary).
RM-5c — cb doctor filesystem/path diagnostics. Detect or warn about path environments known to be unusual or insufficiently tested:
the ContainerBin directory living on a network share;
a project rooted on UNC/network storage;
reparse-point-heavy project roots;
Docker Desktop path-sharing/access failures.
The goal is not to reject everything unusual, but to turn obscure Docker/path failures into actionable diagnostics. Depends on the classification decisions made in RM-6a — a warning should not contradict what the mapper actually supports.
RM-5d — cb self-test --json / machine-readable diagnostics for CI-style consumption on real Windows hosts.
RM-5e — cb self-test --release qualification mode for real Windows hosts. An optional broader release-validation mode recording Windows version; PowerShell version; Docker Desktop / engine version; Docker OSType; filesystem location characteristics; and the results of the existing persistence/path/argv tests. Produces a concise machine-readable summary suitable for attaching to release notes or CI artifacts. Keep normal cb self-test lightweight and offline. Builds on RM-5d's output format.
RM-5f — cb bugreport: assemble sanitized doctor + version + inspect output into one paste-ready block, with secret redaction. Include sanitized compatibility/environment fingerprinting: Windows build; shell/version; Docker version and OSType; ContainerBin version; registry schema version; lock status; and whether the current path is local, UNC, mapped, or reparse-backed. Never include full environment dumps or secrets by default.
RM-6 — Windows path semantics: classification, hardening, and regression corpus.(Merged from the reparse-point/unusual-path-forms hardening item and the path-normalization test corpus item — the corpus is the test vehicle for the hardening, and specifying them separately would mean deciding the semantics twice.) The mapper is intentionally conservative, but Windows has additional filesystem semantics deserving explicit correctness and security coverage. This is the highest-risk area of the project: the failure mode is not a crash but a silently wrong mount, so it warrants unusually dense coverage and careful splitting into small tasks.
RM-6a — Decide and document the classification for each of: junctions and other reparse points inside or below the detected project root; path case-folding / case-insensitive equivalence; UNC paths (\\server\share\...); long-path syntax (\\?\C:\..., \\?\UNC\server\share\...); subst drives and mapped network drives; paths whose target changes between classification and docker run (TOCTOU); and project paths that escape the expected tree through a junction/reparse point. Each must land as supported, rejected, or documented as unsupported — no implicit third state. Preserve the narrow-mount / fail-closed philosophy rather than broadening mounts to make edge cases "just work". Two further inputs carried over from RM-6d: (a) arguments whose final element is ... are now declined outright, which should appear as a row in the classification table; and (b) separator normalization — a declined argument such as .\cmd\... reaches the Linux container with backslashes and the tool reports no match. Normalizing it to ./cmd/... was deliberately not done in RM-6d, because rewriting an argument just classified as not-a-path reintroduces guessing; decide it here, once, for every argument shape rather than as a special case (raised by Devin Review on PR Never map an argument whose final path element is ... #15).
RM-6b — Table-driven path-normalization test corpus, independent of Docker execution: drive-letter absolute paths; forward- and backslash forms; . / ..; spaces; commas; non-ASCII characters; mixed case; trailing dots/spaces where Windows permits or normalizes them; UNC and long-path forms; reparse/junction scenarios where practical. Encodes the RM-6a decisions as executable assertions. Keep the corpus close to internal/pathmap if/when RM-9 lands.
RM-6c — Investigated: comma-named project directories cannot be made to work by sanitizing workspaceRootFor. The original framing assumed the fix was extending workspaceRootFor's existing / → _ collapse to commas. It does not work: mountSpec's src check runs before its dst check, and every tool invocation's root bind mount (main.go:1115) passes the sameroot variable as both src and the input to workspaceRootFor — so workspaceRootFor's output can only contain a comma when root itself already does, and the src check fails closed first, unconditionally, for every provider. No sanitization of the container-side basename changes which branch fires. Docker's --mount syntax has no escape for a comma in a field value, and src must be the real host path, so there is nothing to sanitize on that side without bind-mounting the wrong directory. Done as documentation, PR Document that comma-named project roots cannot work under --mount syntax (RM-6c) #18: classified as Rejected, docs/windows-paths.md row P15, with a corrected/added mountspec_test.go test (TestRootBindMountCommaFailsClosedOnSrc) pinning the real call site — the prior test asserting the dst branch used an artificially clean src that cannot occur together with a comma-laden workspaceRootFor output at any real call site. Genuinely making this work would need a different mechanism (e.g. bind-mounting a Windows 8.3 short-path alias of the real directory where one exists) — a materially larger, riskier change than this item, listed separately below.
RM-6d — Never map an argument whose final path element is ....(Found by the Windows CI runner while adding the Go shims in RM-10, and not Go-specific.)isExplicitWindowsRelPath("./...") is true, so the mapper canonicalizes it — and filepath.Abs on Windows goes through GetFullPathNameW, which strips trailing dots from a path component. C:\proj\... therefore collapses to C:\proj, pathWithin returns ".", and the mapper emits the bare workspace root. The user-visible result is that go test ./... runs only the root package and exits 0 — it under-tests and reports success, the "silently wrong" failure class this project treats as worst. ./cmd/... degrades to that one directory. Any tool given a ./…/... argument is affected; Go is simply the first where the shape is idiomatic. Fix: treat such an argument as not-a-path, which is already correct because the container's working directory is the mapped project directory. There are no false negatives — the mapper only runs on Windows, where a directory named ... cannot exist. Blocks RM-10.Done in PR Never map an argument whose final path element is ... #15. Guarded by hasPackagePatternSuffix, placed as the first statement in resolveWindowsPathArgMode so it precedes even force — a declared path_next/path_equals/path_last value ending in ... is no longer rewritten, and the registry docs record that exception.
RM-7 — Shell/process compatibility: contract and matrix.(Merged from the Windows compatibility matrix item and the shell/process compatibility contract item — the matrix is what you validate the contract against.) PowerShell/cmd behavior is effectively part of ContainerBin's ABI, because argv normalization and executable dispatch depend on Windows shell/process semantics.
RM-7a — Document the contract: which semantics ContainerBin tries to preserve across Windows process launchers — argv delivery; stdin/stdout/stderr; exit codes; TTY detection; current working directory; PowerShell-specific native argument behavior; cmd.exe behavior; and the differences ContainerBin deliberately does not emulate. Makes future regressions identifiable and prevents "fixes" that optimize for one shell while breaking another.
RM-7b — Define and maintain the validation matrix: Windows 11; Windows PowerShell 5.1; PowerShell 7.x; cmd.exe; representative Docker Desktop versions; Linux-containers mode. GitHub-hosted CI should continue covering pure/unit logic and Windows compilation, but should not pretend to provide full Windows + Docker Desktop E2E coverage. Prefer a documented manual release matrix first; a self-hosted Windows runner only if the maintenance cost is justified. Natural consumer of RM-5e.
RM-8 — Structured errors/exit codes: distinguish "tool failed" from "cb infrastructure failed" (e.g. reserve exit code 125+ like Docker does) so scripts can tell them apart. Note that RM-1 moved mountSpec off fatalf onto returned errors, which is a prerequisite shape for this. Done in PR RM-8: reserve exit codes 120 (cb failure) and 130 (interrupted) #13. Reserved 120 = cb infrastructure failure (via fatalf's single choke point) and 130 = interrupted (128 + SIGINT); tool exit codes still pass through untouched, and an unknown subcommand stays 2. 125 was deliberately rejected despite this item's original text: Docker already uses 125/126/127 for daemon-error/not-executable/not-found, so reusing it would recreate the very ambiguity the task exists to remove.
Periodically bump the govulncheck pin in ci.yml (currently @v1.7.0). Dependabot cannot track go install versions inside workflows, so this is a manual chore; results stay fresh regardless (the vulnerability DB is fetched at scan time), but tool-side fixes are not. (From Devin Review round 3.) Recurring chore, not an RM task. Checked 2026-08-18: v1.7.0 is still the latest release (confirmed against both the golang/vuln GitHub tags and the Go module proxy version list) — nothing to bump yet.
Useful enhancements
RM-9 — Decompose main.go (~2,800 lines) into packages: internal/registry, internal/lockfile, internal/pathmap, internal/dockerrun, internal/state, internal/backup, internal/diag, thin main. Mechanical, behavior-preserving, best done as one dedicated PR with no functional changes mixed in. Also the right moment to remove the current = &t aliasing in the registry parser flagged by review. Several other items (RM-6b, test-file consolidation) become easier afterwards, so ordering matters. Done in PR refactor: decompose main.go into internal/ packages (RM-9) #26 (main.go: 3,876 → 249 lines). Final layout differs from the original sketch in ways that avoid real import cycles the sketch didn't have: internal/backup was folded into internal/cli (backup/restore are commands, not a separable data layer), and three packages were added that the original list missed — internal/toml (the TOML lexer shared by both registry and lockfile), internal/atomicio (crash-safe write + .bak recovery, also shared), and internal/mutationlock (split from the signal-handling wrapper, which stays in main to keep exit-code policy in one place). The current = &t aliasing fix landed as its own isolated commit, exactly as scoped. Run through a deliberately heavier-than-usual five-stage review (design-critique-before-implementation, catching import cycles and a version/-ldflags bug before any code moved; implementation with a checkpoint after every step; full-repo-access review; a blind adversarial review; two rounds of Devin Review) given the regression risk of touching every file in the repo at once — full record in .handoff/RM-9/.
RM-10 — Go toolchain shims (go, gofmt). Adds Go to the default registry so go build, go test, go run and gofmt work on a machine with no Go installed. No new provider is needed: unlike Python — whose virtualenv is per-project state living inside the project tree, which is why that provider is hardcoded in runTool — every piece of Go's state lives outside the project (module cache GOMODCACHE=/go/pkg/mod, build cache GOCACHE=/root/.cache/go-build, installed binaries GOBIN=/go/bin), all shared across projects. That is exactly what the declarative stateful provider already expresses, so Go is registry configuration rather than code. node needs a project volume only because node_modules sits inside the project directory; Go needs none. Two user-facing details the task must settle: go build in a Linux container produces a Linux binary (inherent, not a bug — document it, and GOOS=windows is the escape hatch, which is why GOOS is allowlisted), and path-valued Go variables (GOPATH, GOROOT, GOBIN, GOCACHE, GOMODCACHE, GOTMPDIR, GOENV) must not be passed through, since on Windows they hold Windows paths that would silently point Go away from the mounted cache volumes. Done in PR Add Go toolchain shims (go, gofmt) to the default registry #14. Registry configuration only, no runTool changes, as predicted. Two things review changed: neither profile declares any path semantics at all — path_next on go corrupted pass-through argv (go run . -o json handed the user's program a container path) and path_last on gofmt turned the -r rewrite rule into a path. Forced path semantics are positionally blind, so any future profile for a tool with pass-through arguments must weigh the same hazard.
RM-11 — Versioned / per-tool Node runtimes. Support selecting a Node runtime per tool/profile instead of forcing all npm CLIs through the default Node image. Real-world trigger: @whdrnr2583/token-meter exits 1 under the current node:24-slim profile because its native better-sqlite3 11.x dependency does not provide a compatible Node 24 prebuild, while the same package works under node:22-slim. Keep Node-major state groups ABI-safe, preserve image-lock semantics, and define how cb expose npm retains the runtime/state group it originated from. Prefer a small set of supported/LTS runtimes rather than adding every Node release. Done in PR feat: versioned Node runtimes — node22/npm22/npx22, generalized cb expose (RM-11) #30, closes Support versioned / per-tool Node runtimes #28. Added node22/npm22/npx22 as a second, fully-isolated Node runtime (node:22-slim, node22 state group) — no path-mapper or lockfile changes needed, since volume IDs and image locking are already generic over state_group/configured image. Generalized cb expose to accept the name of any npm-shaped stateful profile (not just literally npm) as its positional source argument, so cb expose npm22 <binary> retains the Node 22 runtime identity. Two review rounds (SWE-1.7 Max implement, GLM-5.2 blind verify, Devin Review + Copilot on the PR) found and fixed: a missing lockfile-resolution step in cb expose's discovery path, a missing stateful-provider guard, the npm-global volume matched by logical name instead of mount destination, and a missing test for the core inherited-fields behavior.
RM-12 — Explicit profile-level host mounts. Allow trusted registry profiles to declare narrow host bind mounts with an explicit Windows source, Linux container target, and ro/rw mode. Real-world trigger: Token Meter can run successfully inside node:22-slim, but must read %USERPROFILE%\.claude and %USERPROFILE%\.codex to ingest real Claude/Codex session history; its own persistent state lives under /root/.tokenpulse. Preserve fail-closed validation, narrow mounts, the existing Windows path/reparse-point discipline, and clear trust-boundary diagnostics. Done in PR feat: explicit profile-level host mounts, host_mounts (RM-12) #31, closes Add explicit profile-level host mounts (host_mounts) #29. New provider-agnostic host_mounts registry field (SOURCE:/CONTAINER_PATH:MODE, mode required with no default, only %USERPROFILE% expanded and nothing else, two-stage validation — static structural checks at registry load, environment-dependent resolution at run time through the existing pathmap.CanonicalPath). cwd_mode (the related but orthogonal launcher-CWD problem) was deliberately split out — see the dedicated follow-up below. This PR received unusually deep review (5 Devin Review rounds, 4 GLM adversarial passes) given its trust-boundary nature, and it earned it: found and fixed an orchestrator scoping error (missing project_volumes collision coverage), a severe bug where a path.Clean normalization fix for one finding silently reopened the reserved-namespace check via a ..-traversal target that collapsed to / (caught only by GLM's full-diff re-verify, never by either GitHub-native reviewer), and a second real bug where /venv//root/.cache/pip were reserved by exact match only instead of by prefix like /workspace//cb. Full retrospective in the PR discussion and .handoff/state.md.
RM-13 cwd_mode — explicit isolated/background-launcher CWD semantics. Split out of RM-12 (issue Add explicit profile-level host mounts (host_mounts) #29) because it is implementation-orthogonal: host_mounts is a new registry-declared mount, cwd_mode changes dockerrun.RunTool's project-root-finding logic. Confirmed real during RM-12 scoping: a GUI/MCP host invoking a shim from an arbitrary directory (e.g. C:\Windows\System32) causes RunTool to treat that directory as the project — for a stateful tool, pathmap.WorkspaceRootFor always returns /workspace/<basename> regardless of whether a project marker was found, so npx launched from System32 really does produce /workspace/system32 and a project volume keyed by a hash of that path. Needs an explicit cwd_mode = "isolated" opt-in for background-launcher tools (default "project" unchanged) rather than silently guessing another project root or auto-mounting user data. (From issue Add explicit profile-level host mounts (host_mounts) #29's "Related finding" section.)
RM-14 — AI / automation invocation guidance. Research and document the correct way for AI coding agents and automation launchers to invoke ContainerBin-backed tools when shell CWD is arbitrary or not preserved between tool calls. Real-world failure: an agent verifies D:\Work\project\script.js with Test-Path, then runs node script.js from a different CWD and incorrectly concludes the file/container mapping is broken. Determine the supported instruction mechanisms for major agents (AGENTS.md, CLAUDE.md, Copilot/Codex/Devin equivalents), define one canonical execution contract, and decide whether cb trace/diagnostics should expose CWD/project/path-resolution context more explicitly. Preserve the fail-closed rule: prefer absolute Windows paths or set CWD explicitly; never guess/search for the intended file.
- RM-15 — Runtime defaults / version aliases. Separate stable versioned runtime profiles (node22, node24, future node26; likewise Python/Go/etc.) from unversioned convenience commands (node, npm, python, ...). Add a generic alias/default mechanism so users can inspect and atomically switch the default version for a runtime family (e.g. cb default, cb default set node 22) without duplicating profile configuration. Related tools in one family (node/npm/npx, python/pip) must switch together; versioned shims remain stable. Alias resolution should be visible in cb inspect/cb trace and reuse the target profile's state/image lock rather than creating copied configuration.
Reserved-namespace protection doesn't cover project_volumes/shared_volumes destinations — host_mounts' reserved-namespace check (/workspace, /cb, /venv, /root/.cache/pip, prefix-based) only guards host_mounts targets. A profile could still declare shared_volumes = ["x:/venv"] and shadow the python provider's venv at runtime — the same class of latent breakage the host_mounts fix addresses, just via a different field. Pre-existing (volumes predate host_mounts), not a regression; worth its own pass extending the same pathContainsOrEquals reservation check to ParseVolumeBinding's destination validation. (Raised by GLM-5.2's fourth verify pass on PR feat: explicit profile-level host mounts, host_mounts (RM-12) #31.)
Lock support for locally built images — cb lock fails closed on images without matching RepoDigests. Correct for registry images, but a locally built docker build -t image can never be locked, and once a lockfile exists such a tool becomes unusable. Evaluate locking by image ID, or a per-tool unlocked = true opt-out. (From Devin Review round 3; not a PR Open-source bootstrap: audit fixes, OSS scaffolding, docs, CI/security workflows #1 regression — baseline already errored on digest-less images.)
Reserved-name migration path — reserved tool names are rejected at registry parse time (fail-closed, fine pre-1.0 with no installed base). If a future release ever renders previously legal names reserved, ship the softer design: enforce at mutation/shim-install time, doctor-warn on load, keep removal commands working. (Documented per Devin Review rounds 2–3 discussion.)
Run the Windows CI test step with -v.Test and build (Windows) runs go test ./..., so a skipped test is indistinguishable from a passing one in the log. That matters more here than in most repos: mapToolArgs returns early on non-Windows, so 15+ path-mapper tests exist solely to run on that job, and confirming they actually execute has meant manually digging through job logs since PR Add unit tests for mapToolArgs and pathMapper #8. Two rows of the RM-6a classification table (P1/P2) additionally skip when the runner cannot create a directory symlink, which is invisible today. (Raised by Devin Review on PR Document and test Windows path-form classification (RM-6a) #16; deliberately not folded into a docs PR.)
Version-shaped cb-v dispatch — dispatch (and therefore the name reservation) claims the entire cb-v* prefix for versioned binary names; tightening both in tandem to cb-v<digit> would free names like cb-vault. Low priority: collision space is negligible. (From Devin Review round 6.)
Speculative: bind-mount a Windows 8.3 short-path alias for a comma-named (or otherwise --mount-unrepresentable) project root, instead of the real long path, so such projects could actually work rather than failing closed (see RM-6c / docs/windows-paths.md row P15). Unscoped and not committed to: 8.3 short-name generation is disabled by default on many modern NTFS volumes (a performance optimization), so this would need a capability check and a documented fallback to today's fail-closed behavior, plus verifying EvalSymlinks/case-folding still behave sanely against a short-name path. Materially bigger and riskier than a documentation task.
More providers/integrations: pipx-style Python global tools, Rust/cargo, Ruby/gem, .NET SDK, uv as an alternative Python provider. (Go toolchain carved out as RM-10 above.)
cb expose for go install-ed binaries — go install drops executables into the shared gobin volume (/go/bin), exactly as npm globals land in npm-global. Once RM-10 lands, extending cb expose beyond npm would turn those into shims too. Related to the cb expose beyond npm item below.
cb expose beyond npm: pip/pipx entry points, cargo bin, generic "expose any file from a shared volume".
cb self-test node22 coverage — internal/diag's selfTestSteps only exercises the node/npm/npx (node24) family; RM-11's node22/npm22/npx22 profiles get no offline end-to-end verification. Not a mechanical addition: selfTestSteps currently treats a tool missing from the registry as a hard fail, not a skip — a registry that hasn't yet been upgraded via cb setup/cb install to pick up node22 would start failing self-test the moment steps for it are added. Needs a deliberate policy decision (e.g. distinguish "tool never configured" from "tool added by a newer default set the user hasn't installed yet") before adding node22-image-local/node22-modules-* steps. (Raised by Devin Review on PR feat: versioned Node runtimes — node22/npm22/npx22, generalized cb expose (RM-11) #30; deliberately deferred rather than folded in.)
Startup latency: measure docker run overhead; evaluate --pull=never (should already hold with digests), pre-created networks, and whether a long-lived per-state-group container with docker exec is worth the state-model complexity (probably opt-in only).
Windows ARM64 release target — cross-compiles trivially, but do not claim support until actually validated on ARM64 hardware with Docker Desktop. Natural consumer of RM-5e.
Authenticode signing of released cb.exe (SmartScreen reputation for a tool whose whole job is being on PATH). Needs a cert; sigstore/attestation already covers provenance.
Self-update: cb self-update verifying attestation before replacing the binary; mind the running-exe-replacement dance on Windows.
Proxy/air-gap story: document private registry mirrors in profiles; note that cb lock currently fails closed for mirror-hosted references whose RepoDigests name the canonical repository (deliberate — see PR Open-source bootstrap: audit fixes, OSS scaffolding, docs, CI/security workflows #1 discussion); offline volume export/import (cb backup --state including volume tarballs for disaster recovery).
Observability: CB_DEBUG=1 structured tracing of the exact docker run argv (today cb trace covers pre-flight only); optional log file for support bundles. Feeds RM-5f.
Speculative / future ideas
Linux/macOS hosts: the dispatch/registry/lock core is portable; shims become symlinks, path mapping becomes near-pass-through. Big validation surface; only with real demand.
Policy controls for enterprise: allowed-registry allowlist, mandatory lockfile mode (refuse UNLOCKED), signed registry files.
Image trust: verify Docker Content Trust / cosign signatures at cb lock time and record the verification in the lockfile.
Plugin/provider architecture: external provider executables discovered via naming convention, so new ecosystems don't require forking cb.
WSL2: decide the interoperability model before implementing anything.(Merged from the "WSL2 interoperability model" and "WSL2 integration mode" items — the latter was one candidate direction within the former, and listing both invited implementing before deciding.) The supported direction must be explicit first; the candidates are:
Windows process → Windows cb.exe → Docker Desktop (today's model);
WSL process → Linux-side shim → Docker Desktop integration;
Windows cb.exe targeting a Docker engine exposed from a WSL distro.
These have very different path-mapping rules (C:\x vs /mnt/c/x), filesystem semantics, TTY behavior, and trust boundaries. Do not implement a generic "WSL mode" until the direction and path contract are explicit.
Per-project registry overlays (.container-bin.toml in project root, merged fail-closed) — powerful but expands the trust boundary to repos you clone; needs careful design (explicit trust prompt à la direnv).
Benchmark suite tracking shim overhead across Docker Desktop versions.
SBOM for releases — low value while stdlib-only; revisit if dependencies appear.
Snyk — currently redundant with CodeQL + govulncheck + Dependabot for a zero-dependency Go binary; revisit if the dependency graph grows. Requires external account/token either way.
Conservative path mapping (never guess), allowlist-only env passing, narrow mounts, fail-closed lock/registry validation, dry-run-by-default destructive operations, label-proven ownership before deletion, zero third-party Go dependencies unless clearly justified. Reservation lists must stay in lockstep with the dispatch rules they protect.
Meta-issue preserving the full audit findings and improvement ideas from the open-source bootstrap (PR #1). Categorized by priority; each item can be split into its own issue when picked up.
Updated 2026-08-17 after the Devin Review engagement on PR #1 (6 rounds, 20 findings: 11 fixed in the PR, 9 dispositioned as intentional/unreachable — see the PR discussion).
Reorganized 2026-08-17: work items now carry RM-n identifiers used by the delivery pipeline, and closely related items have been merged into single grouped items with staged sub-tasks. Nothing was dropped in the merge — the detail from each original bullet is preserved under its group. Sub-tasks are lettered (RM-5a, RM-5b, …) and are individually shippable; a group is done when all of its sub-tasks are.
Must-fix before first public release (v1.0.0)
All code items originally in this category were fixed in PR #1 (module path, doctor
where.exeparsing, duplicate registry sections, reserved shim names, lock repo-mismatch fail-closed, version injection). Status of the rest:main-protectionrequires 6 contexts (CI ×3, CodeQL analyze ×2, Dependency review). Devin Review is deliberately NOT a required check: there is no free tier for public GitHub projects, it consumes the maintainer's personal quota, and it may become unavailable — so gating merges on it would be gating on a resource that can vanish. It is fired manually when quota allows, and its findings have been substantive (genuine bugs on PR Open-source bootstrap: audit fixes, OSS scaffolding, docs, CI/security workflows #1 and PR Return an error from mountSpec instead of aborting, and validate dst for commas #9). Revisit if a sustainable free/OSS arrangement appears.v1.0.0-rctag on a throwaway branch after merge to verify the release workflow (draft release, checksums, attestation) end to end before the realv1.0.0. The ldflags version-injection mechanism itself is now asserted in CI, but the attestation step has never executed.Should-fix soon
RM-1 —
mountSpecaborts the process on comma-containing paths — done in PR Return an error from mountSpec instead of aborting, and validate dst for commas #9. Chose a returned error over a-vfallback: fail-closed, no second Docker argv shape to maintain.mountSpecnow returns(string, error)and all seven call sites propagate it. The change also closed a hole found while scoping: the old check inspected onlysrc, butdstcan carry a comma viaworkspaceRootFor(/workspace/<project-basename>, e.g. a directory namedMy, Project) and via hand-editedproject_volumes/shared_volumes, whichparseVolumeBindingdoes not comma-validate — and at the volume call sitessrcis a generated volume name that never contains a comma, so a malformed--mountstring was reaching Docker and being misparsed. Volume creation now also happens after mount validation, so an invalid binding no longer leaves emptycb.managedvolumes behind (that last part from Devin Review). Covered by a portablemountspec_test.go. Correction (RM-6c, PR Document that comma-named project roots cannot work under --mount syntax (RM-6c) #18): theworkspaceRootFor-via-volume-call-site reachability claim above does not actually occur in practice — the root bind mount (main.go:1115) uses the samerootas bothsrcand the input toworkspaceRootFor, and itssrccheck fires first, so a comma-named project already fails closed there before the stateful volume loop is ever reached. Thedstcheck's real, still-live value is guarding hand-typedproject_volumes/shared_volumesTOML, not theworkspaceRootForcase. The fix itself remains correct; only the motivating example was wrong. Seedocs/windows-paths.mdrow P15.RM-2 / RM-3 — Registry write durability. Two independent gaps in the same code path (
atomicWriteFileand its callers). Related enough to keep adjacent and to sequence deliberately — they touch the same functions, so doing them concurrently would conflict — but they are separate mechanisms with different risk profiles and should ship as separate PRs.cbregistry mutations are uncoordinated. Two simultaneouscb expose/cb updateruns can interleave read-modify-write (atomic file replace protects integrity, not lost updates). A simple lock file next to the registry would close it. Concurrency-sensitive; the harder of the two.atomicWriteFile.bakwindow: crash between the two renames leaves the registry only at.bak. Startup could auto-recover (if registry missing && .bak present → restore). Self-contained; the cheaper of the two.RM-4 — Test coverage for
mapToolArgs/pathMapperon Windows CI — done in PR Add unit tests for mapToolArgs and pathMapper #8 (pathmap_test.go): 7 cross-platform tests plus 14 Windows-gated ones covering in-root mapping, external mount allocation and dedup,path_next/path_equals/path_lastsemantics, the dangling-option error, and nearest-existing-ancestor mounting. Confirmed genuinely executing on the windows-latest runner, not just skipping. Broader successor: RM-6b.RM-5 — Environment diagnostics and support tooling. (Merged from six previously separate items: the
cb doctorcontainer-mode check, the registry/lock permission check,cb doctorfilesystem/path diagnostics,cb self-test --json,cb self-test --release, andcb bugreport+ its environment fingerprinting. They are one coherent capability — turning obscure Docker/path/permission failures into actionable, reportable diagnostics — and were duplicating each other: the path-diagnostics item independently listed the container-mode check, and the fingerprinting item was a sub-part ofcb bugreport.) Ordered cheapest-first; each sub-task is shippable alone.RM-5a —
cb doctorverifies Docker Desktop is in Linux-container mode (docker info --format {{.OSType}}). Smallest useful increment and a prerequisite signal for the rest.RM-5b —
cb doctorregistry/lock/shim permission check: warn if the shim directory or registry is writable by other users (trust boundary).RM-5c —
cb doctorfilesystem/path diagnostics. Detect or warn about path environments known to be unusual or insufficiently tested:The goal is not to reject everything unusual, but to turn obscure Docker/path failures into actionable diagnostics. Depends on the classification decisions made in RM-6a — a warning should not contradict what the mapper actually supports.
RM-5d —
cb self-test --json/ machine-readable diagnostics for CI-style consumption on real Windows hosts.RM-5e —
cb self-test --releasequalification mode for real Windows hosts. An optional broader release-validation mode recording Windows version; PowerShell version; Docker Desktop / engine version; DockerOSType; filesystem location characteristics; and the results of the existing persistence/path/argv tests. Produces a concise machine-readable summary suitable for attaching to release notes or CI artifacts. Keep normalcb self-testlightweight and offline. Builds on RM-5d's output format.RM-5f —
cb bugreport: assemble sanitized doctor + version + inspect output into one paste-ready block, with secret redaction. Include sanitized compatibility/environment fingerprinting: Windows build; shell/version; Docker version andOSType; ContainerBin version; registry schema version; lock status; and whether the current path is local, UNC, mapped, or reparse-backed. Never include full environment dumps or secrets by default.RM-6 — Windows path semantics: classification, hardening, and regression corpus. (Merged from the reparse-point/unusual-path-forms hardening item and the path-normalization test corpus item — the corpus is the test vehicle for the hardening, and specifying them separately would mean deciding the semantics twice.) The mapper is intentionally conservative, but Windows has additional filesystem semantics deserving explicit correctness and security coverage. This is the highest-risk area of the project: the failure mode is not a crash but a silently wrong mount, so it warrants unusually dense coverage and careful splitting into small tasks.
\\server\share\...); long-path syntax (\\?\C:\...,\\?\UNC\server\share\...);substdrives and mapped network drives; paths whose target changes between classification anddocker run(TOCTOU); and project paths that escape the expected tree through a junction/reparse point. Each must land as supported, rejected, or documented as unsupported — no implicit third state. Preserve the narrow-mount / fail-closed philosophy rather than broadening mounts to make edge cases "just work". Two further inputs carried over from RM-6d: (a) arguments whose final element is...are now declined outright, which should appear as a row in the classification table; and (b) separator normalization — a declined argument such as.\cmd\...reaches the Linux container with backslashes and the tool reports no match. Normalizing it to./cmd/...was deliberately not done in RM-6d, because rewriting an argument just classified as not-a-path reintroduces guessing; decide it here, once, for every argument shape rather than as a special case (raised by Devin Review on PR Never map an argument whose final path element is ... #15)../..; spaces; commas; non-ASCII characters; mixed case; trailing dots/spaces where Windows permits or normalizes them; UNC and long-path forms; reparse/junction scenarios where practical. Encodes the RM-6a decisions as executable assertions. Keep the corpus close tointernal/pathmapif/when RM-9 lands.workspaceRootFor. The original framing assumed the fix was extendingworkspaceRootFor's existing/→_collapse to commas. It does not work:mountSpec'ssrccheck runs before itsdstcheck, and every tool invocation's root bind mount (main.go:1115) passes the samerootvariable as bothsrcand the input toworkspaceRootFor— soworkspaceRootFor's output can only contain a comma whenrootitself already does, and thesrccheck fails closed first, unconditionally, for every provider. No sanitization of the container-side basename changes which branch fires. Docker's--mountsyntax has no escape for a comma in a field value, andsrcmust be the real host path, so there is nothing to sanitize on that side without bind-mounting the wrong directory. Done as documentation, PR Document that comma-named project roots cannot work under --mount syntax (RM-6c) #18: classified as Rejected,docs/windows-paths.mdrow P15, with a corrected/addedmountspec_test.gotest (TestRootBindMountCommaFailsClosedOnSrc) pinning the real call site — the prior test asserting thedstbranch used an artificially cleansrcthat cannot occur together with a comma-ladenworkspaceRootForoutput at any real call site. Genuinely making this work would need a different mechanism (e.g. bind-mounting a Windows 8.3 short-path alias of the real directory where one exists) — a materially larger, riskier change than this item, listed separately below..... (Found by the Windows CI runner while adding the Go shims in RM-10, and not Go-specific.)isExplicitWindowsRelPath("./...")is true, so the mapper canonicalizes it — andfilepath.Abson Windows goes throughGetFullPathNameW, which strips trailing dots from a path component.C:\proj\...therefore collapses toC:\proj,pathWithinreturns".", and the mapper emits the bare workspace root. The user-visible result is thatgo test ./...runs only the root package and exits 0 — it under-tests and reports success, the "silently wrong" failure class this project treats as worst../cmd/...degrades to that one directory. Any tool given a./…/...argument is affected; Go is simply the first where the shape is idiomatic. Fix: treat such an argument as not-a-path, which is already correct because the container's working directory is the mapped project directory. There are no false negatives — the mapper only runs on Windows, where a directory named...cannot exist. Blocks RM-10. Done in PR Never map an argument whose final path element is ... #15. Guarded byhasPackagePatternSuffix, placed as the first statement inresolveWindowsPathArgModeso it precedes evenforce— a declaredpath_next/path_equals/path_lastvalue ending in...is no longer rewritten, and the registry docs record that exception.RM-7 — Shell/process compatibility: contract and matrix. (Merged from the Windows compatibility matrix item and the shell/process compatibility contract item — the matrix is what you validate the contract against.) PowerShell/cmd behavior is effectively part of ContainerBin's ABI, because argv normalization and executable dispatch depend on Windows shell/process semantics.
cmd.exebehavior; and the differences ContainerBin deliberately does not emulate. Makes future regressions identifiable and prevents "fixes" that optimize for one shell while breaking another.cmd.exe; representative Docker Desktop versions; Linux-containers mode. GitHub-hosted CI should continue covering pure/unit logic and Windows compilation, but should not pretend to provide full Windows + Docker Desktop E2E coverage. Prefer a documented manual release matrix first; a self-hosted Windows runner only if the maintenance cost is justified. Natural consumer of RM-5e.RM-8 — Structured errors/exit codes: distinguish "tool failed" from "cb infrastructure failed" (e.g. reserve exit code 125+ like Docker does) so scripts can tell them apart. Note that RM-1 moved
mountSpecofffatalfonto returned errors, which is a prerequisite shape for this. Done in PR RM-8: reserve exit codes 120 (cb failure) and 130 (interrupted) #13. Reserved 120 = cb infrastructure failure (viafatalf's single choke point) and 130 = interrupted (128 + SIGINT); tool exit codes still pass through untouched, and an unknown subcommand stays 2. 125 was deliberately rejected despite this item's original text: Docker already uses 125/126/127 for daemon-error/not-executable/not-found, so reusing it would recreate the very ambiguity the task exists to remove.Periodically bump the govulncheck pin in
ci.yml(currently@v1.7.0). Dependabot cannot trackgo installversions inside workflows, so this is a manual chore; results stay fresh regardless (the vulnerability DB is fetched at scan time), but tool-side fixes are not. (From Devin Review round 3.) Recurring chore, not an RM task. Checked 2026-08-18:v1.7.0is still the latest release (confirmed against both thegolang/vulnGitHub tags and the Go module proxy version list) — nothing to bump yet.Useful enhancements
main.go(~2,800 lines) into packages:internal/registry,internal/lockfile,internal/pathmap,internal/dockerrun,internal/state,internal/backup,internal/diag, thinmain. Mechanical, behavior-preserving, best done as one dedicated PR with no functional changes mixed in. Also the right moment to remove thecurrent = &taliasing in the registry parser flagged by review. Several other items (RM-6b, test-file consolidation) become easier afterwards, so ordering matters. Done in PR refactor: decompose main.go into internal/ packages (RM-9) #26 (main.go: 3,876 → 249 lines). Final layout differs from the original sketch in ways that avoid real import cycles the sketch didn't have:internal/backupwas folded intointernal/cli(backup/restore are commands, not a separable data layer), and three packages were added that the original list missed —internal/toml(the TOML lexer shared by bothregistryandlockfile),internal/atomicio(crash-safe write +.bakrecovery, also shared), andinternal/mutationlock(split from the signal-handling wrapper, which stays inmainto keep exit-code policy in one place). Thecurrent = &taliasing fix landed as its own isolated commit, exactly as scoped. Run through a deliberately heavier-than-usual five-stage review (design-critique-before-implementation, catching import cycles and a version/-ldflagsbug before any code moved; implementation with a checkpoint after every step; full-repo-access review; a blind adversarial review; two rounds of Devin Review) given the regression risk of touching every file in the repo at once — full record in.handoff/RM-9/.go,gofmt). Adds Go to the default registry sogo build,go test,go runandgofmtwork on a machine with no Go installed. No new provider is needed: unlike Python — whose virtualenv is per-project state living inside the project tree, which is why that provider is hardcoded inrunTool— every piece of Go's state lives outside the project (module cacheGOMODCACHE=/go/pkg/mod, build cacheGOCACHE=/root/.cache/go-build, installed binariesGOBIN=/go/bin), all shared across projects. That is exactly what the declarativestatefulprovider already expresses, so Go is registry configuration rather than code.nodeneeds a project volume only becausenode_modulessits inside the project directory; Go needs none. Two user-facing details the task must settle:go buildin a Linux container produces a Linux binary (inherent, not a bug — document it, andGOOS=windowsis the escape hatch, which is whyGOOSis allowlisted), and path-valued Go variables (GOPATH,GOROOT,GOBIN,GOCACHE,GOMODCACHE,GOTMPDIR,GOENV) must not be passed through, since on Windows they hold Windows paths that would silently point Go away from the mounted cache volumes. Done in PR Add Go toolchain shims (go, gofmt) to the default registry #14. Registry configuration only, norunToolchanges, as predicted. Two things review changed: neither profile declares any path semantics at all —path_nextongocorrupted pass-through argv (go run . -o jsonhanded the user's program a container path) andpath_lastongofmtturned the-rrewrite rule into a path. Forced path semantics are positionally blind, so any future profile for a tool with pass-through arguments must weigh the same hazard.@whdrnr2583/token-meterexits1under the currentnode:24-slimprofile because its nativebetter-sqlite311.x dependency does not provide a compatible Node 24 prebuild, while the same package works undernode:22-slim. Keep Node-major state groups ABI-safe, preserve image-lock semantics, and define howcb expose npmretains the runtime/state group it originated from. Prefer a small set of supported/LTS runtimes rather than adding every Node release. Done in PR feat: versioned Node runtimes — node22/npm22/npx22, generalized cb expose (RM-11) #30, closes Support versioned / per-tool Node runtimes #28. Addednode22/npm22/npx22as a second, fully-isolated Node runtime (node:22-slim,node22state group) — no path-mapper or lockfile changes needed, since volume IDs and image locking are already generic overstate_group/configured image. Generalizedcb exposeto accept the name of any npm-shaped stateful profile (not just literallynpm) as its positional source argument, socb expose npm22 <binary>retains the Node 22 runtime identity. Two review rounds (SWE-1.7 Max implement, GLM-5.2 blind verify, Devin Review + Copilot on the PR) found and fixed: a missing lockfile-resolution step incb expose's discovery path, a missing stateful-provider guard, the npm-global volume matched by logical name instead of mount destination, and a missing test for the core inherited-fields behavior.ro/rwmode. Real-world trigger: Token Meter can run successfully insidenode:22-slim, but must read%USERPROFILE%\.claudeand%USERPROFILE%\.codexto ingest real Claude/Codex session history; its own persistent state lives under/root/.tokenpulse. Preserve fail-closed validation, narrow mounts, the existing Windows path/reparse-point discipline, and clear trust-boundary diagnostics. Done in PR feat: explicit profile-level host mounts, host_mounts (RM-12) #31, closes Add explicit profile-level host mounts (host_mounts) #29. New provider-agnostichost_mountsregistry field (SOURCE:/CONTAINER_PATH:MODE, mode required with no default, only%USERPROFILE%expanded and nothing else, two-stage validation — static structural checks at registry load, environment-dependent resolution at run time through the existingpathmap.CanonicalPath).cwd_mode(the related but orthogonal launcher-CWD problem) was deliberately split out — see the dedicated follow-up below. This PR received unusually deep review (5 Devin Review rounds, 4 GLM adversarial passes) given its trust-boundary nature, and it earned it: found and fixed an orchestrator scoping error (missingproject_volumescollision coverage), a severe bug where apath.Cleannormalization fix for one finding silently reopened the reserved-namespace check via a..-traversal target that collapsed to/(caught only by GLM's full-diff re-verify, never by either GitHub-native reviewer), and a second real bug where/venv//root/.cache/pipwere reserved by exact match only instead of by prefix like/workspace//cb. Full retrospective in the PR discussion and.handoff/state.md.cwd_mode— explicit isolated/background-launcher CWD semantics. Split out of RM-12 (issue Add explicit profile-level host mounts (host_mounts) #29) because it is implementation-orthogonal:host_mountsis a new registry-declared mount,cwd_modechangesdockerrun.RunTool's project-root-finding logic. Confirmed real during RM-12 scoping: a GUI/MCP host invoking a shim from an arbitrary directory (e.g.C:\Windows\System32) causesRunToolto treat that directory as the project — for astatefultool,pathmap.WorkspaceRootForalways returns/workspace/<basename>regardless of whether a project marker was found, sonpxlaunched fromSystem32really does produce/workspace/system32and a project volume keyed by a hash of that path. Needs an explicitcwd_mode = "isolated"opt-in for background-launcher tools (default"project"unchanged) rather than silently guessing another project root or auto-mounting user data. (From issue Add explicit profile-level host mounts (host_mounts) #29's "Related finding" section.)D:\Work\project\script.jswithTest-Path, then runsnode script.jsfrom a different CWD and incorrectly concludes the file/container mapping is broken. Determine the supported instruction mechanisms for major agents (AGENTS.md,CLAUDE.md, Copilot/Codex/Devin equivalents), define one canonical execution contract, and decide whethercb trace/diagnostics should expose CWD/project/path-resolution context more explicitly. Preserve the fail-closed rule: prefer absolute Windows paths or set CWD explicitly; never guess/search for the intended file.node22,node24, futurenode26; likewise Python/Go/etc.) from unversioned convenience commands (node,npm,python, ...). Add a generic alias/default mechanism so users can inspect and atomically switch the default version for a runtime family (e.g.cb default,cb default set node 22) without duplicating profile configuration. Related tools in one family (node/npm/npx,python/pip) must switch together; versioned shims remain stable. Alias resolution should be visible incb inspect/cb traceand reuse the target profile's state/image lock rather than creating copied configuration.project_volumes/shared_volumesdestinations —host_mounts' reserved-namespace check (/workspace,/cb,/venv,/root/.cache/pip, prefix-based) only guardshost_mountstargets. A profile could still declareshared_volumes = ["x:/venv"]and shadow the python provider's venv at runtime — the same class of latent breakage thehost_mountsfix addresses, just via a different field. Pre-existing (volumes predatehost_mounts), not a regression; worth its own pass extending the samepathContainsOrEqualsreservation check toParseVolumeBinding's destination validation. (Raised by GLM-5.2's fourth verify pass on PR feat: explicit profile-level host mounts, host_mounts (RM-12) #31.)cb lockfails closed on images without matching RepoDigests. Correct for registry images, but a locally builtdocker build -timage can never be locked, and once a lockfile exists such a tool becomes unusable. Evaluate locking by image ID, or a per-toolunlocked = trueopt-out. (From Devin Review round 3; not a PR Open-source bootstrap: audit fixes, OSS scaffolding, docs, CI/security workflows #1 regression — baseline already errored on digest-less images.)-v.Test and build (Windows)runsgo test ./..., so a skipped test is indistinguishable from a passing one in the log. That matters more here than in most repos:mapToolArgsreturns early on non-Windows, so 15+ path-mapper tests exist solely to run on that job, and confirming they actually execute has meant manually digging through job logs since PR Add unit tests for mapToolArgs and pathMapper #8. Two rows of the RM-6a classification table (P1/P2) additionally skip when the runner cannot create a directory symlink, which is invisible today. (Raised by Devin Review on PR Document and test Windows path-form classification (RM-6a) #16; deliberately not folded into a docs PR.)cb-vdispatch — dispatch (and therefore the name reservation) claims the entirecb-v*prefix for versioned binary names; tightening both in tandem tocb-v<digit>would free names likecb-vault. Low priority: collision space is negligible. (From Devin Review round 6.)cb add TOOL --image IMAGE: guided profile creation instead of hand-editing TOML.--mount-unrepresentable) project root, instead of the real long path, so such projects could actually work rather than failing closed (see RM-6c /docs/windows-paths.mdrow P15). Unscoped and not committed to: 8.3 short-name generation is disabled by default on many modern NTFS volumes (a performance optimization), so this would need a capability check and a documented fallback to today's fail-closed behavior, plus verifyingEvalSymlinks/case-folding still behave sanely against a short-name path. Materially bigger and riskier than a documentation task.uvas an alternative Python provider. (Go toolchain carved out as RM-10 above.)cb exposeforgo install-ed binaries —go installdrops executables into the sharedgobinvolume (/go/bin), exactly as npm globals land innpm-global. Once RM-10 lands, extendingcb exposebeyond npm would turn those into shims too. Related to thecb exposebeyond npm item below.cb exposebeyond npm: pip/pipx entry points, cargo bin, generic "expose any file from a shared volume".cb self-testnode22 coverage —internal/diag'sselfTestStepsonly exercises thenode/npm/npx(node24) family; RM-11'snode22/npm22/npx22profiles get no offline end-to-end verification. Not a mechanical addition:selfTestStepscurrently treats a tool missing from the registry as a hardfail, not askip— a registry that hasn't yet been upgraded viacb setup/cb installto pick upnode22would start failing self-test the moment steps for it are added. Needs a deliberate policy decision (e.g. distinguish "tool never configured" from "tool added by a newer default set the user hasn't installed yet") before addingnode22-image-local/node22-modules-*steps. (Raised by Devin Review on PR feat: versioned Node runtimes — node22/npm22/npx22, generalized cb expose (RM-11) #30; deliberately deferred rather than folded in.)docker runoverhead; evaluate--pull=never(should already hold with digests), pre-created networks, and whether a long-lived per-state-group container withdocker execis worth the state-model complexity (probably opt-in only).cb.exe(SmartScreen reputation for a tool whose whole job is being on PATH). Needs a cert; sigstore/attestation already covers provenance.cb self-updateverifying attestation before replacing the binary; mind the running-exe-replacement dance on Windows.cb lockcurrently fails closed for mirror-hosted references whose RepoDigests name the canonical repository (deliberate — see PR Open-source bootstrap: audit fixes, OSS scaffolding, docs, CI/security workflows #1 discussion); offline volume export/import (cb backup --stateincluding volume tarballs for disaster recovery).CB_DEBUG=1structured tracing of the exactdocker runargv (todaycb tracecovers pre-flight only); optional log file for support bundles. Feeds RM-5f.Speculative / future ideas
Linux/macOS hosts: the dispatch/registry/lock core is portable; shims become symlinks, path mapping becomes near-pass-through. Big validation surface; only with real demand.
Policy controls for enterprise: allowed-registry allowlist, mandatory lockfile mode (refuse UNLOCKED), signed registry files.
Image trust: verify Docker Content Trust / cosign signatures at
cb locktime and record the verification in the lockfile.Plugin/provider architecture: external provider executables discovered via naming convention, so new ecosystems don't require forking cb.
WSL2: decide the interoperability model before implementing anything. (Merged from the "WSL2 interoperability model" and "WSL2 integration mode" items — the latter was one candidate direction within the former, and listing both invited implementing before deciding.) The supported direction must be explicit first; the candidates are:
cb.exe→ Docker Desktop (today's model);cb.exetargeting a Docker engine exposed from a WSL distro.These have very different path-mapping rules (
C:\xvs/mnt/c/x), filesystem semantics, TTY behavior, and trust boundaries. Do not implement a generic "WSL mode" until the direction and path contract are explicit.Per-project registry overlays (
.container-bin.tomlin project root, merged fail-closed) — powerful but expands the trust boundary to repos you clone; needs careful design (explicit trust prompt à la direnv).Benchmark suite tracking shim overhead across Docker Desktop versions.
SBOM for releases — low value while stdlib-only; revisit if dependencies appear.
Snyk — currently redundant with CodeQL + govulncheck + Dependabot for a zero-dependency Go binary; revisit if the dependency graph grows. Requires external account/token either way.
Design principles to preserve (from PR #1 review)
Conservative path mapping (never guess), allowlist-only env passing, narrow mounts, fail-closed lock/registry validation, dry-run-by-default destructive operations, label-proven ownership before deletion, zero third-party Go dependencies unless clearly justified. Reservation lists must stay in lockstep with the dispatch rules they protect.