Skip to content

bug(mkdocs-deploy): mike deploy --push has no rebase/retry, so a concurrent gh-pages push fails the deploy #13

Description

@MAfarrag

Context

actions/mkdocs-deploy deploys docs by running mike deploy --push <alias> against the shared gh-pages
branch. All three trigger paths (pull_request / main / release) do a plain mike deploy --push with no
fetch-rebase-retry around the push. Surfaced repeatedly by a consumer (serapeum-org/pyramids), where the
Deploy MkDocs workflow intermittently fails and a manual re-run fixes it.

Problem / Current Behaviour

mike deploy --push fetches gh-pages, builds the version commit on that tip, and pushes — all in one step,
with no recovery if the remote advanced in between. If anything pushes to gh-pages in the short window
between mike's fetch and mike's push, the push is a non-fast-forward and the whole job fails:

error: failed to push branch gh-pages to origin:
   ! [rejected]            gh-pages -> gh-pages (fetch first)
  hint: Updates were rejected because the remote contains work that you do not have locally

Because there is no retry, a single lost race kills the deploy and the maintainer must re-run it by hand (the
re-run succeeds because it fetches a now-current tip). Consumers work around it by serializing every deploy of
the workflow through one global concurrency lane, but that only prevents this workflow's runs from racing
each other — it does not protect against an out-of-band gh-pages push (e.g. a history squash to reclaim
clone size) landing in the fetch→push window, and it forces a single lane that cancels other PRs' pending
deploys. A rebase/retry here is what makes the deploy resilient and lets consumers relax that lane.

Affected locations

File Symbol Notes
actions/mkdocs-deploy/action.yml Deploy to GitHub Pages (Pull Request) (lines 199-212) mike deploy --push develop, no retry
actions/mkdocs-deploy/action.yml Deploy to GitHub Pages (Main) (lines 214-229) mike deploy --push main + set-default, no retry
actions/mkdocs-deploy/action.yml Deploy to GitHub Pages (Release) (lines 231-245) mike deploy --push --update-aliases ..., no retry

Steps to Reproduce

1. Trigger two mkdocs deploys close together, or push anything to gh-pages (e.g. a history squash) while a
   deploy is between mike's fetch and its push.
2. The losing deploy fails with `! [rejected] gh-pages -> gh-pages (fetch first)`.
3. Re-run the failed job manually -> it succeeds (it fetched a current tip).

Observed in serapeum-org/pyramids Deploy MkDocs run 29649210655 (job failed at the deploy step with the
rejection above; a squash commit landed on gh-pages inside the fetch->push window).

Proposed Solution

Wrap each mike deploy --push in a fetch-reset-retry loop so a lost push self-heals instead of failing.
Reset the local gh-pages to the current remote tip and let mike re-apply the version (mike is idempotent, so
there are no versions.json conflicts to rebase), then push; retry a few times with backoff:

MIKE="mike"; [ "$PACKAGE_MANAGER" != "pip" ] && MIKE="$PACKAGE_MANAGER run mike"
for attempt in 1 2 3 4 5; do
  git fetch origin gh-pages && git branch -f gh-pages FETCH_HEAD 2>/dev/null || true
  if $MIKE deploy --push develop; then exit 0; fi
  echo "::warning::gh-pages push rejected (attempt $attempt) - refetching and retrying"
  sleep $((attempt * 3))
done
echo "::error::mike deploy --push failed after retries"; exit 1

Apply the same wrapper to the main and release steps (including the set-default --push, which is subject to
the same race). A shared helper/composite step is preferable to duplicating the loop three times.

Out of Scope

  • The consumer-side concurrency configuration (each caller owns that; once this retries, callers can move
    from a single global lane to a per-ref group).
  • Any change to how notebooks are executed or cached.

Effort Estimate

Size: S — a retry wrapper around the three existing deploy steps; the loop logic is small but touches all
three paths and warrants a quick manual verification.

Definition of Done

  • Each deploy path (pull_request / main / release) retries mike deploy --push (and set-default)
    on a non-fast-forward, refetching gh-pages between attempts.
  • A deploy that loses the push race recovers within the retries instead of failing the job.
  • A normal single deploy still publishes exactly one new gh-pages commit (no behaviour change on the
    happy path).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions