Stop the deploy script rewriting itself mid-run #61

Closed
opened 2026-08-26 17:26:56 +00:00 by jcoffey-dev · 0 comments
Owner

Found while installing #60 on the live host, before it ran there.

Moving the deploy script into the repo put it inside the checkout it git reset --hards. Bash doesn't read a script all at once — it reads as it goes, by byte offset — so replacing the file underneath a running shell makes it stop wherever it had reached.

Silently, and with exit status 0. Demonstrated rather than assumed:

echo "line A"
cat > "$0" <<'NEW'
echo "REWRITTEN"
NEW
echo "line B"

prints line A, never reaches line B, and exits 0.

In a deploy that means: build the image, stop before replacing the container, report success. The old container keeps serving while everything says the new one shipped.

It only bites when a deploy carries a change to the deploy script itself — rare enough to be baffling when it happens, and exactly the quiet-failure shape this project keeps paying for. The old host script lived outside the checkout, so it never had this problem; moving it in is what introduced it.

The fix

Re-exec from a copy outside the tree before touching git, and remove the copy on exit. Running from outside the checkout skips the whole thing.

The trap is an if rather than [ -n … ] && trap. The latter rests on errexit ignoring a failed left operand of && — it does (I checked), but a deploy script is a poor place to depend on knowing that.

Testing

case result
script inside checkout, overwrites itself mid-run every later line runs; temp copy removed
same without the guard execution stops at the rewrite, exit 0
script run from outside the checkout no re-exec, unchanged behaviour
--help from both locations exits 0

226 web + 75 server tests and typecheck unaffected.

Merged 2026-08-26 as coffey-labs/ihasmail@fd0fe43ef7

Rebuilt from: git history, session transcript.

Found while installing #60 on the live host, before it ran there. Moving the deploy script into the repo put it **inside the checkout it `git reset --hard`s**. Bash doesn't read a script all at once — it reads as it goes, by byte offset — so replacing the file underneath a running shell makes it stop wherever it had reached. Silently, and with **exit status 0**. Demonstrated rather than assumed: ```bash echo "line A" cat > "$0" <<'NEW' echo "REWRITTEN" NEW echo "line B" ``` prints `line A`, never reaches `line B`, and exits `0`. In a deploy that means: build the image, stop before replacing the container, report success. The old container keeps serving while everything says the new one shipped. It only bites when a deploy carries a change to the deploy script itself — rare enough to be baffling when it happens, and exactly the quiet-failure shape this project keeps paying for. The old host script lived *outside* the checkout, so it never had this problem; moving it in is what introduced it. ## The fix Re-exec from a copy outside the tree before touching git, and remove the copy on exit. Running from outside the checkout skips the whole thing. The trap is an `if` rather than `[ -n … ] && trap`. The latter rests on errexit ignoring a failed left operand of `&&` — it does (I checked), but a deploy script is a poor place to depend on knowing that. ## Testing | case | result | |---|---| | script inside checkout, overwrites itself mid-run | every later line runs; temp copy removed | | same without the guard | execution stops at the rewrite, exit 0 | | script run from outside the checkout | no re-exec, unchanged behaviour | | `--help` from both locations | exits 0 | 226 web + 75 server tests and typecheck unaffected. **Merged** 2026-08-26 as coffey-labs/ihasmail@fd0fe43ef72d <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.