Skip to content

RV-S2: setup.sh truncates config.local.env before heredoc, destroying prior config on failure #171

Closed

Description

@amar-python

What happened

External repository review at commit c7bebd1 reproduced a scenario where setup.sh destroys the existing config.local.env when it fails partway through.

Reproduction:

  • A scratch copy with a pre-existing config.local.env was reconfigured with bash setup.sh --defaults --env dev.
  • The command failed with MYSQL_HOST: unbound variable.
  • The prior config.local.env was left at zero bytes.

The reviewer traced the write to build/setup.sh L339–398, where the file is truncated before an unconditional heredoc containing variables for every supported DB engine expands. Any unset variable (even for a deferred engine) kills the heredoc after the truncation has already committed.

Why this matters

Non-atomic write to a file holding the user's configuration. The write pattern is:

  1. Truncate the destination ← commits destruction immediately
  2. Compute content ← can fail here
  3. Write content ← may never run

The file is gitignored, so there is no git checkout recovery. Any custom DB paths, ports or environment-specific overrides the user had typed are gone. ADR-0001 defers non-PostgreSQL engines, so references to MYSQL_HOST and similar should not block the PostgreSQL path.

Decision taken 3 Nov 2026

Option (b) of 3 considered: switch to an atomic-write pattern. Write to config.local.env.new, verify completion, then mv -f to replace the destination. This fixes the whole class of bug.

Option (a) rejected: initialising the unset variables (MYSQL_HOST=${MYSQL_HOST:-unused}) is a 5-line patch but keeps the destructive write pattern.

Option (c) rejected for now: narrowing the heredoc to drop non-PostgreSQL engine blocks is tempting but reopens engine-scope questions (BR-02) and should stay separate.

Proposed

In build/setup.sh around L339–398:

  1. Write the computed config to config.local.env.new in the same directory.
  2. Verify the file is complete (sentinel line check, non-zero size, or similar).
  3. mv -f config.local.env.new config.local.env once verified.
  4. On failure anywhere between steps 1 and 3, the .new file may be left on disk; the next successful run cleans it up or overwrites it. Clean up on exit via trap is nice-to-have.

Done when

  • Reproduction test added: a shell test that unsets a referenced variable, runs setup.sh, asserts the prior config.local.env survives byte-identically.
  • The test fails on main at c7bebd1 without the fix.
  • The test passes with the fix.
  • Full matrix of --env dev, --env test, --env staging, --env prod with and without --defaults runs clean against a pre-existing config file.
  • No change to the fresh-install path (empty config.local.env becomes populated as before).

Reference

Tracker row G35 (main tab). Repo review finding RV-S2. Related: G34 (RV-S1 deployer DB config), both Standards P1.

Activity

  1. amar-python commented on Nov 9, 2026

    @amar-python
    OwnerAuthor

    Fixed in #202 (merged as 203c8ae).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions