Skip to content

Latest commit

 

History

History
1600 lines (1222 loc) · 141 KB

File metadata and controls

1600 lines (1222 loc) · 141 KB

Changelog

All notable changes to Construct CLI will be documented in this file.

[1.16.4] - 2026-08-27

Fixed

  • microVM: construct now matches msb 0.6.15 (schema, refs, workdir): construct embeds the microsandbox engine through the Go SDK, pinned at v0.6.10, while the host msb CLI could be newer. A newer CLI migrates ~/.microsandbox/db forward and the embedded engine then fails every daemon create with database schema is newer than this msb binary (hit live on a Linux host running msb 0.6.15). The SDK pin is now v0.6.15; when upgrading the host msb, upgrade construct in the same window.
  • No more phantom 3.5GB image transitions: msb image tag does not exist in msb 0.6.15 (and the pull+tag flow was dead code), so a successful GHCR pull always "failed" its tag step and construct fell through to the docker save + load transition. msb load -i also imports archives under the localhost/ prefix, which the bare-ref probe never matched, so every run re-transitioned the archive. Image resolution now probes candidates in order (bare, ghcr.io/estebanforge/construct-box:latest, localhost/construct-box:latest) for EnsureImage, the daemon run spec, the prepull, and ct sys doctor; the pull path verifies the registry ref and the load path verifies the localhost ref, with no tag step at all. Known follow-up: the docker-archive transition still produces an incomplete rootfs under 0.6.15 (guest has no /bin/sh); the registry pull is the reliable path and is preferred.
  • Daemon create no longer dies on image WORKDIR validation: msb 0.6.15 validates the image working directory at create time and construct-box declares WORKDIR /projects, which the transitioned archive fails. Sandbox create now sets an explicit workdir (/home/construct, present in every construct-box image); exec paths set their own cwd, so this is only the default.
  • Boot telemetry is now collectable: msb-boot: lines printed to stderr only, so the dogfood P0 greps over logs/*.log matched nothing even on a healthy install. msbLogBoot now also appends an RFC3339-stamped line to ~/.config/construct-cli/logs/msb-boot.log; the format test isolates HOME so fixture lines cannot pollute the real log.

Docs

  • docs/VMsv2.md section 10: preliminary single-run observations recorded (macOS recreate 490s with the skills-drift recreate firing correctly, reconnect 0s on both hosts) plus the collection caveat that the log-file telemetry ships with this release.

[1.16.3] - 2026-08-26

Added

  • Host skills mount: bind the host's skills library into every supported agent's skills dir: ~/Dev/EstebanForge/AGENTS/skills (or any other configured source) is now bind-mounted at create time into /home/construct/<agent>/skills for ten supported agents (agy, claude, amp, qwen, copilot, crush, droid, goose, kilocode, cline). Source resolution precedence: $CONSTRUCT_SKILLS_SOURCE env var, then [sandbox] skills_source, then auto-detect (~/Dev/EstebanForge/AGENTS/skills, ~/AGENTS/skills, ~/.config/construct-cli/skills, $XDG_DATA_HOME/construct/skills). Mount is read-only by default; opt into read-write via [sandbox] skills_read_only = false when an agent must author skills. Docker compose uses :ro (or :ro,z on Linux SELinux); microVM msb.Mount.Bind honors the same flag. Fails closed (no mount, no error) when the source does not resolve. Managed entirely by construct-cli; manage.sh no longer needs to copy skills into the sandbox home.
  • Daemon recreate parity for skills mounts: a new label construct.daemon.skills_hash (hash of source + read-only flag + target count) is stamped by BuildMsbRunSpec whenever skills are enabled, and msbDaemonNeedsRecreate checks it FIRST in both multi-path and single-path modes. Toggling skills, flipping RO/RW, source appearance, or supported-agent-list growth now recreate the running daemon so the new mounts take effect (previously the change was invisible until manual recreate). P2.2 wire-in (combined mount-set resolution in msbSandboxMounts + MsbPathMaps) is still pending; the hash surface is ready for it.
  • Boot telemetry (msb-boot: log line): EnsureMsbDaemon now emits a stable msb-boot: line at every return path with the outcome (cold | recreate | warm | reconnect), elapsed seconds, mount count, and (for recreate) the reason. msbBootClock is injectable so tests run deterministic without sleeping. Output goes to stderr (run-path rule respected). Numbers land in docs/VMsv2.md section 10 once the dogfood week collects medians; P6 (snapshot fork) is gated on those numbers.
  • Widened daemon flock (phase 1): a blocking syscall.Flock on ~/.config/construct-cli/daemon.lock (mode 0600) is acquired at the top of EnsureMsbDaemon via defer releaseLock(). The critical section now wraps read state, decide, write state, and the recreate/boot itself so concurrent ct invocations learning different roots cannot produce last-write-wins root loss or a double recreate. A 250ms "waiting for another construct invocation" notice fires on slow acquires. ct sys doctor surfaces daemon lock: free/held (per-request probe, intentionally not transactional with the actual lock).
  • Learned roots store (phase 2 data layer): roots.json is a versioned, atomic-write JSON store at ~/.config/construct-cli/roots.json with LRU eviction ([daemon] max_learned_roots, default 8). requestLearnRoot enforces the workspace guard, prompts via gum when interactive, denies with an actionable message when non-interactive. The full EnsureMsbDaemon wire-in (P2.2) is pending; the helper is shipped with nolint:unused and unit tests covering the no-op paths.
  • CLI surface for learned roots: construct sys daemon roots list (table of root, source, last used, mount dest) and construct sys daemon roots forget <path> (refuses configured daemon.mount_paths entries). Pinned paths and forgotten paths are shown side by side; the manage.sh symlink/copy flow for skills stays in place as a fallback for non-construct run paths.
  • Idle stop (phase 3): nothing runs while the user is away. A session registry at ~/.config/construct-cli/sessions/<pid>.json (one file per live ct) is read+written only inside the daemon flock critical section. execViaMsbDaemon registers the current PID; Teardown unregisters and, on the LAST unregister, spawns a detached construct sys daemon idle-watch process (true daemon via Setsid, stdin/stdout/stderr closed, Process.Release). The watcher sleeps [daemon] idle_stop_minutes (default 45; 0 disables), rechecks the registry every 30s, and stops the daemon only if the count is still zero at the deadline. A new ct invocation that registers a session during the sleep stands the watcher down. The watcher acquires the flock before stopping (round 8 fix) so a fresh EnsureMsbDaemon cannot be torn down mid-flight, and re-checks the live count under the lock. Bridges (SSH proxy, clipboard, host exec, herdr) die with the daemon, so idle = smaller host attack surface.
  • Background image prepull (phase 4): after a successful construct update, a detached msb pull + msb image tag of construct-box:latest is spawned so the next ct finds the image already staged. Opt-out via [runtime] prepull_image = false. Output goes to ~/.config/construct-cli/logs/prepull.log. The detached child uses os.Executable() to re-exec the freshly installed binary (verified: installBinaryWithBackup finishes its atomic rename before the prepull spawn at line 385, so the ordering invariant is correct). The prepull logs a warning when [runtime] prepull_image = true but [runtime] backend is not "microvm" (the default "auto" falls through to OCI runtime and never resolves to microvm, so the silent skip is now visible).
  • Credential proxy design (phase 5, design only): docs/CREDS-PROXY.md covers the full design: threat model, component diagram, CA lifecycle (host-generated, 1y validity, embedded in image trust store), per-provider rule format with strip_request_auth flag, keychain-backed token store (macOS Security / Linux Secret Service / permissions-protected file fallback), four-phase rollout plan with runtime.credential_proxy flag, network-mode interaction. Implementation is deferred to a follow-up; P5.2 (peer review) is the next step.

Fixed

  • Daemon flock: no more false "Waiting for another construct invocation" notice on every long boot: the 250ms notice goroutine was disarmed only at release() time instead of at acquire time, so ANY lock hold longer than 250ms — which is every real daemon boot — armed the timer for the full duration and printed the waiting notice 250ms in, uncontended or not. The notice now measures the ACQUISITION wait only (disarm via a dedicated sync.Once fired on acquire success or error), and the goroutine captures the stderr writer before spawn instead of reading the os.Stderr global late, which also fixes the go test -race data race against tests that swap os.Stderr.
  • Doctor: no more duplicate "Container Runtime" line and wrong "Daemon Mode" wording under microvm: the runtime check was appended twice when backend = "microvm" (once inside the msb branch, once at the end of the shared chain), so ct sys doctor printed "Not applicable (runtime backend = microvm)" on two consecutive lines. The Daemon Mode check then showed "Unavailable (config/runtime missing)" — misleading on microvm, where runtimeName is deliberately blanked: the check now reports "Not applicable (runtime backend = microvm)" and points at construct sys daemon status for the real microVM daemon state.
  • Doctor: stale SSH agent sockets no longer counted as "local keys": the keys check listed every file in the mounted construct home .ssh except a fixed non-key set, so per-boot agent.<pid>.sock proxy sockets accumulated there (60 on the dogfood host) and inflated the "Found N local keys" line. The filter is now sshKeyNames, which skips directories, .pub counterparts, known non-key files, and any *.sock (socket cleanup at the source is a tracked follow-up).
  • MicroVM daemon: skills toggle no longer silently misses the new mounts: a Sonnet peer review caught that msbDaemonNeedsRecreate did not consider skills mounts, so toggling skills on a running daemon left the change invisible until manual recreate. Fix: new construct.daemon.skills_hash label (see Added above).
  • Idle-watcher no longer races concurrent watchers or fresh EnsureMsbDaemon: a Sonnet peer review caught that StopMsbDaemonBestEffort did not acquire the daemon flock, so two concurrent watchers could each independently decide "count==0" and race to stop the same daemon, AND a fresh ct invocation's EnsureMsbDaemon could be torn down mid-flight. Fix: StopMsbDaemonBestEffort now acquires the flock, re-checks LiveSessionCount() under the lock, then stops. A session that registered between the watcher's last tick and the stop is honored.

Docs

  • docs/DOGFOODING-1.16.3.md P4.3 procedure corrected: the prepull check told the user to run ct sys update, which is agent-update-inside-container and fails closed on backend = "microvm" by design (exactly what the dogfood run hit). The deterministic path is ct sys prepull (foreground pull loop, same log). Also documents that ct sys self-update only fires the prepull after an actual update — the "already on latest version" no-op returns before the spawn — and that msb image rm matches by exact reference, not substring.
  • docs/VMsv2.md round 8 review provenance entry: records the Sonnet review follow-up (the two must-fix items + the nits we did not act on) so the next reviewer sees the trail.
  • docs/VMsv2.md P3.6 partial marker: the config template and ct sys daemon status session-count line are done; the full docs/CONFIGURATION.md write is still pending (defer to a docs sweep).
  • docs/VMsv2.md round 7 entry: the requestLearnRoot return-value split Sonnet originally flagged was traced to dead code (cleanProjectDir already filters system roots upstream), reverted, and documented so a future maintainer does not "fix" away the no-op ResolveDaemonMountsWithLearned wrapper.

[1.16.2] - 2026-08-25

Fixed

  • MicroVM daemon: switching projects no longer recreates the sandbox: under backend = "microvm", moving to a different project root destroyed and recreated the daemon microVM, wiping guest state (brew and apt installs) and re-running the multi-minute first boot on every switch. The Docker backend's multi-root daemon mounts are now ported to the microVM path: with [daemon] multi_paths_enabled = true, every mount_paths root is mounted under /workspaces/<hash> (full-path hash, so same-named repos cannot collide) and the daemon is reused for any directory inside the set; the sandbox is recreated only when the configured mount set itself changes (microsandbox mounts are create-time only, the SDK has no hot-add). A directory outside every configured root stops with an actionable error instead of a destructive recreate. In default single-path mode, subdirectories of the mounted root now reuse the daemon (previously even a subdirectory of the same project triggered a full recreate), and stale workspace labels are detected before reuse. Workdir mapping resolves symlinks on both sides, so paths reached through macOS /tmp vs /private/tmp or symlinked checkouts no longer spuriously count as outside the mount.

Changed

  • [runtime] engine merged into backend: the two config keys encoded one decision (engine picked the OCI binary, backend only switched container vs microVM, and engine was ignored under microvm), and the literal value docker meant different things on each key. The single backend key now takes auto (default; detects container > podman > docker), container, podman, docker, or microvm; pinning a binary sets the priority order and detection still falls through when the pin is unavailable. Existing configs migrate automatically on first run with an in-place, comment-preserving rewrite: a pinned engine = "podman" becomes backend = "podman" on the same line, engine = "auto" is dropped, and an explicit backend pin always wins over a leftover engine value; if the file cannot be rewritten, the same rules apply in memory for that session and the legacy value can never round-trip back through Save(). The migration write is atomic (temp file plus rename), so a crash mid-write cannot truncate config.toml. One nuance: an old explicit backend = "docker" with no engine now pins the Docker binary instead of auto-detecting; set backend = "auto" to restore detection (documented in docs/CONFIGURATION.md).

Docs

  • MicroVM users are pointed at the daemon setup: README, docs/CONFIGURATION.md, docs/ARCHITECTURE-DESIGN.md, and the config template now state the preferred microVM workflow: enable [daemon] multi_paths_enabled with mount_paths covering your project roots so the daemon sandbox is created once and reused across projects, instead of being recreated (with a full guest re-initialization) on every directory switch.

[1.16.1] - 2026-08-24

Fixed

  • Rootless podman: agents crashed with EACCES: permission denied writing under /home/construct: ct pi update --all and similar commands exec into the daemon as the construct user (uid 1000), but rootless podman's default user namespace maps container uid 1000 to a host subuid (e.g. 100999) while container root maps to the real host user. The /home/construct bind mount is created host-side and owned by the host user, so the mapped construct user only had read access to it, surfacing as mkdir '/home/construct/.pi/agent/trust.json.lock' failures. The generated docker-compose.override.yml now sets userns_mode: keep-id for rootless podman, which maps the host UID/GID directly into the container at the same numeric id instead of through the subuid range, so construct (uid 1000) aliases the host user and can write the mount it already owns. entrypoint.sh's root-phase ownership fix carried the opposite assumption (container root aliases the host user, so stay root and skip the chown); it now reads a new CONSTRUCT_USERNS_KEEPID signal to skip that fallback under keep-id and take the normal numeric-uid chown-then-drop-privileges path instead. The override cache key gained a version marker so every existing docker-compose.override.yml regenerates with the fix on next run; a daemon container already running under the old mapping keeps it until recreated (construct sys doctor --fix, which always stops and recreates the daemon), since userns_mode only takes effect at container creation.

[1.16.0] - 2026-08-23

Added

  • MicroVM hardware isolation engine (backend = "microvm"): Construct now supports microVM hardware isolation powered by microsandbox as an opt-in runtime backend alongside OCI containers. Each sandbox runs within an isolated Linux guest kernel managed by hardware hypervisors (Apple Hypervisor.framework on macOS, KVM on Linux). The runtime layer standardizes lifecycle, command execution, streaming stdio, interactive PTY sessions, and image inspection through a modular Backend interface (internal/runtime/backend.go).
  • Full guest-to-host bridge subsystem for microVMs: All Construct bridges communicate across the microVM boundary via host.microsandbox.internal: token-authenticated clipboard text and image pasting, bidirectional host-exec path mapping, SSH agent proxying (with socket listeners placed on guest tmpfs to bypass VirtioFS socket creation constraints), Herdr status reporting, and loopback TCP relays (127.0.0.1:<port> -> host.microsandbox.internal:<port>) for headless browser access to host development sites.
  • Resource sizing and persistent daemon management: Sandboxes configure 4 vCPUs and 4096 MiB RAM (CPUs: 4, MemoryMiB: 4096) by default, preventing memory starvation or Linux OOM termination on heavy JS/TS agent heaps (Claude Code, Pi extensions, Codex, OpenCode). Sandboxes dynamically recreate when switching across host project roots, persist toolchains and package installations across stop/start cycles on the sandbox root disk, and validate backend readiness fail-closed without silent fallback.
  • MicroVM health diagnostics: construct sys doctor includes dedicated checks for the microVM backend (binary detection, daemon reachability, image state, and hypervisor virtualization support).
  • rtk (Rust Token Killer) joins the default brew packages: token counting and context budgeting inside sandboxes, present on every fresh install.

Fixed

  • podman-compose parse failure on SELinux-labeled read-only mounts: the generated override emitted the global gitignore bind as src:dst:ro:z; podman-compose's short-mount grammar accepts at most three colon-separated fields, so every setup on a podman host died with could not parse mount before the container started. Mounts that already carry a mode flag now append the SELinux label as a comma-joined option (ro,z), which docker compose also accepts, and the override cache key gained a mount_mode_syntax field so existing docker-compose.override.yml files regenerate instead of keeping the unparseable line.
  • Bun bootstrap failed on fresh containers (unzip is required to install bun): the user-package installer reaches the Bun step before any brew formula is populated, and the Debian base image ships no unzip. unzip joins the base image apt layer so the installer path is self-sufficient.
  • Config ownership doctor blocked scripted and agent sessions: rootless podman's user namespace periodically rewrites files under ~/.config/construct-cli through the :z mounts, leaving them owned by a subordinate UID that blocks host-side writes. The doctor confirmed with a prompt that reads stdin (hung or consumed the piped stream in ssh batches and agent runs), and the migration fix path went straight to sudo, which cannot ask for a password without a terminal. Confirm prompts now gate on ui.StdinIsTerminal(): non-interactive sessions run the rootless fix directly, runOwnershipFix tries podman unshare chown -R 0:0 before the sudo fallback, and sudo only inherits stdin on a real terminal.

[1.15.1] - 2026-08-20

Added

  • construct sys shims: real PATH executables that route agents through the sandbox, for tools that spawn agent binaries directly: shell aliases only exist inside an interactive shell, so orchestrators, IDE extensions, and CI wrappers that resolve an agent binary on PATH or exec it without a shell (Paseo and similar harnesses spawning pi --mode rpc) never saw the aliases and ran the bare host binary. construct sys shims --install (default dir ~/.local/bin) writes two executables per supported agent: <slug> execs construct <slug> with stdin/stdout passed through unchanged (JSONL RPC streams stay clean), and ns-<slug> execs the real host binary directly, non-sandboxed (the ns- shell functions from the old alias system, now as files; the real binary is resolved on PATH while skipping the shim dir so it can never resolve to our own shim). Refuses to overwrite files it did not write (--force overrides), warns when the shim dir is off-PATH or another binary wins resolution, and --uninstall removes only files carrying our marker. --remove-aliases performs a standalone cleanup of the legacy managed shell alias block for upgraders who do not want shims.
  • Host path arguments are staged into the sandbox for harness-driven runs: orchestrators pass absolute host paths as flag values (pi --extension and --mcp-config point at temp bridge files under /var/folders, --session at the host ~/.pi store), and those paths do not exist inside the container (the sandbox home is the bind-mounted construct home; macOS Docker Desktop does not share /var/folders). Flagged values for known agents that resolve to existing host files under trusted roots (temp trees, the agent's host config dir, the caller's cwd) are copied to <construct home>/.construct-staging/<run-id>/ (0700, 8 MB per file, 16 files per run) and rewritten to their /home/construct/.construct-staging/... paths, in engine.Prepare so the daemon exec, compose run, and msb paths all inherit it. Session files register a copy-back on Teardown so sandbox progress reaches the original host store. Values outside the allowlist are left untouched. Verified live with the exact Paseo argv: bridge extension loaded in-sandbox, sandbox-born session resumed, clean JSONL on stdout.

Changed

  • The construct sys aliases system is removed: shell aliases were invisible to non-shell callers, which is the gap shims close. sys shims --install migrates existing setups by removing the managed # construct-cli aliases start/end block from the shell rc (timestamped backup first; hand-written aliases and functions are never touched). For muscle memory and older instructions, construct sys aliases --uninstall still works (deprecation note, removes the block, exit 0) while any other legacy aliases flag prints the replacement commands and exits 1.
  • All run-path status output now goes to stderr, keeping stdout reserved for the agent's own output: banners such as Running in Construct daemon: [...], daemon startup, SSH/Herdr proxy notices, rebuild hints, migration chatter, first-run initialization, and image-build progress printed to stdout, which corrupts line-delimited JSON streams consumed by harnesses and other non-interactive callers. New ui.Info/InfoLn/InfoF helpers carry these messages on internal/agent (engine, runner, msb), internal/migration, internal/runtime, internal/env, internal/config, and internal/network; interactive output is visually unchanged because terminals merge both streams. The interactive attach prompt and explicit CLI output (construct agents, help) intentionally remain on stdout. Verified warm: pi --mode rpc through an installed shim returns a pure JSONL stream with zero non-JSON stdout lines; cold-start output (first-run init, image build) also lands on stderr.

[1.14.1] - 2026-07-30

Added

  • Host exec bridge now propagates the agent's working directory to host binaries: when an agent invokes an allowlisted host binary (host_binaries, e.g. wicket) from inside the sandbox, the bridge previously ran it in the daemon's cwd. So cwd-aware CLIs like wicket wp (which detect the Docker Compose project by walking up from cwd for a compose file, with no --project flag) resolved the wrong project and failed with no services found. The shim (internal/templates/construct-host-exec) now sends the container $PWD as a cwd field on the /exec payload; internal/hostexec/server.go translates it to the matching host path via the project mount (${PWD}:${CONSTRUCT_PROJECT_PATH}), validates the result stays at or under the host project root (rejecting ../ escapes and arbitrary host dirs like /etc), and sets it as the child's working directory. Empty, unset, or out-of-mount cwd falls back to inheriting the daemon cwd (the previous behavior), so the change is backward compatible. internal/agent/engine.go passes runtime.GetProjectMountPath() (container root) and e.cwd (host root) to StartServer. After this, an agent can cd into a client project and run wicket wp / wicket docker and have them target that project on the host. Security: cwd is constrained to the project mount; anything outside the host project root is rejected, which adds nothing the agent could not already do (it can already cd there and the binary already runs as the host user). v1 limitation: only the project mount is translated, not the home mount (/home/construct); a cwd there falls back to inheriting. Requires construct build to bake the updated shim into the image. Documented in docs/HOST-EXEC.md.

[1.14.2] - 2026-07-30

Fixed

  • Host exec cwd propagation now covers daemon /workspaces mounts, not just the project mount: 1.14.1 translated the agent's container cwd only against the primary project mount (/projects/<name>), so an agent whose cwd sat under a daemon multi-path mount (/workspaces/<sha256[:8]>, one per daemon.mount_paths entry) could not be translated and wicket wp fell back to the daemon cwd, failing with no services found. StartServer now takes a list of PathMap{Container,Host}; internal/agent/engine.go passes the project mount plus every runtime.ResolveDaemonMounts mount, and resolveHostCwd tries each map while still rejecting anything that escapes a mount's host root. Empty or unknown cwd still falls back to inheriting the daemon cwd. Backward compatible; the home mount (/home/construct) remains untranslated. Requires construct build to bake the updated shim. Documented in docs/HOST-EXEC.md.

[1.14.0] - 2026-07-28

Added

  • Host loopback forwarding lets headless browsers reach host dev sites: agent-browser's Chromium runs inside the sandbox, but Chromium hardcodes localhost and *.localhost to 127.0.0.1 (RFC 6761), bypassing /etc/hosts, DNS, dnsmasq, and --host-resolver-rules. So a headless browser could not reach host dev servers like http://hyperpress.localhost, even though non-browser tools (curl, git, MCP) reached them fine. The fix is blind TCP relays on the container's 127.0.0.1 that forward to host.docker.internal, launched by entrypoint.sh (socat, next to the SSH bridge). Blind relay preserves the HTTP Host header and TLS SNI, so host vhost routers (valet, Hyperpress) and certs see the real hostname. Configured via [sandbox] host_loopback_ports (list of ints, default [80, 443], same port both sides). Emitting CONSTRUCT_LOOPBACK_PORTS into docker-compose.override.yml also adds cap_add: NET_BIND_SERVICE (consolidated with strict-mode NET_ADMIN into one cap_add: block) so the non-root construct user's socat can bind privileged ports. The port list is hash-tracked in overrideInputs, so changing it regenerates the override; an empty list disables the feature and drops the cap. Platform caveat: on Linux the host-gateway is the bridge IP, so host services must bind 0.0.0.0/bridge (not 127.0.0.1-only) to be reachable; macOS host-gateway already routes to host 127.0.0.1.
  • Terminal identity markers now forward into the sandbox: KITTY_WINDOW_ID, GHOSTTY_RESOURCES_DIR, and TERM_PROGRAM are passed into the container as -e flags on both launch paths (direct compose run and the daemon compose run -d, so docker exec sessions inherit them) so in-container TUIs and pi extensions can detect the outer terminal and do the right thing (kitty-graphics inline image rendering, etc.). TERM itself is intentionally NOT forwarded by default to avoid terminfo mismatches (forwarding xterm-kitty into a container without kitty-terminfo breaks ncurses apps like less/vim/btop); the identity vars are enough for detection. Users who need TERM can add it to env_passthrough and install ncurses-term/kitty-terminfo in the image.

Fixed

  • construct build no longer fails on the socat file cap: the Dockerfile ran setcap cap_net_bind_service+ep /usr/bin/socat at build time, but BuildKit's default sandbox blocks file-capability writes during docker build and aborts with Invalid file 'setcap' for capability operation (granting it needs the security.insecure entitlement, which docker compose build does not enable by default). The file cap is now applied at runtime: entrypoint.sh runs the same setcap as root in its startup block, before the gosu drop to the construct user. It is idempotent and best-effort, and the cap persists on the overlay for the container's lifetime, so the non-root socat still binds 80/443 via the file cap plus the NET_BIND_SERVICE bounding set (both still required). libcap2-bin (which provides the setcap binary) stays installed in the image.

[1.13.0] - 2026-07-24

Added

  • Host qmd model cache is now reused inside the sandbox: when the host has a qmd GGUF model cache at $XDG_CACHE_HOME/qmd/models (or ~/.cache/qmd/models by default), Construct bind-mounts it into the container at /home/construct/.cache/qmd/models so the qmd semantic-search backend reuses the already-downloaded models (~1.5 GB) instead of re-fetching them on every container recreate. The mount is read-write so models qmd fetches lazily later (the reranker and query-expansion GGUFs pull on first qmd query) write back to the shared host cache and become one-time. It is added only when the host directory exists (no config flag, no effect when absent), is resolved the same way qmd resolves its own cache ($XDG_CACHE_HOME first, then $HOME/.cache), and is hash-tracked in overrideInputs so docker-compose.override.yml regenerates when the directory appears or disappears. Mirrors the existing conditional gitignore mount. Documented in docs/ARCHITECTURE-DESIGN.md and docs/CONFIGURATION.md; AGENTS.md gains a contributor note on the conditional-mount pattern for future additions.

Fixed

  • construct sys daemon restart no longer fails on Docker Desktop: the daemon could fail to restart with a bare "exit status 1" that swallowed docker's actual error. The root cause was two-fold. CleanupExitedContainer ran a plain docker rm, which races on macOS where a freshly stopped container can briefly resist removal while Docker Desktop tears down host bind-mounts (extra bind-mounts, including the new qmd cache mount, widen the window). And Start()/Restart() downgraded that cleanup failure to a warning and then ran docker compose run --name, which hit a name conflict with the not-yet-removed container. Fixes: CleanupExitedContainer now uses docker rm -f (force, safe because the daemon has no dependent containers); cleanup failure in Start() now aborts instead of warn-and-continue; and Restart() no longer duplicates the exited-container cleanup, delegating it to Start() as the single owner of that logic. End-to-end verified: construct sys daemon restart exits 0 and the qmd mount survives the restart.
  • Daemon start and cleanup errors now surface docker's stderr: Start(), StopContainer, and CleanupExitedContainer switched from cmd.Run() to cmd.CombinedOutput() and include the captured output in the error, so a failed daemon start prints docker/compose's real reason instead of an opaque "exit status 1". This is what made the restart failure above diagnosable instead of a guessing game.

[1.12.2] - 2026-07-22

Fixed

  • Host exec shim no longer deadlocks on open-but-empty stdin: construct-host-exec blocked forever when launched with stdin as an open pipe that never sent data and never closed (no EOF), which is exactly the condition created by non-interactive launchers like pi-unified-exec that hold stdin open for the session lifetime. The old bare base64 read sat waiting for bytes or EOF that never arrived, so every proxied host binary (e.g. wicket config servers --json) hung with zero output under such launchers while working fine in a human's interactive terminal. The fix is a hybrid read: a non-blocking read -t 0 peek first (returns success only when data or EOF is immediately available), and if nothing is pending the read is skipped entirely, the common case for flag-only CLIs. Only when data or EOF is actually waiting does the shim consume stdin, bounded by head -c 1048576 (1 MiB cap, forces EOF after N bytes or on real EOF so base64 always flushes and closes) and timeout 5 as defense-in-depth against slow trickle feeds. If either bound fires, the shim now emits a stderr warning attributing the truncation to itself instead of surfacing it later as a confusing bridge-side parse error. The bridge contract (stdin ships up front, base64-encoded, live interactive input still unsupported by design) is unchanged. Regression tests cover the open-empty pipe case (asserts sub-3s return, was an infinite hang) and the over-cap truncation warning.

[1.12.1] - 2026-07-22

Added

  • Pi Extension Package Support: New [pi] config section lets you install pi coding-agent extensions through Construct's generated setup script instead of hand-running pi install. Sources use pi's own install syntax (npm:name, git:github.com/user/repo, url, or a local path) and are passed verbatim to pi install, which manages ~/.pi/agent/npm and settings.json atomically and is idempotent, so re-running ct sys packages --install is safe. The block runs after the [npm] step so pi is already on PATH, and it is guarded by command -v pi: if pi is absent the configured extensions are skipped with a warning rather than aborting the whole install. Each package is isolated (|| echo), so one bad extension does not take down the rest. internal/templates/packages.toml ships the section empty with commented examples so you opt in by uncommenting. Tests cover TOML parsing (table-driven), script generation, verbatim source passing, the empty-config no-op, and the missing-binary guard branch.

Changed

  • Script generators now use strings.Builder: GenerateInstallScript and GenerateTopgradeConfig (in internal/config/packages.go) stopped concatenating with += in loops and switched to strings.Builder, with fmt.Fprintf for the quoted-list entries. Same generated output, less allocation churn. Side benefit: the config local in GenerateTopgradeConfig no longer shadows the package name.

[1.12.0] - 2026-07-17

Added

  • Herdr Integration Bridge: When construct is launched from a Herdr pane, per-agent Herdr integrations now fire inside the container, producing the turn-level idle/working status panel natively (green checkmark when the agent is ready, spinner when working). Previously the agent ran in an isolated container PID namespace invisible to Herdr's host-side foreground-job detection, leaving a permanent spinner.
    • Integration file sync (internal/agent/integration_sync.go): mirrors host-side integration files into the construct home per agent slug. Currently syncs herdr-agent-state.ts for pi from $PI_CODING_AGENT_DIR/agent/extensions/ (or ~/.pi/agent/extensions/) to the container's ~/.pi/agent/extensions/. SHA-256 content compare: copies only when missing or different, so Herdr-side updates propagate on the next run; identical files are skipped (mtime preserved). Per-agent registry; other agents can be added later. Best-effort; never blocks the run.
    • Herdr socket bridge (internal/agent/herdr_bridge.go): the host Herdr API socket is AF_UNIX and unreachable from a container via bind-mount (Docker Desktop surfaces a regular file, not a live socket — Connection refused). A host TCP listener proxies each connection to the unix socket, and a per-session in-container socat (runtime-launched via docker exec, no image rebuild) bridges back to host.docker.internal:<port>. Mirrors the existing SSH agent bridge: deterministic port band 48600-58599 (no SSH overlap) with ephemeral fallback, macOS binds 127.0.0.1 / Linux 0.0.0.0, per-PID socket /tmp/herdr-agent.<pid>.sock, cleaned up in Teardown().
    • Env forwarding: injects HERDR_ENV=1, HERDR_PANE_ID (forwarded verbatim from the host pane shell), and HERDR_SOCKET_PATH (repointed at the in-container proxy socket) into the exec env. These three are the gate Herdr's integrations check (enabled() returns false without all three). Active on the daemon and attach paths; runNewContainer (compose run with no daemon) is intentionally not covered since the in-container socat does not exist there.

[1.11.0] - 2026-07-10

Added

  • Seccomp Relaxation (disable_seccomp): New [sandbox] config option that emits security_opt: [seccomp:unconfined] in the generated docker-compose.override.yml, enabling headless browser automation inside the container. Docker's default seccomp filter blocks syscalls Chrome/Chromium's multi-process CDP backend needs (clone3 with namespace flags, seccomp(2) BPF install, ptrace), causing every persistent browser launch (agent-browser, Playwright, CDP extensions) to die with Trace/breakpoint trap. Default-off; opt in with [sandbox] disable_seccomp = true. The toggle is hash-tracked so flipping it regenerates the override, and construct sys doctor surfaces when it is enabled (with a security note). Requires construct build + container restart to take effect. See docs/SECURITY.md for the tradeoff (removes a kernel-level syscall-restriction layer).

[1.10.0] - 2026-07-03

Added

  • Host Exec Bridge (host_binaries): New [sandbox] config option that lets the agent invoke selected host-only binaries from inside the container, where they actually execute on the host machine (not in the sandbox). The agent sees them on PATH and calls them normally; a shim transparently proxies each call to a host-side bridge that runs the real binary as your host user and streams stdout/stderr back with exit codes preserved.
    • Config: [sandbox] host_binaries = ["wicket"]. Off when empty (no bridge starts, zero attack surface).
    • Security model: each listed binary runs on the host with full container-controlled argv. The bridge is token-gated (per-session 32-byte bearer, X-Construct-Exec-Token), allowlist-pinned (unknown argv[0] fails closed with 403), and resolves each binary to an absolute host path at startup (no per-request exec.LookPath, no PATH-poisoning). Only list binaries you trust with container-controlled argv — declaring e.g. docker grants effective host root to the agent. A startup banner (⚠ host exec enabled: …) confirms when active; see docs/HOST-EXEC.md for the full threat model.
    • No interactive TTY by design: there is no controlling terminal; pipe stdin (one-shot) works, interactive prompts do not. Pass --no-interactive/--json/--yes flags where the listed binary offers them. Future TTY support, if ever needed, is an HTTP-upgrade transport on the same bridge (not a socat rebuild).
    • Daemon-safe: symlinks are reconciled host-side in Prepare() (no docker exec), so toggling the list takes effect on the next construct invocation without a daemon restart. Requires construct build after first enabling (the shim is baked into the image).
    • Per-call timeout: 30-minute hard cap (override via CONSTRUCT_HOST_EXEC_TIMEOUT); bridge-initiated kills surface as exit code 124 (the timeout(1) convention).
    • Audit log: every invocation is recorded at ~/.config/construct-cli/logs/host_exec.log (timestamp, argv, resolved path, exit code, duration). construct sys doctor warns on misconfig (listed binary missing on host PATH, stale shim manifest, shim not yet baked into the image).

[1.9.4] - 2026-06-20

Added

  • SSH Identity Pinning (ssh_pin_identities): New [sandbox] config option that pins one SSH identity per host to avoid Too many authentication failures when the forwarded agent holds many keys. Two entry forms: "host=keyname" (simple) and "alias=hostname=keyname" (multi-account on same service). Pinned identities are serialized into CONSTRUCT_SSH_PIN_IDENTITIES and consumed by ensure_ssh_config() in the container entrypoint, which emits IdentitiesOnly yes + the named key for each configured host.
  • Per-Session SSH Proxy Sockets: Each construct process now uses its own socat socket (/home/construct/.ssh/agent.<pid>.sock) rather than a shared /home/construct/.ssh/agent.sock. Concurrent sessions sharing one daemon no longer overwrite each other's agent proxy. Teardown() cleans up only the calling session's socket.
  • SSH Bridge Regression Tests: Table-driven tests for sshPinIdentitiesEnv (10 cases including malformed-entry guards), sshProxySockForPID (distinct PIDs, stable same PID, exact path), bridge no-agent guard, full proxy integration, and Bitwarden vault lock/unlock recycle case.
  • Entrypoint ensure_ssh_config Bash Tests: New internal/templates/entrypoint_ssh_config_test.go extracts and executes the real ensure_ssh_config shell function in an isolated HOME. Seven cases: no hardcoded IdentityAgent, physical-keys-only, agent pin with .pub, alias three-field form, physical-key fallback, pin skipped when key missing, opt-out (# construct-managed: false) respected.

Fixed

  • SSH Agent Forwarding in Container: ensure_ssh_config() no longer emits phantom IdentityFile paths for keys that do not exist on disk, eliminating false "no SSH key" reports from agents. Hardcoded ~/.ssh/default and ~/.ssh/personal are only emitted when the files are actually present.
  • IdentityAgent Override Removed: The Host * block no longer writes IdentityAgent ~/.ssh/agent.sock, which was overriding the per-session SSH_AUTH_SOCK env var and routing all agent requests to whichever session last wrote that socket. SSH now uses SSH_AUTH_SOCK directly, injected per-session by the engine.
  • Error Context on SSH Proxy Helpers (Go Mistakes #49): Both ensureDaemonSSHProxy and waitForDaemonSSHProxy now wrap errors with %w, including container name, socket path, and port, enabling errors.Is/errors.As unwrapping and actionable messages in multi-session scenarios.

[1.9.3] - 2026-06-18

Fixed

  • Pi Update No Longer Updates Extensions: Pi changed the bare pi update to update only pi (self), with pi update --all required to update pi and its extensions together. Construct's system update (ct sys update) was still calling bare pi update, so pi extensions were silently no longer updated on each run. All three invocation sites now use pi update --all: the generated topgrade config (packages.go), the static topgrade.toml template, and the manual update-all.sh fallback.

[1.9.2] - 2026-06-18

Removed

  • Worktrunk: Removed worktrunk from the default Cargo install list (packages.toml). Upstream worktrunk 0.59.0 fails to build from a registry tarball (cargo install) because its vergen-gitcl build script cannot compute VERGEN_GIT_DESCRIBE outside a git worktree, and src/cli/mod.rs uses a hard compile-time env!("VERGEN_GIT_DESCRIBE"). The Topgrade/Cargo update step aborted on every update run. The historical 0.11.1 addition entry is retained for accuracy.

[1.9.1] - 2026-06-15

Added

  • SSH Agent Reachability Check in sys doctor: The existing "SSH Agent" check only verified SSH_AUTH_SOCK was set, which a stale/recycled socket (e.g. Bitwarden/1Password after lock) passes while every in-container ssh/git op fails silently. The check now probes the agent directly via ssh-add -l and reports reachable (with key count), reachable-but-no-keys, not-reachable, or unknown (ssh-add missing). Extracted into a unit-tested checkSSHAgent helper.

Fixed

  • Concurrent Setup Deadlock: Two docker compose run setups running at once (e.g. a user retrying because setup looked stuck) share the same home bind-mount and both write into npm's shared global node_modules + cache, deadlocking on npm's cache/lock and hanging indefinitely. Setup now takes a non-blocking exclusive flock on ~/.config/construct-cli/setup.lock before spawning the compose run; a second instance refuses to start and tells the user to wait. The lock auto-releases on process exit, so no manual cleanup is needed after a crash.
  • Redundant npm Reinstalls During Setup: Every npm install -g in the generated setup script used --force, re-fetching and re-linking all global packages on every setup run. This made setup slow enough to look stuck (the trigger for the retry that caused the deadlock above). --force is dropped from the setup path so npm skips packages already at the target version. update-all.sh still uses --force for explicit @latest upgrades (intentional).
  • SSH Agent Bridge Silently Failed on Stale Port: The in-container socat SSH-agent proxy (started by entrypoint.sh at container creation) baked the host bridge port into its argv. The host bridge (StartSSHBridge) bound a random ephemeral port that changed across CLI invocations, so the stale socat kept pointing at a dead host port and every ssh/git op failed with "communication with agent failed". The v1.8.15 "restart socat on each exec" fix did not actually work: ensureDaemonSSHProxy backgrounds socat (returns near-instantly, almost never errors) and both exec sites discarded the result of waitForDaemonSSHProxy (gated on if err == nil). The liveness probe was test -S (socket file exists), which a leftover socket or stale socat passes. Now both exec sites check and log both errors, and the probe is a real UNIX-CONNECT (socket actually accepting connections).
  • Topgrade Brew Step Crash on Linux: The homebrew/cask tap (auto-tapped under HOMEBREW_NO_INSTALL_FROM_API mode) breaks brew upgrade on Linux because casks use arch-conditional sha256 arm:/intel: that resolves to nil on non-macOS systems (e.g. Casks/0/0-ad), aborting the whole update run and stalling all formula upgrades. Both update-all.sh and entrypoint.sh now defensively untap homebrew/cask on Linux (idempotent). Casks are macOS-only and non-functional on the Linux box.

Changed

  • Deterministic SSH Bridge Port per Box: StartSSHBridge now derives a stable TCP port from the box identity (hash of the container name) instead of an ephemeral random port, with fallback to ephemeral on bind failure. The same box now maps to the same host port across invocations, so a stale socat baked at container creation keeps pointing at the right port instead of aging out. Band sits below the Linux ephemeral range (32768+) to reduce OS collisions. Correctness is still guaranteed by the per-exec socat restart regardless of port strategy.

[1.9.0] - 2026-06-10

Added

  • Non-interactive Container Exec: New construct sys exec -- <command> command allows running a single command inside a running Construct container without attaching to an interactive shell. Designed for LLM agents operating in headless environments that need to execute commands inside the container and capture output. Streams stdout/stderr separately to the host, returns the container process exit code. Supports both daemon and CWD-scoped containers. Requires a running container (start with construct sys shell or construct sys daemon start).
  • Daemon Name Constant: Canonical DaemonName constant in internal/constants/constants.go replaces scattered string literals across agent engine and sys packages.
  • Container Naming Export: CwdContainerName() moved from internal/agent (unexported) to internal/runtime (exported) for cross-package reuse.
  • Non-interactive Exec Primitive: ExecNonInteractiveStream() in internal/runtime/runtime.go executes commands in running containers without TTY allocation, streaming stdout/stderr separately, returning real exit codes.

Changed

  • MapDaemonWorkdir and ReadKeyringEnv exported from agent package for reuse by sys exec.
  • Smart Migration Rebuilds: Container image rebuilds are now gated by per-template hash tracking. Version bumps that only change Go code (no template changes) skip the rebuild entirely, reducing update time from ~2 minutes to ~2 seconds. Templates are classified into tiers: image-baked (Dockerfile, entrypoint.sh, etc.) trigger a full rebuild; runtime-only (docker-compose.yml, agent-patch.sh) trigger a deferred restart; no changes means no rebuild.

[1.8.15] - 2026-06-03

Changed

  • Cross-Platform SSH Agent Bridge: Replaced macOS-only SSH agent forwarding with a TCP bridge that works on both macOS and Linux. On macOS the bridge binds 127.0.0.1 (Docker Desktop routes it); on Linux it binds 0.0.0.0 so containers reach it via host.docker.internal. Removed direct SSH_AUTH_SOCK socket mounts and permission-fixing logic from entrypoint and compose overrides.
  • Dynamic SSH Agent Socket Re-reading: The TCP bridge now re-reads SSH_AUTH_SOCK per connection, with up to 3 retry attempts (100ms backoff). Handles agents like Bitwarden that recycle socket paths on vault lock/unlock.
  • Daemon SSH Proxy Restart: Running containers now get their socat proxy restarted with the current bridge port on each exec, preventing stale-port failures across sessions.

Fixed

  • NPM Package Setup Failures: Added --force flag to global npm package installs and upgrades during construct provisioning. Prevents setup crashes caused by pre-existing symlink conflicts (e.g. EEXIST conflicts during @kilocode/cli installation) and cascading tar TAR_ENTRY_ERROR ENOENT extraction errors (which blocked the installation of the pi package).
  • SSH Agent Proxy Leak: Added pkill cleanup commands in container provisioning and execution engines to terminate stale background socat socket listeners before binding new instances, preventing orphaned process accumulation and routing failures across sessions.

[1.8.14] - 2026-06-02

Added

  • Global Gitignore Mount: User's global gitignore file is now automatically mounted (read-only) into the container at /home/construct/.config/git/ignore. Detects the file from four locations in priority order: git config core.excludesFile, $XDG_CONFIG_HOME/git/ignore, ~/.gitignore, and ~/.gitignore_global. Tilde paths and spaces in paths are handled correctly. Mount is included in the override hash for proper cache invalidation.

[1.8.12] - 2026-05-20

Added

  • CWD-Derived Container Naming: Each working directory now gets its own container (construct-cli-<sha256[:8]>) instead of a shared singleton. Running agents from multiple terminals with different working directories no longer conflicts with "Container 'construct-cli' is already running." Same directory always hashes to the same container name, preserving attach semantics.

Fixed

  • Daemon Killed by sys doctor --fix: cleanupAgentContainer prefix-matched construct-cli-daemon and killed it; recreateDaemonContainer then saw it missing and no-op'd. Daemon is now excluded from session container cleanup.
  • Stopped Containers Returned by Network Manager: runningSessionContainers used docker ps -aq (all states), causing spurious UFW rule warnings on stopped containers. Now filters by running state.
  • Legacy Singleton Missed by Migration: collectSessionContainers only discovered CWD-hash containers, missing pre-upgrade construct-cli singleton. Now includes exact-match discovery for the legacy name.
  • Stale Error Message: sys doctor --fix suggestion referenced old docker rm -f construct-cli instead of prefix-based cleanup.
  • Keyring Env Path Hardcoding: readKeyringEnv used os.UserHomeDir() instead of config.GetConfigDir() for the keyring env file path.

Changed

  • Container Discovery: All consumers of the static "construct-cli" container name (doctor.go, migration.go, network/manager.go, reset-environment.sh) now discover containers by prefix "construct-cli-" via runtime.ListContainersByPrefix().
  • Architecture Docs: Updated ARCHITECTURE-DESIGN.md Sections 4, 9.3, and 11.2.1 for the new naming pattern.

[1.8.11] - 2026-05-20

Added

  • Antigravity Update Integration: Added agy update integration to the dynamic Topgrade generator (packages.go), topgrade.toml template, and the manual system update fallback script (update-all.sh).

Fixed

  • Yolo Configuration for agy: Fixed yolo settings (yolo_all and yolo_agents) to correctly apply the --dangerously-skip-permissions flag when initializing the agy agent.
  • Agent Credential Persistence: Added gnome-keyring, libsecret-1-0, and dbus-x11 to the container image, with automatic daemon startup in the entrypoint. Agents (e.g., agy) that rely on the OS keyring for OAuth tokens now persist credentials across container restarts. Previously, every session required a fresh login.
  • Keyring Daemon Startup: Fixed gnome-keyring-daemon invocation in the entrypoint. The --start and --unlock flags are mutually exclusive and caused the daemon to silently fail, leaving the keyring locked. Changed to --unlock --components=secrets which both starts the daemon and unlocks the login keyring with a blank password.
  • Keyring Env Vars Not Reaching Agents: docker exec runs agents directly (no shell), so .bashrc/.profile are never sourced and GNOME_KEYRING_CONTROL/DBUS_SESSION_BUS_ADDRESS were invisible to agent binaries. agy's keyring auth timed out after 1s, fell back to browser OAuth, and hung. Fixed by having the entrypoint write these vars to ~/.construct-keyring-env, which the Go CLI reads from the host-side bind mount and injects via -e flags on every docker exec.

[1.8.8] - 2026-05-20

Changed

  • Replaced Gemini CLI with Antigravity CLI: Replaced gemini agent (Google Gemini CLI, npm-installed) with agy agent (Google Antigravity CLI, curl-installed from https://antigravity.google/cli/install.sh). Binary lands at ~/.local/bin/agy. Removed Gemini-specific clipboard paste wrapper (~220 lines of Python PTY code). Renamed GEMINI_API_KEY to ANTIGRAVITY_API_KEY throughout source, templates, and docs. Updated agent registration, memory paths, constants, env passthrough, runtime candidates, wayland/yolo flags, help text, aliases, verification loops, and all documentation.

[1.8.7] - 2026-05-15

Fixed

  • Yolo Mode Ignored on Cold-Start: Config values (yolo_all, yolo_agents) from config.toml were not applied when running agents via the cold-start (non-daemon) path. Root cause: Execute() applied yolo flags to a local variable, but runNewContainer() discarded its args parameter (_) and read e.args directly from the struct. The daemon path worked because it passed yolo-applied args directly. Fixed by writing yolo results back to e.args and removing the dead parameter from runNewContainer().

[1.8.6] - 2026-05-14

Fixed

  • Global AGENTS.md Symlinks Leaked onto Host: Removed creation of AGENTS.md, CLAUDE.md, and GEMINI.md symlinks in /workspaces/ and /projects/. These directories are host bind mounts, so symlinks pointing to the container-internal /home/construct/AGENTS.md leaked onto the host filesystem as dangling links. Symlink aliases are now created only inside /home/construct/ (container-internal). Existing dangling symlinks on the host must be cleaned up manually.

[1.8.5] - 2026-05-12

Fixed

  • AGENTS.md Symlink Scope: Fixed symlinks being created at every directory level (including project subdirectories), which overwrote pre-existing AGENTS.md files. Symlinks are now created only at /workspaces/, /workspaces/<hash>/, and /projects/. Pre-existing real files are never overwritten. Now also creates CLAUDE.md and GEMINI.md symlinks alongside AGENTS.md, all pointing to the global rules file.

[1.8.4] - 2026-05-11

Added

  • Global AGENTS.md Symlinks: Automatically creates symlinks to the global AGENTS.md rules file in common mount points (/workspaces/, /projects/) and their subdirectories. This ensures agents can easily discover global instructions regardless of the project's mount path or random daemon hash.

[1.8.3] - 2026-05-09

Fixed

  • ARM64 Setup & agent-browser: Fixed agent-browser installation failure on ARM64 Linux by implementing an automatic wrapper that uses the system Chromium (/usr/bin/chromium).
  • Resilient Package Installation: Added failure-safe command execution (cmd || echo ...) in install_user_packages.sh to prevent a single tool failure from aborting the entire setup.
  • Sudo Detection: Improved SUDO_AVAILABLE check to use sudo -n apt-get --version, resolving issues in restricted sudoers environments where true was not permitted.
  • Multimodal Clipboard Regression: Resolved image pasting failures in Gemini and Codex agents.
    • macOS Networking: Fixed 127.0.0.1 hardcoding in the clipboard server; now correctly uses host.docker.internal (or configured clipboard_host), making the host clipboard reachable from the container.
    • Ghostty / Bracketed Paste Support: Added PTY interception for \x1b[200~ sequences. PTY wrappers now consume bracketed paste content and inject image paths directly, bypassing terminal protocol mismatches.
    • Daemon Patching: Restored runAgentPatchInDaemon logic to ensure shims, wrappers, and Xvfb are correctly initialized in persistent daemon containers.
    • Headless X11 (Xvfb): Moved Xvfb startup to the agent-patch script to ensure a valid DISPLAY is always available for agents that expect a real display.
    • Gemini @-prefix: Ensured Gemini uses the required @path injection format while Codex uses raw paths.
  • Agent Update Hardening: Added defensive command -v checks for Claude and Pi update commands in Topgrade configuration, preventing errors in environments where these agents are not installed.

Changed

  • Official Goose CLI Installer: Switched to the official Goose installation script (github.com/aaif-goose/goose) for more reliable setup.
  • Updated OpenCode Installer: Updated the OpenCode post-install command to use the canonical curl -fsSL https://opencode.ai/install | bash.
  • Pi Update Integration: Integrated pi update into the primary Topgrade update path to ensure Pi and its internal packages are kept current during ct sys update.
  • Clean Brew Installation Logs: Optimized the Homebrew installation script to check for existing packages before running brew install, replacing noisy "already installed" warnings with clean confirmation messages.

Optimized

  • Fast Agent Startup (Patch Marker): Implemented a versioned marker-based guard (~/.construct_patched) to skip redundant agent clipboard patching on startup. This significantly reduces latency when entering a shell or launching an agent in a warm daemon container. Patching is now only re-triggered after Construct version changes or package installations.

[1.8.2] - 2026-05-09

Fixed

  • Daemon shell with no args: execViaDaemon now defaults to the configured shell (sandbox.shell or /bin/bash) when invoked without arguments, matching the existing behavior of execInRunningContainer. Previously, ct sys shell passed an empty command to docker exec, causing "requires at least 2 arguments" error.

[1.8.1] - 2026-05-09

Fixed

  • Root exec in daemon containers: resolveExecUserForRunningContainer() now returns "construct" on all code paths instead of "". Previously, docker exec -it into the daemon ran agents as root (since USER construct is commented out in the Dockerfile), causing Claude CLI to reject --dangerously-skip-permissions. Added uid==0 guard and UsesUserNamespaceRemap() check.
  • Pre-existing test failure on uid=0 CI: TestAppendExecUserRunFlags now correctly handles root environments by expecting no --user flag when uid=0.
  • Missing daemon launch messages: Restored "Running in Construct daemon: [args]", "Entering Construct daemon shell...", "✓ Started SSH Agent proxy (daemon)", and exit code 126/127 PATH hints that were lost in the v1.8.0 refactor.

Changed

  • Claude install moved to packages.toml: Claude Code installation moved from hardcoded packages.go to [post_install] section in packages.toml, consistent with droid/opencode pattern.
  • Pi self-update in update routine: Added pi update to both update-all.sh manual fallback and topgrade [commands] config.

Removed

  • Dead code cleanup: Removed unused containerHasUIDEntryFn variable, dead execUserForAgentExec function (never called from production), and stale test mocks that referenced removed behavior.

[1.8.0] - 2026-05-08

Changed

  • Runtime Engine Extraction: Extracted ~1000 lines of monolithic container orchestration from internal/agent/runner.go into a dedicated RuntimeEngine in new internal/agent/engine.go. runner.go now delegates to engine.Prepare() and engine.Execute(), centralizing daemon detection, clipboard server, SSH bridge, login forwarding, environment assembly, and container state handling.
  • Security Session Interface: Introduced a new Session interface in internal/security/session_deep.go with noOpSession (disabled) and secureSession (active) implementations. Replaces direct SessionManager coupling throughout the codebase with a clean, testable abstraction.
  • SecretShield Deep Module: Created internal/security/shield.go as a deep module encapsulating secret detection and redaction into a single atomic Protect() operation. integration.go now delegates scanning to SecretShield instead of orchestrating Scanner directly.
  • Runner Integration Refactor: internal/security/runner_integration.go completely rewritten around the Session interface. Uses the new security.Open() factory and delegates env masking to sess.MaskEnv(). Eliminates redundant EnvMasker instantiation at every call site.
  • Scanner JSON Serialization: Replaced hand-rolled string-buffer JSON generation in internal/security/scanner.go with proper encoding/json MarshalIndent/Unmarshal for manifest and redaction index I/O.
  • RiPGrep Path Resolution: Changed hardcoded /usr/bin/rg stat to exec.LookPath("rg") for cross-distro compatibility.
  • Session Manager Cleanup Removal: Removed Manager.Cleanup() and isProcessAlive orphan-reaping logic from internal/security/session.go. Session lifecycle is now managed explicitly through the Session interface.
  • Workspace Type Override: Added CONSTRUCT_SECURITY_WORKSPACE_TYPE environment variable and WorkspaceTypeNone for testability. DetectWorkspaceType() now respects the override.
  • OverlayFS Mount Fix: Corrected overlayfs mount options to use lowerdir=<lower>,upperdir=<upper>,workdir=<work> instead of the erroneous lowerdir=<lower>:<upper>,upperdir=<upper>,workdir=<work> that duplicated the upper layer in the lower stack.
  • Setup PATH Construction: runSetup now constructs PATH and CONSTRUCT_PATH directly via env.BuildConstructPath() instead of the removed applyConstructPath() helper.

Added

  • Clipboard Server Stop: Added Stop() method to internal/clipboard/server.go for clean listener shutdown.
  • Security Session Tests: Added internal/security/session_test.go with regression coverage for both noOpSession (disabled) and secureSession (enabled with CONSTRUCT_SECURITY_WORKSPACE_TYPE=none) deep interface behavior.

Fixed

  • Nil Status Guards: Added nil-pointer checks in internal/security/integration.go before calling sm.status.IsEnabled().
  • CI Lint Toolchain Pin: Bumped golangci-lint pin from v2.11.4 to v2.12.2 in build and release workflows; aligned local Makefile CI pin to 2.12.2.

Removed

  • ~29 Helper Functions from runner.go: Removed collectForwardedEnv, buildRunFlags, shouldEnableLoginForward, applyYoloArgs, applyConstructPath, execUserForAgentExec, appendExecUserRunFlags, resolveExecUserForRunningContainer, shouldEnableYolo, yoloFlagForAgent, readLoginBridgePorts, parsePorts, formatPorts, mapDaemonWorkdir, warnDaemonMountFallback, getEffectiveCwd, execViaDaemon, runAgentPatchInDaemon, buildDaemonExecEnv, execInRunningContainer, startDaemonSSHBridge, ensureDaemonSSHProxy, waitForDaemonSSHProxy, checkDaemonSSHProxy, startDaemonBackground, waitForDaemon, ensureAgentRuntimeDirs, appendAgentSpecificRunFlags, appendAgentSpecificDaemonEnv, and appendAgentSpecificExecEnv. All behavior preserved in RuntimeEngine.

[1.7.7] - 2026-05-07

Changed

  • Pi Coding Agent Package: Switched from @mariozechner/pi-coding-agent to @earendil-works/pi-coding-agent in default npm packages.
  • Gemini CLI Distribution: Moved gemini-cli from Homebrew to npm (@google/gemini-cli) for consistent cross-platform installation.
  • OpenCode Install Source: Removed opencode from default Homebrew packages; added automatic OpenCode installation via official installer in [post_install] commands.

Removed

  • Oh My Pi Agent: Removed omp (Oh My Pi) from supported agents. Unregistered from agent.go, help.go, memories.go, update-all.sh, packages.toml, README.md, and AGENTS.md.

[1.7.6] - 2026-05-03

Fixed

  • OrbStack Repeated Launch: On macOS, Construct no longer brings OrbStack to the foreground on every invocation when Docker is already running in the background. startRuntime now checks docker info before launching OrbStack, avoiding redundant open -a OrbStack calls.

[1.7.5] - 2026-05-02

Fixed

  • pnpm Update False Failure: ct sys update reported pnpm: FAILED because topgrade's native pnpm step exits non-zero when pnpm is managed by Homebrew. Topgrade's pnpm step is now disabled — brew already handles pnpm updates.

[1.7.4] - 2026-04-28

Fixed

  • OpenCode First-Run SQLite Error: Pre-creates ~/.local/share/opencode and ~/.config/opencode directories before container startup, preventing the DrizzleError: Failed to run the query 'PRAGMA journal_mode = WAL' failure that occurred on first run in a fresh Construct environment.

[1.7.3] - 2026-04-27

Changed

  • Expanded Default Env Passthroughs: Fresh configs now default sandbox.env_passthrough to include GITHUB_TOKEN, GEMINI_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, QWEN_API_KEY, MINIMAX_API_KEY, KIMI_API_KEY, ZAI_API_KEY, MIMO_API_KEY, OPENCODE_API_KEY, and CONTEXT7_API_KEY.

[1.7.2] - 2026-04-21

Added

  • Host Service Env: New host_service_env field in [sandbox] config section. Injects environment variables into the container with localhost/127.0.0.1 automatically rewritten to host.docker.internal. Enables agents inside the sandbox to reach host services like AgentMemory without complex IP detection. Example: "AGENTMEMORY_URL=http://localhost:3111".
  • AgentMemory config directory (~/.agentmemory) is now created on container first run.

Changed

  • Replaced the [bridge] configuration section and internal/bridge package (IP detection, gateway probing, CONSTRUCT_* env vars) with the simpler host_service_env mechanism. The old [bridge] config is no longer recognized and should be removed from config.toml.

Removed

  • Deleted internal/bridge/ package (config, detector, injector, integration).
  • Removed [bridge] section from config template and BridgeConfig type.

[1.7.1] - 2026-04-20

Added

  • Daemon Restart Command: New construct sys daemon restart command that stops and starts the daemon container in one operation. Handles all container states: missing (starts), stopped (cleans up and starts), and running (stops then starts).

[1.7.0] - 2026-04-16

Changed

  • Host Service Env: Replaced the [bridge] configuration section with host_service_env in [sandbox]. Configure environment variables that are injected into the container with localhost/127.0.0.1 automatically rewritten to host.docker.internal, enabling agents to reach host services (e.g., AgentMemory) without complex IP detection.

[1.6.4] - 2026-04-11

Added

  • Hide Secrets Allowlist: Added hide_secrets_allow_paths configuration option to exclude specific files from redaction. This allows tools that must read files directly (like AWS CLI with ~/.aws/credentials) to bypass secret redaction while keeping the security model intact.
  • Content-Based Scanning: Enhanced secret detection with ripgrep integration for fast pattern-based scanning across all project files.
  • Stream-Time Output Masking: Added regex-based secret pattern matching in stdout/stderr to catch API keys, tokens, and credentials in agent output.

Changed

  • Help Text Simplified: Removed verbose Network Management and Agent Examples sections from main help output for better scannability. Users can access detailed help via namespace-specific commands (construct network --help).
  • Help Text Alignment: Fixed column alignment in help text - all descriptions and continuation lines now align properly for improved readability.
  • CLI Command Syntax: Corrected documentation to use construct <agent> instead of non-existent construct run <agent> syntax across all documentation files.

Fixed

  • Hide Secrets Experiment Notice: Suppressed "hide_secrets=off" message for users who haven't enabled the experimental feature, preventing unnecessary noise.
  • Code Lint Issues: Fixed ineffassign lint error in scanner.go by removing unused args assignment.

Documentation

  • Comprehensive Documentation Restructure: Created 8 new user-facing documentation files organized by topic:
    • docs/HIDE-SECRETS.md - Complete secret redaction user guide
    • docs/INSTALLATION.md - Platform-specific installation instructions
    • docs/CONFIGURATION.md - Complete configuration reference
    • docs/SECURITY.md - Security features and best practices
    • docs/PROVIDERS.md - Custom Claude API endpoint configuration
    • docs/PACKAGES.md - Package management guide
    • docs/AGENTS.md - Complete agent reference
    • docs/INDEX.md - Documentation navigation hub
  • README Streamlining: Reduced README from ~500 lines to ~100 lines with links to detailed documentation.

[1.6.3] - 2026-04-08

Fixed

  • Codex Image Paste Reliability (Non-Daemon): Added a dedicated Codex Python PTY wrapper (construct-codex-wrapper-v1) that intercepts paste keystrokes, fetches PNG data from the host clipboard bridge, saves it in .construct-clipboard/, and injects the file path directly into Codex input. This removes dependence on fragile terminal/X11 clipboard internals.
  • Copilot PTY Recursion / PTY Exhaustion: Fixed wrapper installation logic to avoid overwriting or self-targeting the real npm-global copilot binary, preventing recursive wrapper launches and OSError: out of pty devices.
  • Copilot Wrapper Install Robustness: Hardened wrapper path selection to install at the active PATH-resolved binary while preserving the true npm-global target for execution.
  • Clipboard Debug Coverage: Expanded construct sys clipboard-debug with Codex wrapper diagnostics (which codex, wrapper marker/version, _REAL target, wrapper log tail) to make Codex paste failures directly actionable.

Changed

  • CI Lint Toolchain Pin: Updated CI golangci-lint pin from v2.10.1 to v2.11.4 in build and release workflows; aligned local Makefile CI pin message to 2.11.4.
  • Codex Clipboard Architecture: Retired the legacy Codex WSL/powershell clipboard fallback path in favor of PTY-wrapper-first handling.
  • Entrypoint Clipboard Behavior: Clarified runtime behavior so Codex is excluded from optional X11 sync startup because Codex paste now routes through PTY interception.

Removed

  • Legacy Codex WSL Clipboard Fallback: Removed WSL env injection (WSL_DISTRO_NAME, WSL_INTEROP, forced DISPLAY) from run/exec/daemon env assembly for Codex.
  • powershell.exe Shim Path: Removed the fake powershell.exe template and all associated embedding/init/migration/runtime references.
  • Legacy Codex WSL Docs/Diagnostics: Removed stale WSL/powershell references from clipboard diagnostics and architecture documentation.

Documentation

  • Clipboard & Architecture Docs Refresh: Updated docs/CLIPBOARD.md and docs/ARCHITECTURE-DESIGN.md to reflect the new PTY-wrapper model for Codex, Copilot wrapper v9, and removal of the WSL/powershell fallback path.

[1.6.2] - 2026-04-06

Fixed

  • Claude Code Update False Failure: claude update exits non-zero when already up-to-date, causing topgrade to report Claude Code: FAILED in the summary even though no update was needed. Fixed by appending || true to the command in both the embedded topgrade.toml template and the dynamically generated topgrade config (GenerateTopgradeConfig).

[1.6.0] - 2026-04-02

Added

  • Crush CLI Agent Support: Added Charmbracelet Crush (crush) as a first-class agent, installed via npm (@charmland/crush), with agent mount registration, AGENTS.md global rules path wiring, help/docs updates, and post-install/post-update verification checks.

Changed

  • Yolo Agent Coverage: Added crush to yolo flag handling (--yolo) and updated supported-agent documentation comments.
  • Agent Install Detection: Included crush in initial agent-installed detection checks used after image build/setup.
  • Alias UX Messaging: Updated shell alias onboarding copy to include crush among example sandboxed commands.

Fixed

  • Yolo Supported List Accuracy: Corrected README yolo-supported slug list to match runtime behavior.
  • Agent Additions Log Completeness: Updated AGENTS.md entry for Crush to include all touched integration files.

[1.5.2] - 2026-03-22

Fixed

  • Copilot Image Paste via PTY Wrapper: Replaced the non-functional JS clipboard bridge approach with a Python PTY wrapper that intercepts paste keystrokes at the outer Docker PTY layer, fetches the image from the host clipboard bridge, saves it to .construct-clipboard/, and injects @path as typed text into Copilot's input. The JS bridge never fired in headless environments because Copilot's internal clipboard module had no display to read from.
  • Kitty Keyboard Protocol (KKP) Support: Modern terminals (Ghostty and others using KKP) send Ctrl+V and Cmd+V as CSI-u escape sequences (\x1b[118;5u and \x1b[118;9u) rather than the legacy \x16 control byte. The wrapper now intercepts all three variants via _handle_paste().
  • PTY Wrapper PATH Shadowing: The wrapper is installed at /home/linuxbrew/.linuxbrew/bin/copilot (the Homebrew bin, which takes PATH priority) rather than ~/.local/bin/copilot which was silently bypassed. The real copilot binary path (~/.npm-global/bin/copilot) is resolved via npm-global candidates and injected at install time so Node's relative module imports resolve correctly.
  • Clipboard Diagnostic Improvements: sys clipboard-debug now shows the resolved which copilot path, the wrapper version, the _REAL binary path, and the full wrapper log tail for Copilot sessions.

[1.5.1] - 2026-03-20

Fixed

  • SSH Agent Access in Daemon Sessions (Linux): Fixed a regression where agents running via exec into a warm daemon container on Linux could not see the forwarded SSH agent. Injected SSH_AUTH_SOCK=/ssh-agent into the environment for all daemon and attachment exec flows.
  • Claude & Copilot Image Pasting (Headless/Linux): Enabled reliable image pasting for Claude Code and GitHub Copilot in headless environments by forcing XDG_SESSION_TYPE=wayland, routing clipboard requests through the clipper bridge.
  • Multimodal Paste Routing: Added claude and copilot to the file-based paste agent list so they correctly receive @path references for pasted images instead of raw binary data.

Added

  • Regression Coverage for Headless Clipboard: Added unit tests ensuring claude, pi, and copilot receive required environment variables for headless clipboard routing.
  • Regression Coverage for Daemon SSH Agent: Added verification for SSH_AUTH_SOCK injection in daemon execution environments on Linux.

[1.5.0] - 2026-03-19

Added

  • Generic Environment Variable Passthrough: Added first-class [sandbox].env_passthrough support so users can forward arbitrary host environment variables into Construct without editing compose overrides.
  • Prefix-Based Env Auto-Pass: Added [sandbox].env_passthrough_prefixes with default ["CNSTR_"], allowing host vars like CNSTR_CONTEXT7_API_KEY to appear inside Construct as CONTEXT7_API_KEY.
  • Default Auth Passthroughs: Fresh configs now include GITHUB_TOKEN and CONTEXT7_API_KEY in sandbox.env_passthrough by default.
  • Regression Coverage for Env Forwarding: Added tests covering explicit env passthrough, CNSTR_ prefix stripping, precedence rules, fresh-run flag injection, daemon exec env assembly, and template/default-config drift.

Changed

  • Env Precedence Rules: Explicit sandbox.env_passthrough keys now win over prefix-derived CNSTR_ passthrough when both target the same inside variable name.
  • Documentation: Updated README and architecture docs to document generic env passthrough, default forwarded keys, and the new CNSTR_ auto-pass behavior.

[1.4.6] - 2026-03-18

Fixed

  • NPM Global Package Updates: sys update now upgrades npm global packages to their latest versions. Previously, topgrade's npm update -g respected caret semver ranges and would not cross minor/major version boundaries (e.g., 0.58.4 would never reach 0.60.0). Disabled topgrade's npm step and replaced it with explicit npm install -g <pkg>@latest for each globally installed package.

Added

  • Oh My Pi Agent: Added Oh My Pi (omp) as a supported agent — a fork of Pi Coding Agent with Python/IPython integration, LSP support, and extended tooling. Installed via Bun (@oh-my-pi/pi-coding-agent).

Changed

  • Agent Addition/Removal Docs: Updated AGENTS.md with complete checklist for adding or removing agents, covering all files that need changes.

[1.4.5] - 2026-03-17

Removed

  • Linux Brew Self-Heal: Removed heal_linux_brew_conflicts() from update-all.sh and its associated tests. The summarize formula conflict has been resolved at the image level and this code path was dead.

[1.4.4] - 2026-03-16

Fixed

  • Pi Image Paste Routing: Pi agent sessions now force the clipboard path through the clipper shim instead of native X11 bindings, avoiding Linux image-paste failures in the container.
  • Copilot Clipboard Bridge Reliability: Added a direct patch for @teddyzhu/clipboard so Copilot can fetch clipboard images from the host bridge even when native clipboard access is unavailable in the container.
  • Headless Clipboard Sync Timing: Tightened the X11 clipboard sync loop and improved argument handling/debug logging in the clipboard bridge scripts to reduce races when agents read pasted images.
  • Linux Brew Update Self-Healing: construct sys update now maintains a Linux self-heal list for unsupported Homebrew formulas, currently replacing summarize with the supported npm package @steipete/summarize before brew upgrades run.

Added

  • Clipboard Debug Command: Added construct sys clipboard-debug to show clipboard bridge logs, patch state, temporary files, and running sync processes for Copilot debugging.
  • Clipboard Debug Documentation: Added docs/CLIPBOARD.md with patching details, debug workflow, and expected log locations for clipboard bridge issues.
  • Updater Regression Guards: Added template coverage for Linux brew self-heal entries and to ensure unsupported fallback packages like summarize are not shipped as default packages.toml installs.

Changed

  • Debug Environment Propagation: CONSTRUCT_DEBUG=1 is now forwarded consistently into run, exec, and daemon flows so clipboard diagnostics work across agent entry paths.
  • Default Package Template: Added lftp, tmux, and btop to the default Homebrew package template.
  • CLI Help: Updated help output to expose the new construct sys clipboard-debug command.

[1.4.3] - 2026-03-11

Fixed

  • Beta Channel Stable Release Detection: Beta-channel update checks now compare both VERSION and VERSION-BETA, so beta users receive newer stable releases such as 1.4.2 even when the beta marker is still on an older prerelease like 1.4.0-beta.11.

Added

  • Update Channel Regression Coverage: Added tests covering beta users receiving newer stable releases, preferring newer prereleases when beta is ahead, and fallback behavior when one remote marker fetch fails.

[1.4.2] - 2026-03-11

Added

  • Bun Package Manager Support in packages.toml: Added a first-class [bun] section so default and user-defined Bun global packages can be managed alongside apt, brew, npm, and pip.
  • Doctor Packages Template Drift Detection: construct sys doctor now warns when packages.toml is missing default template sections, keys, or default list entries such as bun.packages = ["@tobilu/qmd"].
  • Doctor Drift Coverage: Added test coverage for missing package sections, missing template list entries, and user override cases that should not be flagged.

Changed

  • Bun Availability: Bun is now installed unconditionally during setup and exposed on PATH, instead of being treated as an opt-in [tools] entry.
  • Default Package Template: Moved @tobilu/qmd into the default [bun] package list in packages.toml.
  • Local Lint Version Handling: Local make lint and make check no longer fail on patch-level golangci-lint drift; CI remains pinned to the repo version.
  • CLI Help and README: Updated documentation and help text to reflect Bun support, default Bun installation, and packages.toml drift checks in construct sys doctor.

Fixed

  • Clipboard Test Lint Failure: Adjusted PNG test fixture allocation to satisfy prealloc linting and keep make check green.

[1.4.1] - 2026-03-03

Fixed

  • Embedded Gum Confirm Option Styling: Restored proper selected/unselected option spacing and styling in embedded gum confirm prompts so interactive confirmations no longer render collapsed labels like YesNo.

Added

  • Confirm Style Regression Coverage: Added UI test coverage to ensure embedded confirm defaults retain required prompt and option style spacing.

[1.4.0] - 2026-02-25

Changed

  • Linux Userns/Rootless Runtime Detection: Added runtime-level user namespace remap detection and propagation (CONSTRUCT_USERNS_REMAP) across compose startup, setup, daemon, and update flows.
  • Linux Identity Strategy for Rootless Modes: Updated Linux startup behavior to avoid forcing host UID:GID mappings when userns remap/rootless mode is active, while preserving existing non-root-strict behavior where applicable.
  • Entrypoint User Drop Logic (Linux): Entrypoint now keeps namespace-root execution in remapped-userns mode to prevent bind-mount ownership drift during startup bootstrap.
  • Exec User Mapping Guardrails: exec_as_host_user Linux Docker exec mapping is now skipped automatically when userns remap is detected.
  • Host Alias Targeting: construct sys aliases now resolves aliases through the managed ct shim (with executable fallback) instead of bare construct, preventing PATH-driven version drift when Homebrew stable and local beta coexist.
  • Installer Version Normalization: scripts/install.sh now normalizes incoming versions (strips optional v prefix) and preserves prerelease identifiers in installed-version detection.
  • Linux Permission Recovery Flow (Runtime): Replaced the legacy best-effort auto-fix behavior with a strict, interactive repair flow that detects non-writable config paths, prompts for confirmation, and blocks agent execution until ownership is repaired.
  • Linux Permission Recovery Flow (Migration): Migration permission recovery now follows the same explicit yes/no prompt model and surfaces exact manual remediation commands when the user declines or repair fails.
  • Ownership Remediation Guidance: Runtime and migration flows now present runtime-aware manual commands (podman unshare chown for rootless/userns scenarios plus sudo chown) instead of a single static hint.
  • SSH Confirmation UX Fallback: Interactive confirmations now automatically fall back to a plain [Y/n] prompt in SSH sessions to avoid unreadable remote TUI selection rendering.

Fixed

  • Docker Compose Variable Warnings on Non-Linux Hosts: Fixed spurious WARN: "CONSTRUCT_HOST_UID/GID/USERNS_REMAP" variable is not set warnings from Docker Compose when running outside the daemon on macOS. These variables are Linux-only; the compose file now uses ${VAR:-} default-value syntax to silently substitute empty/zero defaults instead of warning.
  • Recurring Config Home Ownership Drift: Fixed repeated ownership drift on ~/.config/construct-cli/home in Linux rootless/userns-remapped Docker/Podman scenarios that caused recurring permission warnings and repair loops.
  • Doctor Runtime-Aware Remediation: construct sys doctor --fix now applies Linux runtime-aware ownership repair paths, including podman unshare first for Podman rootless and sudo fallback (with prompt when needed).
  • Doctor Compose Override Reconciliation: construct sys doctor --fix now regenerates docker-compose.override.yml from current runtime/template settings and validates stale/unsafe user mappings.
  • Doctor Guidance Accuracy: Linux doctor permission diagnostics now report userns-remap context and show runtime-appropriate manual remediation commands.
  • Podman Userns Ownership Re-Drift: Entrypoint startup now skips recursive bind-mount chown -R in remapped-userns mode, preventing fixed ownership from being re-corrupted after startup.
  • Template Path Type Collisions: Migration/init/runtime now self-heal template targets that accidentally exist as directories (for example entrypoint-hash.sh/, agent-patch.sh/) by replacing them with proper files.
  • Home Volume Helper Mount Target Collisions (Linux Docker/Podman): Runtime preparation now also normalizes helper mount targets under ~/.config/construct-cli/home/.config/construct-cli/container (install_user_packages.sh, entrypoint-hash.sh, update-all.sh, agent-patch.sh) so stale directory/file-type collisions are repaired before container setup runs.
  • Fail-Fast Runtime Prep for Mounted Helpers: Runtime now aborts immediately when mounted-helper template preparation fails, instead of continuing into late OCI mount errors during setup.
  • False Rebuild Loop on macOS: Agent startup now auto-clears stale .rebuild_required markers when the container image entrypoint hash is already current, while still blocking when a rebuild is truly required.
  • Release/Tag Consistency: Release workflow now rejects v-prefixed tags and dispatches tap updates with normalized non-prefixed versions.
  • Legacy Linux 1.3.x → 1.4.x Upgrade Safety: Prevented continuation after unresolved ownership drift in config mount paths by failing early with actionable commands, avoiding partial startup/migration behavior.
  • Setup/Spinner Log Peek UX: Pressing Enter to peek logs now shows a clear message when no output has been flushed yet, instead of an empty snapshot.
  • Doctor Daemon Recreate Identity Drift (Linux Rootless/Podman): construct sys doctor --fix daemon recreation now propagates runtime userns identity context (CONSTRUCT_USERNS_REMAP) so recreated daemon runs don’t trigger immediate post-fix ownership drift warnings.
  • Migration Side Effects on Invalid Commands: Startup migration checks now run only for recognized commands/subcommands, preventing typos like construct sys rebuilt from triggering migrations before returning an unknown-command error.

Added

  • Regression Coverage for Rootless Ownership Fixes: Added/updated unit coverage for userns-aware runtime decisions, Podman rootless fix paths, and sudo fallback behavior.
  • Custom Compose Override Opt-In Flag: Added [sandbox].allow_custom_compose_override (default false) for advanced users who intentionally manage docker-compose.override.yml behavior.
  • Regression Tests for Marker/Template Edge Cases: Added tests for stale rebuild-marker auto-clear behavior and directory-collision recovery on mounted template helper paths.
  • Regression Coverage for Home Helper Mount Collisions: Added runtime tests validating repair of file-vs-directory collisions for home/.config/construct-cli/container helper targets.
  • Regression Coverage for Command Gating and Doctor Runtime Env: Added tests for migration gating on unknown subcommands and doctor compose env runtime identity propagation.
  • Dynamic Custom Provider Aliases: construct sys aliases --install now discovers all custom [claude.cc.*] sections from the user's config.toml and automatically installs the corresponding cc-* shell aliases alongside the built-in providers (e.g. [claude.cc.lmstudio]cc-lmstudio).

[1.3.9] - 2026-02-24

Fixed

  • Podman Non-Interactive Setup Runs: Added -T to non-interactive compose run flows used by rebuild/setup/update/package install paths to avoid Podman TTY/conmon startup failures on Linux.
  • SELinux Home-Directory Rebuilds: When SELinux labels are active and Construct is launched from the home directory, compose override generation now falls back container working_dir to /projects to prevent Linux startup failures while preserving the project mount.
  • Broken Gum Binary Fallback: UI now validates that gum is executable (not only present in PATH) before using it, ensuring clean fallback output and readable setup error logs on Linux.

Added

  • Regression Coverage for SELinux Home Fallback: Added Linux runtime test coverage validating /projects fallback working_dir behavior when home-directory SELinux relabeling is skipped.

Changed

  • Linux Startup Identity Propagation: Linux compose runs now propagate host UID:GID into startup env for setup, interactive runs, daemon startup, doctor compose actions, and package/update operations.
  • Entrypoint Ownership Strategy (Linux): Entrypoint now supports host numeric UID:GID ownership/runtime mapping (when provided) while preserving HOME=/home/construct semantics.

Fixed

  • Recurring Linux Home Ownership Drift: Prevented repeated ownership drift on ~/.config/construct-cli/home caused by container startup user/ownership mismatch across Docker and Podman flows.
  • Config Permissions Doctor Coverage: construct sys doctor now reports ownership mismatch (not only writability) for Linux config directories.
  • Codex Startup Permission Loop: Resolved repeated permission-fix prompts triggered by home mount ownership mismatch before ct codex startup.

Added

  • Comprehensive Linux doctor --fix Remediation: Added Linux --fix flow that repairs config ownership/permissions, rebuilds stale/missing image for startup fixes, recycles stale session container, and recreates daemon container.
  • Regression Coverage for Linux Ownership Fixes: Added unit tests for ownership-state detection, linux fix flow, host identity env injection, daemon recreation/session cleanup fix paths, and template guards for host identity propagation.
  • Release Channels (stable / beta): Added runtime.update_channel to config, beta version marker support (VERSION-BETA), and installer channel selection (CHANNEL=beta) for selective prerelease adoption.
  • Semver Prerelease Comparison: Added shared semantic version comparison logic with prerelease support for update checks and migration gating.

[1.3.7] - 2026-02-22

Added

  • Unified Test Summary (make test): Added a combined end-of-run summary that reports unit pass/fail/skip counts, unit package pass/fail counts, integration totals, and overall status.
  • Codex Regression Tests: Added targeted tests for Codex env injection and fallback behavior, including CODEX_HOME presence for Codex runs, WSL fallback env injection when clipboard patching is enabled, and guard checks ensuring non-Codex agents do not inherit Codex-only env vars.
  • Attach Execution Regression Tests: Added focused tests covering attach-session env injection (HOME, PATH, clipboard vars, Codex vars), shell fallback behavior, and Linux host-UID exec mapping.
  • Podman Compose Selection Tests: Added unit coverage to verify command selection prefers podman-compose when present and falls back to podman compose when it is not.

Changed

  • Test Runner Consolidation: make test and make test-ci now run through a shared scripts/test-all.sh flow for consistent unit+integration reporting.
  • Color Controls for Test Summaries: Added status-aware summary coloring (green/yellow/red) with NO_COLOR to disable and FORCE_COLOR=1 to force output coloring.
  • Codex Config Home Resolution: Force Codex runs (standard + daemon) to use CODEX_HOME=/home/construct/.codex so config is loaded from /home/construct/.codex/config.toml instead of project-relative .codex paths under /projects/....
  • Codex Agent Env Injection Refactor: Centralized Codex-specific run/daemon environment injection into dedicated helper functions to reduce drift across execution paths.
  • Attach Execution Path Parity: Attach-to-running-container flows now use interactive exec with the same env protections as normal/daemon runs (construct PATH, HOME=/home/construct, clipboard vars, and agent-specific env injection).
  • Linux Non-Daemon Host UID Mapping: Non-daemon Linux Docker agent runs now apply host UID:GID mapping when exec_as_host_user=true and force HOME=/home/construct.
  • Podman Compose Invocation Strategy: Compose command resolution now uses podman-compose when available and otherwise falls back to podman compose.
  • Strict Network Naming Consistency: Strict mode now uses a consistent construct-net network name across network precreate checks and compose override generation.
  • Ownership Repair Mapping: Runtime and migration ownership repair commands now use numeric uid:gid mapping for broader Linux compatibility.

Fixed

  • Entrypoint HOME/Permissions Regression (Linux Docker): Resolved a regression where entrypoint privilege drop could run as a raw host uid:gid without a passwd entry, causing HOME=/ and repeated permission errors writing ~/.ssh, ~/.bashrc, and setup files.
  • Daemon Session Startup Reliability: Restored reliable startup behavior for daemon-backed runs (construct <agent>) when host UID does not exist inside the container.
  • Linux Config Ownership Auto-Recovery: Hardened migration/runtime permission recovery to attempt non-interactive sudo ownership repair first, with clearer remediation when elevation is unavailable.
  • Host UID Exec Fallback Messaging: Ensured fallback warnings are visible when host UID mapping cannot be used inside the container.
  • Docker Override Host UID Injection: Removed runtime host UID/GID env injection from generated Docker overrides to avoid reintroducing raw-UID startup regressions.
  • Codex Attach Permission Path Drift (Linux): Fixed attach sessions that could miss home/config env injection and fall back to project-relative .codex resolution.
  • Podman Runtime/Compose Mismatch: Fixed runtime detection success followed by compose invocation failure on hosts without podman-compose.
  • Linux Exec Documentation Drift: Updated docs/comments to match current behavior when host UID is missing in container /etc/passwd (keep host mapping and force HOME=/home/construct).

[1.3.2] - 2026-02-20

Added

  • Compose Network Health Check: construct sys doctor now detects stale Docker Compose networks that require recreation (for example after Docker daemon/network default changes such as IPv4/IPv6 toggles).

Fixed

  • One-Step Compose Network Recovery: construct sys doctor --fix now automatically applies targeted compose network recovery by bringing down the compose stack with orphan cleanup and removing the stale compose network(s) when needed.
  • Doctor Fix Regression Safety: Added unit coverage for compose-network fix flow (success, no-op, and failure cases) to prevent regressions.

[1.3.1] - 2026-02-19

Added

  • Issue Template Diagnostics: Added GitHub bug report template fields requiring construct sys doctor output and setup/update logs.
  • Doctor Environment Visibility: construct sys doctor now reports host UID/GID, container user mapping, daemon mode details, daemon mount paths, and latest update log path.
  • Setup/Update Diagnostics: Added richer setup and update diagnostics (UID/GID, PATH, brew/npm/topgrade presence, Homebrew writability checks) plus post-run agent command verification summaries.
  • exec_as_host_user Mode: Added [sandbox].exec_as_host_user (default true) to run Linux Docker exec sessions as host UID:GID for better host file ownership.

Changed

  • Docker Linux User Mapping Strategy: Docker overrides no longer force user: by default on Linux; root-bootstrap remains default and user mapping is now podman-only unless strict mode is explicitly enabled.
  • Strict Non-Root Mode Documentation: Added explicit warnings and limitations for [sandbox].non_root_strict in config template, doctor output, and README.
  • NPM Global Prefix Flow: Setup/update now configure npm global prefix earlier to reduce EACCES failures.

Fixed

  • Agent Installation Continuation: Hardened package install script generation so Homebrew failures do not abort later NPM agent installs.
  • Manual user: Override Recovery (Docker/Linux): Detects unsafe manual user: mappings in docker-compose.override.yml, warns users, and regenerates override safely (except when non_root_strict=true).
  • Entrypoint Shell Setup Noise: Ensured .bashrc is created before alias-source checks to avoid missing-file warnings.
  • Daemon Exec UX: Added targeted hinting when daemon exec exits with command-not-found.
  • Exec User Fallback Safety: When exec_as_host_user=true, Construct now verifies host UID exists in container /etc/passwd; if not, it warns and falls back to the container default user.
  • Stable ns-* Alias Paths: sys aliases --install now preserves stable shim paths (for example /opt/homebrew/bin/...) instead of resolving to versioned Cellar/Caskroom paths.

[1.2.10] - 2026-02-06

Changed

  • Design Docs Mount Model: Updated ARCHITECTURE-DESIGN.md to document the active dual mount behavior: /projects/<folder> for ephemeral runs and /workspaces/<hash>/... for daemon multi-root runs.

Fixed

  • Clipboard Host Command Timeouts (macOS): Added a timeout for osascript image reads in the host clipboard bridge to prevent hangs when reading image clipboard data.
  • Clipboard HTTP Server Timeouts: Added read/write/idle timeouts to the clipboard HTTP server to avoid stuck connections.
  • Codex WSL Fallback Request Timeout: Added curl connection and overall time limits in powershell.exe to avoid indefinite waits when fetching clipboard images.
  • Codex Daemon Workspace Path Mapping: Added /mnt/c/workspaces -> /workspaces aliasing and expanded shim path handling so WSL-style fallback paths resolve correctly in daemon sessions.

[1.2.8] - 2026-02-05

Added

  • Config Defaults Check: construct sys doctor now reports missing default config keys and can append them with --fix (with backup).
  • Construct PATH Export: Entry point now writes a construct-managed PATH profile to stabilize login shells.
  • Non-Daemon PATH Injection: Inject full Construct PATH for setup, container runs, and daemon exec sessions via CONSTRUCT_PATH.
  • PATH Sync Test: Added a unit test to verify PATH parity across Go and template files.
  • Clipboard Patch Flag: Added agents.clipboard_image_patch to enable/disable clipboard image patching and codex WSL clipboard workaround.
  • Brew Package: Added nano to default Homebrew packages.

Changed

  • Safe Defaults Overlay: Defaults are now applied in code when config values are missing.
  • No Config Auto-Merge: Removed automatic config.toml merges and config template hash tracking.

Fixed

  • Agent PATH Parity: Hardcoded PATH across env, entrypoint, compose, and Dockerfile to keep agent binaries available everywhere.
  • Setup + Daemon PATH Injection: Ensure full Construct PATH is injected for setup runs and daemon exec sessions so tools are available everywhere.

[1.2.3] - 2026-02-03

Added

  • Provider Key Passthrough: Always forward common provider API keys into containers, preferring CNSTR_ values and falling back to unprefixed names when empty or missing. Keys: ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, OPENROUTER_API_KEY, ZAI_API_KEY, OPENCODE_API_KEY, HF_TOKEN, KIMI_API_KEY, MINIMAX_API_KEY, MINIMAX_CN_API_KEY.

Fixed

  • Daemon SSH Agent Bridge: Ensure daemon execs initialize the SSH agent proxy and expose SSH_AUTH_SOCK on macOS so agents can access forwarded keys.

[1.2.1] - 2026-01-28

Fixed

  • Daemon Yolo Flag: Avoid injecting --dangerously-skip-permissions when execing into a root-running daemon to prevent permission errors.
  • Daemon User Enforcement (macOS): Run all agent execs inside the daemon as the construct user to avoid root exec on macOS.

Changed

  • Run User (macOS): Force non-daemon agent runs to use the construct user to avoid root exec on macOS.

[1.2.0] - 2026-01-27

Added

  • Daemon Control Commands: Added construct sys daemon subcommands to start, stop, attach, and check status of the background daemon.
  • Multi-Root Daemon Mounts: Support for multiple host root mounts for the daemon with validation, overlap warnings, and deterministic mount hashing.

Changed

  • Workspace Mount Path: Removed the legacy /workspace fallback; project mounts now always use /projects/<folder> via CONSTRUCT_PROJECT_PATH.
  • Compose Env Injection: CONSTRUCT_PROJECT_PATH is now injected automatically for all compose-based commands to keep mount paths consistent.

Fixed

  • Daemon Workdir Mapping: Improved daemon working directory mapping to resolve host paths against validated daemon mounts.

[1.1.3] - 2026-01-26

Added

  • Shared Entrypoint Hash Helper: Introduced a shared script for computing and writing entrypoint hashes, used by both setup and update flows.
  • Agent Patch Script: Centralized clipboard and agent patching into a reusable script.

Changed

  • Update Flow Patching: sys update now runs agent patching and writes the entrypoint hash immediately to avoid redundant setup on next run.
  • Mounted Update Scripts: Update and patch scripts are mounted from the host into the container for faster iteration without rebuilds.
  • Update Fallback Warning: Warns when falling back to /usr/local/bin/update-all.sh and suggests running construct sys refresh.

Fixed

  • Post-Update Double Setup: Avoids re-running full setup after updates when only patching is needed.
  • Rebuild Template Duplication: construct sys rebuild no longer copies container templates twice during automatic migration + refresh.
  • Daemon Mount Working Dir: Agent exec in the daemon now maps the host working directory to the correct mounted path.

[1.1.0] - 2026-01-25

Improved

  • Multiple performance optimizations where implemented to make the CLI faster and more efficient. Now it's fast. Really fast.

Added

  • Daemon Auto-Start Service: Added sys daemon install, sys daemon uninstall, and sys daemon status for managing a background daemon service (systemd) that can auto-start on login/boot.
  • Daemon Auto-Start Config: New [daemon] auto_start setting to start the daemon on first agent run for faster subsequent startups.

Changed

  • Faster Runtime Detection: Runtime detection now checks container, podman, and docker in parallel to reduce startup latency on multi-runtime systems.
  • Daemon Exec Fast Path: When a daemon container is running, agents run via exec instead of compose run for much faster startup.
  • Compose Override Caching: Docker compose override generation is cached to avoid unnecessary regeneration on repeated runs.
  • Parallel Runtime Detection: Checks container, podman, and docker concurrently to cut detection latency by 0.5-1s.
  • Daemon Exec Path: Agent runs reuse the warm daemon container for 2.5-7s faster startup.
  • Entrypoint Caching: Skips expensive clipboard and agent patching when entrypoint hash is unchanged (200-800ms saved).
  • Preemptive Services: Clipboard server and SSH bridge already start asynchronously; no added latency from sequential startup.

Fixed

  • Daemon Staleness Guard: Detect stale daemon containers (old image) and fall back to normal startup with clear guidance to restart the daemon.
  • Daemon Shell Exec: construct sys shell now execs a default shell when attaching to a running daemon, preventing empty exec calls.
  • Post-Update Entrypoint Patching: Clearing the entrypoint hash after updates ensures new agents get patched correctly.

[1.0.1] - 2026-01-21

Fixed

  • OrbStack/Podman Sudo Compatibility: Fixed package install/update flows (sys packages --install and sys update) failing with "PAM account management error" in environments where sudo is unavailable or misconfigured (OrbStack, rootless Podman, minimal containers). Thanks @KingMob for the bug report.
    • Install scripts now detect if running as root (no sudo needed) or test sudo availability before use
    • Gracefully skips privileged apt operations when sudo unavailable instead of failing
    • Applies to both install_user_packages.sh (generated) and update-all.sh (template)

[1.0.0] - 2026-01-20

Added

  • Production Ready: Marked Construct CLI as production ready.

[0.15.11] - 2026-01-18

Added

  • SELinux Label Control: Added sandbox.selinux_labels config to enable, disable, or auto-detect SELinux mount labels.
  • Doctor Ownership Check: Added a Linux/WSL config permissions check with a chown fix suggestion for ~/.config/construct-cli.
  • Automatic Config Permission Fix: On Linux/WSL, automatically detect and fix config directory ownership issues before runtime preparation, with clear messaging and user confirmation before running sudo.
  • Simple Progress Mode: Added dot-based progress output for non-TTY environments and when CONSTRUCT_SIMPLE_PROGRESS=1 is set.
  • Podman Rootless Support: On Linux, container now runs as user (not root) for proper Podman rootless compatibility. macOS continues to use root with gosu drop.
  • Truecolor Support: Forward host COLORTERM to container, defaulting to truecolor when unset. Fixes washed-out colors over SSH.

Fixed

  • SELinux Home Relabeling: Skip :z labels when running from the home directory to avoid relabel errors.
  • Config Write Guidance: Emit clearer permission warnings when config-generated files cannot be written.

[0.15.2] - 2026-01-15

Added

  • Goose CLI: Added goose agent, with first-run configure guidance.
  • Droid CLI: Added droid agent.
  • Kilo Code CLI: Added kilocode agent.
  • Yolo Mode Config: Added [agents] config to enable yolo flags per-agent or globally in config.toml.
  • Agent Browser: Added agent-browser to default npm packages and configured its post-install dependency setup. Headless browser automation CLI for AI agents. Fast Rust CLI with Node.js fallback. No MCP required.
  • LiteLLM: Added litellm to default pip packages; open-source LLM gateway/SDK with a unified OpenAI-compatible API across 100+ providers, with cost/error handling and failover.
  • Post-Install Hooks: Added [post_install].commands to packages.toml, executed after all package managers finish.

[0.14.3] - 2026-01-13

Added

  • Podman Compose: Added podman-compose to default brew packages in packages.toml.

[0.14.2] - 2026-01-11

Changed

  • Non-Sandboxed Aliases: ns- entries are now shell functions in the RC file, forwarding flags and args without installing extra files.
  • Agent Rules Bulk Replace: sys agents-md now supports replacing all agent rules at once with a single pasted prompt, including Copilot frontmatter.

[0.13.2] - 2026-01-08

Fixed

  • Stale Entrypoint Detection: Detect and prompt rebuild when the container image entrypoint is out of date to avoid repeated setup spinners.

[0.13.1] - 2026-01-07

Added

  • Pi Coding Agent: Added support for Pi Coding Agent (@mariozechner/pi-coding-agent).
    • Config mounted at /home/construct/.pi
    • Added to default npm packages in packages.toml
    • Auto-creates ~/.pi/agent/auth.json (empty object) on first run

[0.12.0] - 2026-01-07

Changed

  • Container User Authentication: Set fixed password "construct" for the construct user to enable sudo access when running commands inside sys shell.
    • All automated operations (init, build, migrate, update, rebuild) remain completely passwordless via NOPASSWD sudoers configuration

[0.11.6] - 2026-01-06

Added

  • Static Site Generators: Added Hugo and Zola to default brew packages in packages.toml.
  • Ruby Gems Support: Added support for installing Ruby gems via a new [gems] section in packages.toml.
    • Includes jekyll as default gem
    • Integrated with package installation scripts and Topgrade updates
  • Host System Info: Added new "Host System" check to sys doctor to display OS, architecture, and version details.
    • Shows macOS version and kernel on Apple/Intel Macs
    • Shows Linux distribution name and kernel version
    • Detects and displays WSL environment on Windows

Fixed

  • Doctor SSH Keys Display: Fixed SSH key listing in sys doctor to exclude non-key files (known_hosts, config, agent.sock, etc.).
    • Renamed check from "Local SSH Keys" to "Construct SSH Keys" for clarity
    • Keys shown are those stored in Construct's SSH directory (whether imported or generated)

[0.11.5] - 2026-01-05

Fixed

  • Agent PATH Visibility: Fixed issue where agents (particularly codex) couldn't see binaries in PATH when running commands via their Bash tools.
    • Root cause: /etc/profile was resetting PATH when bash spawned, wiping out Homebrew paths
    • Patched /etc/profile in entrypoint.sh (idempotent, no image rebuild needed)
    • Centralized PATH definition in internal/env/env.go (DRY principle)
    • Synchronized PATH configuration across docker-compose.yml, entrypoint.sh, and env.go
    • Ensures all agent subprocesses inherit full PATH including Linuxbrew, Cargo, npm-global, etc.
  • CT Symlink Stability: ct now targets the stable Homebrew path on macOS/Linux, and sys doctor self-heals broken Cellar-based symlinks.
  • Agent Detection in Doctor: Agent install check now verifies binaries inside the container so Homebrew/NPM-based installs are detected correctly.
  • Rebuild Help Clarity: sys rebuild help text now explicitly mentions it runs migrate before rebuilding.

[0.11.3] - 2026-01-03

Added

  • md-over-here: Added md-over-here to default brew packages in packages.toml template.
  • Brew Installation Detection: self-update and update notifications now detect if the CLI was installed via Homebrew and provide appropriate instructions (brew upgrade estebanforge/tap/construct-cli) instead of attempting a manual binary overwrite.

Fixed

  • Package Installation Reliability: Improved package install flow (sys packages --install) to use run --rm instead of exec, allowing it to work correctly even if The Construct is not already running.
  • Initialization Consistency: Updated sys init and sys rebuild to always perform a full migration (syncing config and templates) before building the image.
  • Migration Messaging: Clarified migration messages to indicate when an image is "marked for rebuild" versus being actively rebuilt.
  • Container Rebuild Reliability: improved sys rebuild to force fresh container images.
    • Now stops and removes running containers and images before rebuilding (not just marking for rebuild)
    • Added support for macOS 26+ native container runtime management.
  • Container Image Optimization: Updated base image to a more stable version.
    • Leaner base image means faster rebuilds and reduced storage footprint.
  • Template Synchronization: Improved migration flow (sys config --migrate) to ensure binary rebuilds are correctly triggered when embedded templates change.
    • Automatic removal of old Docker image (forces rebuild with new Dockerfile)
    • New hash-based template change detection (more reliable than version checks)

[0.11.2] - 2026-01-03

Fixed

  • Version-Independent Aliases: Fixed Homebrew alias installation to use version-independent paths.
    • Aliases now use construct command instead of hardcoded Cellar paths (e.g., /opt/homebrew/Cellar/construct-cli/0.11.0/bin/construct)
    • Aliases remain functional after Homebrew updates without reinstallation
    • Also improves portability for curl-based installations and local builds
  • Chromium Multi-Arch Support: Fixed Puppeteer-based tools (url-to-markdown, browser automation) on arm64 hosts.
    • Installed system Chromium from Debian repos (automatically matches container architecture)
    • Configured Puppeteer to use system Chromium instead of downloading x86-64 version
    • Prevents "Dynamic loader not found: /lib64/ld-linux-x86-64.so.2" errors on Apple Silicon/arm64
    • Includes all required Chromium dependencies (fonts, GTK, NSS, etc.)

[0.11.1] - 2026-01-02

Added

  • Homebrew Installation: Added support for installing via Homebrew (Linux & macOS) using brew install EstebanForge/tap/construct-cli.
  • Topgrade Integration: Automated package updates inside Construct via Topgrade
    • Ensures all system packages, language tools, and development utilities stay current
    • Seamless integration with Construct's containerized environment
    • Configurable via packages.toml for custom update policies
  • Worktrunk.dev Support: Default installation and usage of Worktrunk for simultaneous agent collaboration
    • Enables multiple AI agents to work on the same codebase without conflicts
    • Provides intelligent workspace isolation and synchronization
    • Configured by default for optimal multi-agent workflows

[0.10.1] - 2025-12-31

Added

  • Cargo Package Support: users can now install Rust-based tools and utilities via a new [cargo] section in packages.toml.
  • Centralized Debugging: unified all debug logging under the CONSTRUCT_DEBUG environment variable.
    • When enabled (CONSTRUCT_DEBUG=1), logs are written to ~/.config/construct-cli/logs/ on the host.
    • Container-side logs (like powershell.exe and clipboard-x11-sync) are redirected to /tmp/ for guaranteed visibility and write access.
    • Replaced legacy CONSTRUCT_CLIPBOARD_LOG with the new unified system.
  • Non-Sandboxed Agent Aliases: sys aliases --install now creates ns-* prefixed aliases for agents found in PATH.
    • Example: ns-claude, ns-gemini, etc. run agents directly without Construct sandbox
    • Useful for running agents with full host access when needed
  • Alias Re-installation: sys aliases --update now supports updating existing installations.
    • Detects existing aliases and offers to re-install with gum confirmation prompt
    • Automatically removes old alias block before installing fresh
    • Adds missing ns-* aliases when updating existing installations
  • Shell Config Backups: automatic timestamped backups before modifying shell config files.
    • Creates .backup-YYYYMMDD-HHMMSS files before any changes
    • Applies to both .zshrc, .bashrc, and .bash_profile
    • Protects users from critical shell configuration errors

Optimized

  • Sandbox Isolation: sanitized the PATH environment variable across all templates to prevent host-side directory leakage into the sandbox.
  • Streamlined Installation:
    • Eliminated redundant package installations by removing duplicates between APT and Homebrew phases.
    • Added DEBIAN_FRONTEND=noninteractive to silence UI dialogs during container setup.
  • Improved Migration Experience: refined version detection messaging to accurately distinguish between binary upgrades, downgrades, and template-only syncs.

Fixed

  • Codex Image Paste: restored full image support for OpenAI Codex CLI agent after its internal mechanism changed from direct binary data to path-based references.
    • Fixed Ctrl+V shortcut by shimming the WSL clipboard fallback with a smart powershell.exe emulator that returns workspace-compatible paths.
    • Implemented /mnt/c/projects and /mnt/c/tmp symlinks within the container to ensure Codex path resolution works seamlessly across all host platforms.
    • Improved "New Flow" paste detection by allowing Codex to receive raw image paths instead of multimodal @path references.
  • Self-Update Reliability: fixed a confusing state where self-update would report an upgrade from 0.3.0 due to intentional version file deletion.
  • Clipboard Image Paste: Centralized file-based paste agent list (gemini, qwen, codex) into a single constant to prevent drift.
  • Container Rebuild Reliability: improved sys rebuild to force fresh container images.
    • Now stops and removes running containers and images before rebuilding (not just marking for rebuild)
    • Added --no-cache flag to build command to bypass Docker layer cache
    • Added support for Apple container runtime (macOS 26+) alongside Docker and Podman
  • SSH Key Prioritization: Enhanced SSH configuration management to ensure correct key selection order for all hosts.
    • Auto-generates ~/.ssh/config with SSH agent support and key prioritization (default and personal keys tried first).
    • Applies to all SSH connections (GitHub, GitLab, private servers, etc.), not just specific hosts.
    • Adds RSA key algorithm support (PubkeyAcceptedAlgorithms +ssh-rsa) for legacy servers.
    • Falls back to physical SSH keys if present after trying agent keys.
    • Automatic updates on CLI upgrade unless user opts out with construct-managed: false flag.
    • Creates backup (~/.ssh/config.backup) before each update.

[0.10.0] - 2025-12-30

Added

  • User-Defined Package Management: Customize your sandbox environment with packages.toml configuration.
    • Install additional APT, Homebrew, NPM, and PIP packages beyond the defaults.
    • Easy activation of specialized version managers: NVM, PHPBrew, Nix, Asdf, Mise, VMR, Volta, and Bun.
    • New construct sys packages command to quickly edit package configuration.
    • New package install command construct sys packages --install to apply package changes to running containers without restart.
    • Package configuration persists across updates and environments.
  • Dynamic Project Mount Paths: Agents now mount the current host directory to /projects/<folder_name> instead of static /workspace.
    • Improves agent contextual awareness and long-term memory.
    • Dynamically calculates mount path based on current directory name.
    • Automatically updates all internal templates (Dockerfile, docker-compose) and helper scripts.
    • Preserves compatibility with non-standard folder names (spaces, special characters).

Optimized

  • Faster Docker Builds: Streamlined base image for significantly faster build times.
    • Reduced Dockerfile to only essential build-time dependencies.
    • Moved most packages to runtime installation during first container start.
    • Critical packages verified at startup to ensure reliability.
    • Leaner base image means faster rebuilds and reduced storage footprint.

Improved

  • Enhanced Code Quality: Internal refactoring and expanded test coverage.
    • Comprehensive clipboard functionality test suite.
    • Updated linter configuration for stricter code quality enforcement.
    • Improved error handling and package naming conventions.
  • Package Management Refinements:
    • PHP extensions (PCOV) now installed via Homebrew tap instead of hardcoded scripts for better maintainability.
    • Node.js version unlocked from node@24 to node for automatic latest stable version.
    • Native Node.js module compilation support with automatic compiler symlinks (g++-11g++-14).
    • Removed conflicting bash-completion package to prevent installation failures.

[0.9.1] - 2025-12-27

Changed

  • Clipboard Image Pasting: Fixed image pasting across agents with image-first handling, normalization/resize, and @path only for Gemini and Qwen.

[0.9.0] - 2025-12-26

Optimized

  • Reliable Agent Installation: Overhauled entrypoint.sh to prevent partial installation failures.
    • Split large brew install commands into categorized blocks (Core, Dev, Languages, Linters, Web/Build).
    • Unified npm package installation into a single, efficient command.
    • Implemented hash-based change detection for entrypoint.sh to automatically trigger re-installation when scripts are updated.
  • Smart Runtime Configuration:
    • Optimized Linux runtime detection to avoid unnecessary user ID mapping for UID 1000 users, enabling proper permission fixups.
    • Improved migration flow (sys config --migrate) to ensure binary rebuilds are correctly triggered when embedded templates change.

Fixed

  • Permission Issues: Fixed permission errors during initial setup by allowing the container to start as root for volume ownership fixes before dropping privileges.
  • Missing Tools: Resolved issue where tools like golangci-lint could be missing due to silent installation failures.
  • Build Caching: Fixed an issue where Docker build cache would persist stale entrypoint.sh versions, preventing updates from being applied.

[0.8.0] - 2025-12-25

Added

  • Git Identity Inheritance: Automatically propagates host git identity (user.name and user.email) to the container environment.
    • Solves commit attribution issues inside the container.
    • Enabled by default, configurable via propagate_git_identity in config.toml.
    • Safely injects values as GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, etc., without mounting potentially incompatible host git configs.
  • Improved Shell Prompt: Container hostname is now set to sandbox (was random ID) for a cleaner prompt experience: construct@sandbox:/workspace$.
  • Headless Login Bridge: New construct sys login-bridge command to enable local browser login callbacks for headless-unfriendly agents (Codex, OpenCode with OpenAI GPT or Google Gemini).
    • Runs until interrupted and forwards localhost OAuth callbacks into the container.

[0.7.0] - 2025-12-24

Added

  • Global Agent Rules Management: New construct sys agents-md command to manage rules for all supported agents in one place.
    • Interactive selection UI powered by gum.
    • Supports: Gemini, Qwen, OpenCode, Claude, Codex, Copilot, and Cline.
    • Auto-Initialization: Automatically creates missing rules files and parent directories on demand.
    • Open All: "Open all Agent Rules" option to quickly edit all supported agent rules at once.
    • Context-Aware Expansion: Automatically resolves ~ to the Construct persistent home directory (~/.config/construct-cli/home/) so rules are correctly applied within the container environment.
    • Fallback UI: Seamlessly falls back to a numeric menu if gum is not available.

Changed

  • Generic Workspace Path: Renamed the internal container mount point and working directory from /app to /workspace.
    • This reflects the project's evolution into a general-purpose sandbox for any CLI agent, not just those used for "app" development.
    • Automatically updates all embedded templates (Dockerfile, docker-compose) and runtime override generation.
    • Updated design and user documentation to reflect the new path.

Fixed

  • Improved UI grammar and messaging consistency across system commands.
  • Updated .gitignore to prevent tracking of local debug logs while preserving them for development.

[0.6.0] - 2025-12-23

Added

  • Secure SSH Agent Forwarding: Automatic detection and secure mounting of the local SSH agent into the container.
    • Supports Linux and macOS (OrbStack and Docker Desktop).
    • Implements a TCP-to-Unix bridge for robust macOS/OrbStack connectivity, bypassing common permission and socket quirks.
    • Fully configurable via forward_ssh_agent in config.toml (enabled by default).
  • SSH Key Import System: New construct sys ssh-import command to securely bring host keys into Construct.
    • Interactive multi-select UI powered by gum.
    • Automatic permission fixing (0600) and matching .pub/known_hosts support.
    • Smart logic to skip selection if only one key is found.
  • Config Restoration: New construct sys config --restore command to immediately recover from configuration backups.
  • Shell Productivity Enhancements:
    • Automatic management of .bash_aliases inside the container.
    • Standard aliases included: ll, la, l, and color-coded ls/grep.
    • Zsh-like navigation shortcuts: .., ..., .....
  • Improved Diagnostics: construct sys doctor now reports SSH Agent connectivity and lists imported local keys.

Changed

  • Non-Destructive Migration: Redesigned migration flow (sys config --migrate) logic to be strictly additive.
    • Preserves all user comments and formatting.
    • Automatically identifies and preserves custom Claude Code aliases, moving them to a dedicated "User-defined" section.
    • Prevents TOML section duplication.
  • Unified macOS Magic Path: Simplified container orchestration to use industry-standard magic paths for macOS container bridges.
  • Entrypoint Reliability: Container now starts as root to perform critical permission fixes (SSH sockets, home directory ownership) before dropping to the non-privileged construct user via gosu.

Fixed

  • Fixed "Permission denied" errors when accessing mounted SSH sockets on macOS.
  • Fixed "Communication failed" errors by implementing a Go-native TCP bridge for macOS.
  • Full compliance with strict golangci-lint rules, improving overall code robustness and error reporting.
  • Simplified config.toml template to reduce clutter and minimize merge conflicts.

[0.5.0] - 2025-12-22

Added

  • Self-Update Command: construct sys self-update now downloads and installs the latest version directly from GitHub releases
    • Automatic platform detection (darwin/linux, amd64/arm64)
    • Prompts for confirmation when already on latest version
    • Backup and restore on failure
  • Smart Config Migration: Config merging now only runs when template structure actually changes
    • Hash-based detection of config template changes
    • Skips unnecessary backup/merge cycles on patch version updates
    • Container templates still update on every version bump (for bug fixes)

Changed

  • Simplified Update Check: Version checking now uses lightweight VERSION file instead of GitHub API
    • Faster checks, no API rate limits
    • Download URLs constructed directly from version string
  • Install Script Improvements:
    • Checks for existing installation before downloading
    • Prompts user if same version already installed
    • Uses VERSION file for remote version lookup
    • Reads local .version file first, falls back to querying binary
    • New FORCE=1 env var to skip version check

Fixed

  • Version command no longer triggers config initialization
  • Update check now uses proper semver comparison (was treating any version difference as "update available")

[0.4.1] - 2025-12-22

Added

  • make lint now runs golangci-lint for parity with CI checks.

Fixed

  • Migration merge now skips incompatible types and validates TOML, preventing corrupted config files after upgrades.
  • Improved error handling and warnings for clipboard server, daemon UI rendering, log cleanup, and shell alias flows.

[0.4.0] - 2025-12-21

Added

  • Automatic Migration System: Seamless upgrades with zero user intervention
    • Version tracking via .version file in config directory
    • Automatic detection of version changes on startup
    • Smart detection of 0.3.0 → 0.4.0 upgrades (handles missing version file)
    • Improved Config Merging: New template-first merge logic that preserves comments and layout while syncing supported user values.
    • Automatic replacement of container templates with new versions
    • Automatic removal of old Docker image (forces rebuild with new Dockerfile)
    • Persistent volumes preserved (agents, packages, configurations)
    • Backup of old config created during migration (config.toml.backup)
    • Clear migration progress output with success/error reporting
    • New migration command construct sys config --migrate for manual config/template refresh (useful for debugging)
  • Expanded Container Toolchain: Added comprehensive language support to the sandbox:
    • Languages: Rust, Go, Java (OpenJDK), Kotlin, Swift, Zig, Ruby, PHP, Dart, Perl, Erlang, COBOL.
    • Build Tools: Ninja, Gradle, UV, Composer.
    • Utilities: jq, fastmod, tailwindcss CLI.
  • Cross-Boundary Clipboard Bridge: Unified host-container clipboard for seamless text and image pasting
    • Secure Host-Wrapper Bridge: A secure Go HTTP server on the host provides authenticated clipboard access to the container via ephemeral tokens.
    • Universal Image Support: Robust support for pasting images directly into agents across macOS, Linux, and Windows (WSL).
    • Multi-Agent Interception: Automatic shimming of xclip, xsel, and wl-paste inside the container to redirect calls to the bridge.
    • Dependency Patching: entrypoint.sh automatically finds and shims nested clipboard binaries in node_modules (fixes Gemini/Qwen clipboardy issues).
    • Tool Emulation: Fake osascript shim allows agents to use macOS-native "save image" logic while running on Linux.
    • Smart Path Fallback: Automatically saves host images to .gemini-clipboard/ and returns multimodal @path references for agents expecting text.
    • Runtime Code Patching: Automatically bypasses agent-level process.platform checks that would otherwise disable image support on Linux.
    • Zero Config: Transparently handles all platform-specific clipboard complexities with no user setup required.
  • Development Installation Scripts: New tools for local testing and debugging
    • install-local.sh: Full-featured install with automatic backups and verification (defaults to ~/.local/bin)
    • dev-install.sh: Lightning-fast dev install for rapid iteration (no confirmations, no backups)
    • uninstall-local.sh: Safe uninstall with backup restoration options
    • New Makefile targets: install-local, install-dev, uninstall-local
  • Comprehensive Development Documentation
    • New DEVELOPMENT.md with complete development workflow guide
    • Detailed installation methods, testing procedures, and troubleshooting
    • VS Code tasks configuration examples

Changed

  • Help Text Alignment: All CLI help descriptions now properly aligned for better readability
    • Aligned # comments across all help sections (sys, network, daemon, cc)
    • Consistent formatting in main help, network help, daemon help, and provider help
  • Installation Defaults: Local installation scripts now default to ~/.local/bin (no sudo required)
    • Users can override with INSTALL_DIR environment variable
    • Improved user experience for development workflows
  • Clipboard Integration Architecture: Upgraded from manual directory sync to a secure, real-time HTTP bridge.
    • Transparent redirection of all terminal clipboard tools to the host system.
    • Integrated support for multimodal agents (Claude, Gemini, Qwen).

Fixed

  • Runtime Package Conflicts: Resolved naming collision in internal/agent/runner.go
    • Standard library runtime package now imported as runtimepkg
    • All runtime function calls updated to use proper package reference
  • Version Location: Updated version references in documentation to reflect actual location
    • Version now correctly documented as internal/constants/constants.go (not main.go)

Documentation

  • Updated README.md with cross-boundary clipboard bridge instructions
  • Added DEVELOPMENT.md with development workflow and testing guide
  • Updated AGENTS.md with correct file paths and new clipboard features
  • Updated ARCHITECTURE-DESIGN.md with detailed clipboard bridge architecture

[0.3.0] - 2025-12-18

Added

  • Core CLI Framework: Single-binary CLI for running AI agents in isolated containers
    • Runtime auto-detection: macOS containerpodmandocker
    • Embedded templates (Dockerfile, docker-compose, entrypoint, configs)
    • Self-building on first run with construct sys init
  • Network Isolation: Three modes for security
    • permissive: Full network access (default)
    • strict: Custom network with domain/IP allowlist/blocklist
    • offline: No network access at all
    • Live UFW rule application while agents are running
  • Agent Support: Pre-configured support for multiple AI agents
    • Claude Code, Gemini CLI, Qwen Code, GitHub Copilot CLI
    • OpenCode, Cline, OpenAI Codex
    • Agent configuration directories mounted from host
  • System Commands (construct sys):
    • init: Initialize environment and install agents
    • update: Update agents to latest versions
    • reset: Delete volumes and reinstall
    • shell: Interactive shell with all agents
    • aliases --install: Install agent aliases to host shell
    • version: Show version
    • config: Open config in editor
    • agents: List supported agents
    • doctor: System health checks
    • self-update: Update construct binary
    • check-update: Check for available updates
  • Network Commands (construct network):
    • allow <domain|ip>: Add to allowlist
    • block <domain|ip>: Add to blocklist
    • remove <domain|ip>: Remove rule
    • list: Show all rules
    • status: Show active UFW status
    • clear: Clear all rules
  • Daemon Mode (construct sys daemon):
    • start: Start background container
    • stop: Stop background container
    • attach: Attach to running daemon
    • status: Show daemon status
  • Claude Provider Aliases (construct cc):
    • Support for alternative Claude-compatible API endpoints
    • Providers: Z.AI, MiniMax, Kimi, Qwen, Mimo
    • Environment variable expansion and automatic reset
    • Configuration in config.toml under [claude.cc.*]
  • Persistent Volumes: Agent installs persist across runs
    • construct-agents: Agent binaries and configs
    • construct-packages: Homebrew packages
    • Ephemeral containers (--rm) for clean host system
  • Auto-Update System:
    • Passive background update checks (configurable interval)
    • Desktop notifications for available updates
    • Self-update command for binary upgrades
    • Version checking against GitHub releases
  • Platform Support:
    • macOS (Intel and Apple Silicon)
    • Linux (amd64 and arm64)
    • SELinux support with automatic :z labels
    • Linux UID/GID mapping in generated override files
    • WSL2 compatibility

Configuration

  • Main config at ~/.config/construct-cli/config.toml
  • Runtime engine selection (auto, container, podman, docker)
  • Network mode configuration with domain/IP lists
  • Provider-specific environment variables
  • Auto-update check settings

Infrastructure

  • Makefile with comprehensive build targets
  • Cross-compilation support for all platforms
  • Unit and integration test suites
  • CI-ready lint and test commands
  • GitHub Actions integration ready
  • Install/uninstall scripts

Documentation

  • Comprehensive README.md with examples
  • ARCHITECTURE-DESIGN.md with architecture details
  • AGENTS.md for code agents working on the project
  • CONTRIBUTING.md for contributors
  • LICENSE.md (MIT)