Skip to content

preamble emits double-quoted values for shell variables that came from quoted dotenv lines, breaking dependent apps #28

@hefgi

Description

@hefgi

Summary

The per-slug preamble at .ecluse/preambles/<slug>.sh (introduced in 0.3.0) preserves the quote characters from the source .env file when re-emitting them as shell export statements. The result is that the runtime env var contains literal quote characters as part of its value, breaking any consumer that expects the unquoted value.

This is a separate bug from #26 / #27 but compounds with #27: zsh users hit #27 first (no env loads at all), bash users would hit this quote bug as soon as the preamble actually loads.

Version / environment

  • ecluse 0.3.0
  • process_manager = "tmux"
  • macOS 14, zsh and bash both affected
  • mode = "hybrid"

Reproduction

Given a .env file with quoted values (the most common dotenv style):

# .env
ONYX_ENVIRONMENT="development"
E2E_TEST_EMAIL="e2e-test@example.com"
API_BASE_URL=http://localhost:4444

After ecluse up feat-foo, inspect the generated preamble:

grep -E "ONYX_ENVIRONMENT |E2E_TEST_EMAIL|API_BASE_URL" .ecluse/preambles/feat-foo.sh
# Output:
#   export ONYX_ENVIRONMENT='"development"'
#   export E2E_TEST_EMAIL='"e2e-test@example.com"'
#   export API_BASE_URL='http://localhost:4444'

Note the literal "..." double quotes inside the single quotes on the first two lines — those quotes are part of the value, not syntax. The third line is correct because the original .env line had no quotes.

When the preamble is sourced and a downstream app reads the var:

source .ecluse/preambles/feat-foo.sh
echo "ONYX_ENVIRONMENT is [$ONYX_ENVIRONMENT]"
# Output: ONYX_ENVIRONMENT is ["development"]   ← includes the quotes!

For example a vite plugin that validates ONYX_ENVIRONMENT against z.literal("development") will fail with:

{
  "received": "\"development\"",
  "code": "invalid_literal",
  "expected": "development",
  "message": "Invalid literal value, expected \"development\""
}

…because the value is the 13-character string "development" (with quotes), not the 11-character string development.

Root cause

The preamble generator appears to be reading the raw bytes of each line in .env, finding the =, taking everything after it as the value (including any surrounding quotes), and then wrapping that whole substring in single quotes for the export statement. Dotenv parsers normally strip the outer quotes during parse; this code path is treating dotenv syntax as if it were shell syntax (where FOO="bar" is identical to FOO=bar).

A correct implementation would either:

  1. Parse the .env file with a dotenv-compatible parser that strips outer matching quote pairs ("..." and '...') before re-serialising
  2. Re-emit values with shell-correct quoting via printf %q (bash) or equivalent, working from the parsed (unquoted) value, not the raw source bytes

Right now it's the worst of both worlds: values that happen to have no quotes in .env come through fine, but values that have quotes round-trip with the quotes embedded.

Suggested fix

Use a dotenv parser when reading .env / .env.local, and re-emit with shell-safe quoting from the parsed value:

// pseudocode
for (key, value) in dotenv_parse(env_file_path) {
    // value already has outer quotes stripped by the parser
    writeln!(preamble, "export {}={}", key, shell_escape(&value))?;
}

shell_escape should wrap the value in single quotes and escape any embedded single quotes. The current logic seems to be doing roughly the second half (single-quote wrapping) but skipping the dotenv parse step.

Impact

Followup to #26, related to #27

This issue was masked in 0.2.16 because the cd-ordering bug from #26 meant the per-slot env never loaded at all — so nobody noticed the preamble itself was malformed. Now that the preamble is the primary env source for spawned services (per the 0.3.0 design), the malformation matters.

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