Skip to content

Releases: EstebanForge/construct-cli

The Construct CLI 1.9.4

20 Jun 22:03

Choose a tag to compare

[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.

The Construct CLI 1.9.3

18 Jun 17:43

Choose a tag to compare

[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.

The Construct CLI 1.9.2

18 Jun 14:05

Choose a tag to compare

[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.

The Construct CLI 1.9.1

16 Jun 01:02

Choose a tag to compare

[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.

The Construct CLI 1.9.0

10 Jun 17:54

Choose a tag to compare

[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.

The Construct CLI 1.8.15

04 Jun 03:09

Choose a tag to compare

[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).

The Construct CLI 1.8.14

02 Jun 12:53

Choose a tag to compare

[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.

The Construct CLI 1.8.13

01 Jun 19:59

Choose a tag to compare

[1.8.13] - 2026-06-01

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.

The Construct CLI 1.8.12

20 May 21:03

Choose a tag to compare

[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.

The Construct CLI 1.8.11

20 May 17:37

Choose a tag to compare

[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.