Skip to content

Releases: hefgi/ecluse

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 15 Jun 10:11
0068f0e

Fixed

  • tmux startup sources .env, .env.local, and .env.ecluse with an explicit ./ prefix (. './.env'). zsh's POSIX . builtin does not search cwd unless . is in $PATH, so the previous bare . '.env' form silently failed on zsh (the default macOS shell), leaving services in tmux windows without the per-slot env. (#27)
  • parse_env_file now strips a matched outer pair of "..." or '...' from dotenv values. The per-slug preamble previously echoed ONYX_ENVIRONMENT='"development"', leaking the literal double-quote characters into the runtime env and breaking strict consumers (e.g. zod z.literal("development")). Unquoted values like postgres://x:5433/db?a=b are unaffected. (#28)

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 13 Jun 14:02
00fe951

Added

  • Pending sessions: up/down reserve the session and release the state lock during provisioning/teardown, so parallel sessions never serialize on image pulls or hooks. ls shows (pending) (and "status" in --json), warns about entries pending >15 minutes after a crashed command, and down <slug> recovers them. Concurrent finalizes are guarded by per-operation ownership tokens — deleted sessions are never resurrected by a racing command.
  • Rollback-on-failure is enforced by an undo-stack guard: any bring_up failure tears down exactly what was created (containers, overlays, worktree, spawned services), in reverse order. Failed re-ups of existing sessions keep their data volumes.
  • PID files record the process start token; a recycled PID is never signaled, never attributed by whose-pid, and reports as down. Containers are matched by compose project label instead of name substrings.
  • tmux services run as their window's own process with recorded pane PIDs; commands that exit within ~1.5s fail ecluse up with the exit status and last output. Dead panes are kept for inspection; window shell is a plain shell with the session env.
  • publish_primary on [[services]]: explicit control over primary-port publication (the implicit "extra_port with container_port suppresses the primary" rule is deprecated; validate warns).
  • Docker-gated end-to-end test suite (tests/docker_e2e.rs): lifecycle, rollback, multi-compose teardown, container mode — runs wherever a docker daemon is available, skips elsewhere.

Changed

  • extra_ports use the same per-slot spacing as primary ports (base + slot*slot_stride) and are probed before startup: occupied extras are a hard error under strict_port and a warning otherwise. With slot_stride = 1 (the default) allocations are unchanged.
  • up --force only kills processes owned by the session (verified via pid files/tmux panes), with TERM→KILL escalation; unowned listeners produce a warning naming the PID.
  • Teardown dispatches on the session's recorded mode, so changing mode in .ecluse.toml no longer strands containers of existing sessions.
  • ModeHandler::bring_up takes a BringUpRequest; container/hybrid share the docker-startup, worktree, port-allocation and spawn building blocks (was ~100 duplicated lines).
  • Session state records explicit (compose, overlay) pairs; teardown no longer reconstructs compose paths from overlay filenames (ambiguous for hyphenated slugs). Legacy state files still tear down via the old path.
  • Branch handling: up <branch> tracks origin/<branch> when it only exists on the remote (instead of forking a same-named branch from HEAD), and refuses to resume a slug that belongs to a different branch.
  • The tmux env preamble is per-slug (.ecluse/preambles/<slug>.sh); a shared file leaked ports between parallel sessions.

Fixed

  • kill paths signal the whole process group with TERM→KILL grace — service children no longer survive ecluse down holding their port.
  • A failing service spawn cleans up the services already spawned instead of orphaning them.
  • ecluse sync no longer accepts any directory whose path merely contains the slug; the cwd must be a linked worktree of the repo. It also refuses sessions that are mid-operation.
  • worktree remove verifies the directory is actually gone instead of treating git worktree prune success as removal success.
  • Shared-lock acquisition reads the real state when state.lock is missing (previously reported "no sessions" while sessions ran); ls no longer panics on malformed timestamps; .env.ecluse is parsed by a single shared parser everywhere.
  • Examples and README migrated off the deprecated on_up/on_down hook names (the README example also ran migrations in pre_up, before any env exists; now post_up).

v0.2.17

Choose a tag to compare

@github-actions github-actions released this 10 Jun 22:59
b231a61

Added

  • inherit_env entries now support a per-file mode (symlink or copy). The default remains symlink — existing configs are unchanged — but each entry may opt into the object form { file = "...", mode = "copy" } to get a one-time copy from the root file. Copy mode lets a worktree edit its own .env.local (e.g. flip AUTH_ENABLED=false) without affecting the root file or other parallel worktrees. On subsequent ecluse up runs, copied files are preserved verbatim — never re-copied — so per-worktree edits stick. Stale symlinks left over from a prior symlink configuration are replaced with a fresh copy.

v0.2.16

Choose a tag to compare

@github-actions github-actions released this 09 Jun 15:43
5d8ea4c

Added

  • ecluse whose-pid <pid> command: reverse-maps a PID to the ecluse session that owns it. Checks .ecluse/pids/<slug>/*.pid files and walks descendants up to 5 levels, so task/make-spawned children resolve to the right session. For tmux-managed sessions also checks pane PIDs and their subtrees. Use --json for machine-readable output. Exit code: 0 if owned, 1 if not. Required before any manual kill of a process on an ecluse-allocated port — prevents parallel agents from killing each other's services.
  • slot_stride top-level config field: spacing between ports of adjacent slots. Defaults to 1 (unchanged behaviour). Set to 10 to give slots 1/2/3 ports base+10, base+20, base+30 instead of base+1, base+2, base+3. Wider spacing reduces the chance that an agent misidentifies an adjacent-slot port as its own stale leftover. find_free_port and the adjacent-gap validation both account for stride.

Changed

  • skills/ecluse/SKILL.md adds a "Killing services safely" section: forbids raw lsof -ti | xargs kill of ecluse-allocated ports, mandates ecluse down --keep-worktree + ecluse up --reuse-worktree as the canonical reset, and requires ecluse whose-pid ownership verification before any manual kill. Also warns that external task runners (task, make, npm run) bypass .env.ecluse and should be replaced with command = "..." entries in .ecluse.toml.

Fixed

  • ecluse up on resume (second invocation against an existing session) now honors the worktree_path recorded in state.json instead of recomputing the default <root>/<worktree_dir>/<slug> location. Sessions whose worktree lives outside .ecluse/worktrees/ — e.g. those auto-registered from a sibling git worktree the user created manually — no longer fail with "worktree not found at ; remove --reuse-worktree or run ecluse up without it" on the second ecluse up.
  • ecluse status now always reports the port allocated by ecluse (from state.json / .env.ecluse), never a port that happens to be open in the service's process subtree. Previously, if a child process in the service tree was listening on a different port (e.g. a task-spawned helper that bound 2080 while the actual service was on 11734), status reported that child's port — making the table contradict .env.ecluse and hiding the real problem. Health is now computed by verifying that the service process subtree owns the expected port; if it doesn't, the service shows as down. Affects native services in tmux and nohup process_manager modes.

v0.2.15

Choose a tag to compare

@github-actions github-actions released this 08 Jun 16:38
9e4057c

Added

  • pre_spawn lifecycle hook — runs after the worktree and slot are allocated but before any services are started. Useful for provisioning secrets, seeding databases, or any setup that must happen before the first process starts. Receives the same env vars as post_up.
  • host_port field on [[services]] blocks: decouples the host-side port range from the container-internal port. Set base_port to the port the container listens on (e.g. 5432) and host_port to the host range base (e.g. 11532). ecluse publishes (host_port+slot) → base_port in the overlay and sets ECLUSE_<NAME>_PORT = host_port + slot. Defaults to base_port when omitted — existing configs are unaffected.

v0.2.14

Choose a tag to compare

@github-actions github-actions released this 03 Jun 20:10
4c6b8e9

Added

  • ecluse status: port-allocation-only services (no command) now show in the STATUS column instead of ✗ down. They are not counted in the "N services down" summary, do not trigger exit code 1, and have "managed": false in JSON output. This prevents agents from treating unmanaged services as failures.
  • tmux windows now source .env, .env.local, and .env.ecluse before running the service command. Manual restarts inside a tmux window (↑ Enter) automatically have the correct environment without needing source .env.ecluse && in every command field. Load order: .env.env.local.env.ecluse (ecluse slot vars win on overlap).
  • nohup-spawned services now receive vars from .env, .env.local, and .env.ecluse merged into their environment at spawn time, consistent with tmux behaviour.

Fixed

  • ecluse up with no argument now fails fast with an actionable error when stdin is not a terminal (CI, agents, piped shells), instead of blocking on a branch-name prompt until killed.
  • ecluse down and ecluse shutdown pre/post hook failures now emit a warning and continue teardown instead of aborting. Teardown must always complete regardless of hook exit codes.
  • Error messages when no .ecluse.toml is found or no active session exists are now context-aware: the hint differs depending on whether the cwd is a linked git worktree, the repo root, or unrelated to any known session.

v0.2.13

Choose a tag to compare

@github-actions github-actions released this 02 Jun 12:12
5028455

Added

  • extra_ports field on [[services]] blocks: a list of additional per-slot port allocations, each with base_port and port_env. The env var is set to base_port + slot in the process environment. For docker services the port is also published as a host→container binding in the compose overlay and injected into compose_env so compose files can interpolate it (e.g. ${PGPORT}). This is the generic replacement for debug_port — use it for debugger ports (Node.js --inspect, Delve, debugpy, pprof), auxiliary listeners, or any secondary port a service exposes.
  • debug_port is now deprecated. It continues to work — existing configs are unchanged — but ecluse validate emits a warning and extra_ports should be used for new configs.

Fixed

  • ecluse down (hybrid mode) now always stops Docker containers even when no overlay file paths are recorded in session state. Previously, if overlay_file was absent from state, docker compose down was never called and containers kept running silently.
  • ecluse up from inside a non-ecluse git worktree (e.g. a sibling path, not under .ecluse/worktrees/) now correctly uses the actual worktree directory instead of computing a path under worktree_dir. Previously the computed path didn't exist and ecluse up failed with a "worktree not found" error.
  • ecluse status, ecluse ls, ecluse env, and ecluse shell now acquire a shared (read-only) lock instead of an exclusive lock. These commands no longer time out with "another ecluse process may be running" when a long-running ecluse up holds the exclusive lock.
  • Port collision detection now checks docker ps host-port bindings in addition to lsof. Docker containers claim host ports before they start listening, so an lsof-only check could pick a port already reserved by a container. Both checks are best-effort: if Docker is unavailable, the check is skipped and never blocks.

v0.2.12

Choose a tag to compare

@github-actions github-actions released this 02 Jun 10:40
6f0347b

Changed

  • command is now optional for native services. Omitting it puts the service in port-allocation-only mode: ecluse allocates the port and injects all ECLUSE_* env vars, but does not spawn or manage the process — start it yourself via a task runner, post_up hook, or any other means. ecluse validate emits a warning (not an error) when command is absent.

v0.2.11

Choose a tag to compare

@github-actions github-actions released this 01 Jun 23:21
b5d36e3

Added

  • inherit_env config field (default: [".env", ".env.local"]) — files listed here are symlinked from the main worktree root into each new worktree at ecluse up time. Symlinks keep worktrees in sync with root changes automatically. Set to [] to opt out. Pass --no-inherit-env to skip for a single up call (for CI/agents).
  • ecluse shutdown now prints a ecluse flush hint when any session teardown fails, making recovery more discoverable.

Fixed

  • Hardcoded credentials removed from the node-container example compose file.

v0.2.10

Choose a tag to compare

@github-actions github-actions released this 01 Jun 21:57
725c262

Added

  • debug_port field on [[services]] blocks: secondary port for debuggers or auxiliary servers. ecluse computes debug_port + slot and exposes it as ECLUSE_<NAME>_DEBUG_PORT. Use when a service exposes a second listener (Node.js --inspect, Delve, debugpy, pprof, etc.) that defaults to a hardcoded port and would collide across parallel sessions.
  • ecluse up now accepts branch names with slashes directly: ecluse up feat/add-auth → slug feat-add-auth, branch feat/add-auth. The --branch flag is removed — branch comes from the argument itself.
  • ecluse up with no argument uses the git worktree location to determine intent: inside an ecluse-registered worktree → reuse stored slug; inside any other git worktree → auto-detect branch from cwd and register the worktree (no --reuse-worktree flag needed); in the main worktree / repo root → prompt for a branch name. Detached HEAD exits with a clear error.
  • Worktree deletion guard: ecluse down and ecluse shutdown now always prompt before removing a worktree (stop / keep / delete). A ⚠ warning is shown if the worktree has uncommitted changes. Pass --delete-worktree / --delete-worktrees to skip the prompt and delete (for CI/agents), or --keep-worktree / --keep-worktrees to skip and keep.

Changed

  • ecluse status session header is now a borderless key-value block (Slug, Slot, Worktree, Tmux) with right-aligned labels, replacing the single run-on line. Labels use the user-facing term "Slug" (not "Session").
  • ecluse status last column adapts to the session's process manager: WINDOW (tmux window name) for tmux sessions, PID for nohup, omitted entirely for container-only sessions.
  • ecluse status native service health check for tmux sessions now verifies that a descendant of the tmux pane's shell process owns the expected port, rather than probing the port directly. This correctly handles port collisions with unrelated processes and services that fail to start despite their tmux window existing.

Fixed

  • docker stop calls in --force now use DOCKER_HOST from the active Docker context, so OrbStack containers are correctly targeted.
  • tmux session spawning no longer fails with "duplicate session" when the previous session's shell is still alive after services crash. The stale session is killed before a new one is created.