Stop deploy.sh from rewriting itself while bash is reading it
CI / verify (push) Successful in 2m42s
CI / publish (push) Has been skipped

`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) <noreply@anthropic.com>
This commit is contained in:
2026-08-13 15:08:22 -07:00
parent 13dec6b4b8
commit a21ecf9e53
+24 -1
View File
@@ -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.