Skip to content

zsh: tmux startup '. <file>' fails — POSIX dot builtin doesn't search cwd in zsh, need './<file>' or 'source' #27

@hefgi

Description

@hefgi

Summary

After the fix from #26 landed in 0.3.0 (cd <worktree> now runs before the env sources), the per-slot env files are still not loading on zsh. The new tmux startup command uses the POSIX . (dot) builtin to source .env, .env.local, and .env.ecluse with a relative path that has no ./ prefix:

cd '<worktree>';
set -a; . '<abs>/.ecluse/preambles/<slug>.sh'; set +a;
set -a; . '.env'; set +a;
set -a; . '.env.local'; set +a;
set -a; . '.env.ecluse'; set +a

In bash, . '.env' searches the current directory and works. In zsh (default shell on macOS since Catalina), the . builtin does not search the current directory by default — it only searches $PATH. The shell logs:

.: no such file or directory: .env
.: no such file or directory: .env.local
.: no such file or directory: .env.ecluse

…and continues silently. The per-slot env never loads, so any process spawned in the tmux window only sees the per-slug preamble's vars — which is fine for the ECLUSE_* vars (those are in the preamble) but loses anything from .env, .env.local, or .env.ecluse that isn't duplicated into the preamble. In practice this means apps that rely on user-managed .env (e.g. dotenv-style config, framework defaults like ONYX_ENVIRONMENT=development) crash on startup because their config schemas fail validation.

Version / environment

  • ecluse 0.3.0
  • process_manager = "tmux"
  • macOS 14, zsh 5.9 (default /bin/zsh)

Reproduction

cd /tmp && mkdir zsh-source-test && cd zsh-source-test
echo 'export FOO=bar' > .env
bash -c '. .env && echo "bash: FOO=$FOO"'
# → bash: FOO=bar

zsh -c '. .env && echo "zsh: FOO=$FOO"'
# → zsh:.:1: no such file or directory: .env
# (FOO is unset, the source silently failed)

zsh -c '. ./.env && echo "zsh: FOO=$FOO"'
# → zsh: FOO=bar   ✓ works with ./ prefix

zsh -c 'source .env && echo "zsh: FOO=$FOO"'
# → zsh: FOO=bar   ✓ works with `source` instead of `.`

This is documented zsh behavior — see the zsh manual on shell builtins for .: "Unlike in some other shells, zsh's . builtin does NOT search the current directory for the script unless . is in $PATH". POSIX . is supposed to search $PATH only; bash extends it to also try cwd, which is what most users rely on without realizing.

Suggested fix

Two equivalent fixes — pick whichever fits the codebase:

  1. Add ./ prefix to relative paths: change . '.env' to . './.env' (and same for .env.local, .env.ecluse). This is the POSIX-correct way and works in both bash and zsh.

  2. Use source instead of .: source '.env' works in both bash and zsh because source is a bash/zsh extension that searches cwd in both shells (it does not exist in pure POSIX sh, but ecluse already targets bash-compatible shells via process_manager = "tmux" invoking the user's interactive shell — which is bash/zsh on macOS and Linux, never dash).

Option (1) is more portable (works under POSIX sh/dash too in case anyone runs ecluse there); option (2) is more readable.

Impact

  • Severity: medium. The cd-first fix in tmux startup sources .env / .env.local / .env.ecluse before cd to worktree → per-slot env not loaded #26 closes the worst part (cross-slot env corruption via the global preamble). With per-slug preambles, the slot identity is preserved.
  • But: any env defined in .env, .env.local, or .env.ecluse that is NOT mirrored into the per-slug preamble is lost on zsh. In practice this includes user-managed config files (.env), per-worktree overrides written by post_up hooks (.env.local), and the canonical ecluse env file (.env.ecluse).
  • Single-shell bash users don't see the bug. zsh users (default on macOS) do.
  • The failure is silent in tmux scrollback (only visible at the top of the pane before output scrolls past); errors only surface when an app tries to read a missing env var.

Followup to #26

This issue is the residual half of #26 — the cd ordering and per-slug preamble parts of that fix both landed correctly and are working in 0.3.0. This is a separate, smaller bug that only manifests under zsh.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions