Skip to content

auto-deploy: stop tag pushes hijacking branch hosts; fix pull order + deploy-on-failure#1422

Open
jonfroehlich wants to merge 2 commits into
masterfrom
fix-autodeploy-tag-hijack-and-pull-order
Open

auto-deploy: stop tag pushes hijacking branch hosts; fix pull order + deploy-on-failure#1422
jonfroehlich wants to merge 2 commits into
masterfrom
fix-autodeploy-tag-hijack-and-pull-order

Conversation

@jonfroehlich

Copy link
Copy Markdown
Member

What & why

Hardens the GitLab/GitHub auto-deploy webhook (auto-deploy/index.php).

1. Tag pushes hijacking branch hosts (the headline bug)

The condition ($incomingOp == "tags" && $OPERATION = "TAG") used = (assignment) instead of ==. That made the clause true for every tag push regardless of host config, and reassigned $OPERATION to "TAG" for the rest of the request — so a tag push drove the branch-tracking test server down the tag code path, leaving its checkout detached at that tag. Fixed to ==.

2. Pull/checkout order

git pull aborts from a detached HEAD ("You are not currently on a branch"). For branch hosts we now checkout first, then pull; for tag hosts we fetch first (the tag usually isn't local yet). Prod's tag path is unchanged in the happy case.

3. Deploy-on-failure gate (from code review)

The container build ran unconditionally after checkout/pull, so any pull failure still rebuilt from stale source while looking freshly deployed. Now we confirm the checked-out HEAD equals the pushed commit ($req['after']) before building; abort + log on mismatch. Fail-safe: if the payload has no usable SHA, deploy as before (never blocks a legit deploy).

4. Same =/== footgun, twice more

_trace()/_debug() guarded on if ($x = TRUE) — always true, never disableable. Fixed to a defined() toggle (default on; silence via define('AUTODEPLOY_TRACE'/'AUTODEPLOY_DEBUG', false) in config.php).

5. determine_branch_name() typo

Guarded on misspelled $reqest → always returned null (branch fast-path was dead code). Fixed to $request.

Testing

  • php -l clean (no local PHP runtime; linted in a throwaway php:8-cli-alpine container).
  • No automated harness exists for this PHP webhook; the deploy gate is written fail-safe (falls back to prior behavior on any ambiguity) precisely because it can't be exercised end-to-end off-server.

🤖 Generated with Claude Code

jonfroehlich and others added 2 commits July 22, 2026 14:15
Two related bugs that together froze makeabilitylab-test for four days
(Jul 18-22) while making it look freshly deployed.

1. `$OPERATION = "TAG"` was an assignment, not a comparison. Every tag
   push therefore satisfied the condition on EVERY configured host, and
   reassigned $OPERATION to "TAG" for the remainder of the request. A
   branch-tracking host (the test server) was thus driven down the tag
   path -- `git fetch` + `git checkout <tag>` -- leaving its checkout
   detached at that tag. Confirmed: tag 2.27.0 points at 578d643, which
   is exactly the commit the test host was frozen on.

2. Once detached, it could not recover. `git pull` aborts from a
   detached HEAD, and do_clone_or_pull() ran BEFORE do_checkout_branch(),
   so the pull failed, the local branch never advanced, and the checkout
   stayed behind. For branch deploys we now check out the branch first
   and pull second, which both fixes the failure and self-heals an
   already-detached checkout. Tag deploys keep the original order, since
   a tag generally isn't present locally until after the fetch.

Not fixed here, but worth noting: index.php calls deploy_container()
regardless of whether the pull or checkout succeeded, so a failed update
still produces a successful-looking rebuild. That is what hid this --
/version.json reported built_at advancing on every push while git_sha sat
at a four-day-old commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lings

Follow-up hardening from code review of the tag-hijack/pull-order fix:

- Deploy gate: the container build ran unconditionally after checkout/pull, so
  any pull failure (merge conflict, network/ssh error, or a detached HEAD the
  reorder doesn't cover) rebuilt from stale source while looking freshly
  deployed. Now confirm the checked-out HEAD equals the pushed commit
  ($req['after']) before building; abort and log loudly on mismatch. Fail-safe:
  if the payload carries no usable SHA, deploy as before (never blocks a legit
  deploy).

- _trace()/_debug(): `if ($x = TRUE)` was an assignment, not a comparison --
  the same =/== footgun this branch fixes in the deploy condition -- so they
  always logged and could never be disabled. Default on (preserves behavior),
  disable via define('AUTODEPLOY_TRACE'/'AUTODEPLOY_DEBUG', false) in config.php.

- determine_branch_name(): guarded on misspelled $reqest, so it always returned
  null and the branch-name fast path was dead code. Fixed to $request.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant