From a21ecf9e53a9f02441dacfdb35696de55f6fe5ad Mon Sep 17 00:00:00 2001 From: Kartios Date: Thu, 13 Aug 2026 15:08:22 -0700 Subject: [PATCH] Stop deploy.sh from rewriting itself while bash is reading it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `git reset --hard origin/main` replaces this script mid-execution. bash does not slurp a script — it reads incrementally and remembers a byte OFFSET, so after the reset it resumes at that offset into different content. This is not theoretical. The 13dec6b deploy hit it: the new public-origin gate and the rollback were on disk and never ran, because bash was still executing the buffered previous version. That deploy exited 0 and the release is healthy, so it cost nothing this time. The failure mode when it does bite is a spliced or half-executed line, part way through a deployment. scripts/autodeploy.sh has always re-exec'd from a mktemp copy for exactly this reason. deploy.sh needed the same guard. PIG_REPO_ROOT is resolved before the re-exec and exported across it: after the re-exec `$0` is the copy in /tmp, so `dirname "$0"` would cd to the wrong tree. Verified with a harness that rewrites the original mid-run and asserts the child keeps both its content and its working directory. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/deploy.sh | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 56e32a7..519e812 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -26,7 +26,30 @@ set -euo pipefail -cd "$(dirname "$0")/.." +# Resolved BEFORE the re-exec below and carried across it. After the re-exec +# `$0` is a copy in /tmp, so `dirname "$0"` would point at the wrong tree. +PIG_REPO_ROOT="${PIG_REPO_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}" +export PIG_REPO_ROOT +cd "$PIG_REPO_ROOT" + +# Run from a copy, because this script rewrites itself. +# +# `git reset --hard origin/main` below replaces this very file while bash is +# still reading it. bash does not slurp a script: it reads incrementally and +# remembers a byte OFFSET, so after the reset it resumes at that offset into +# different content — which silently skips or splices steps, and at worst +# executes a fragment of a line. The 13dec6b deploy hit exactly this: the new +# public-origin gate was on disk and never ran, because bash was still +# executing the buffered previous version. +# +# It failed harmlessly that time. It is not guaranteed to. scripts/autodeploy.sh +# has always had this guard; deploy.sh needed it for the same reason. +if [ "${PIG_DEPLOY_REEXEC:-}" != '1' ]; then + _copy=$(mktemp -t pig-deploy.XXXXXX) + cat "$0" > "$_copy" + PIG_DEPLOY_REEXEC=1 exec bash "$_copy" "$@" +fi +trap 'rm -f "$0"' EXIT # What compose will run. Must match the `image:` default in docker-compose.yml, # because that is the tag a rollback re-points at the previous image.