diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index 5c756cc6e..aa2b73652 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -37,7 +37,23 @@ jobs: - name: Build docs run: | - python scripts/zensical_build.py build + echo "::group::Available memory before build" + free -h || true + echo "::endgroup::" + + # Capture peak RSS and exit status so an OS kill (e.g. SIGKILL -> + # exit 247) is visible. Continue to the diagnostics step regardless. + status=0 + /usr/bin/time -v python scripts/zensical_build.py build || status=$? + echo "docs build exit status: $status" + + if [ "$status" -ne 0 ]; then + echo "::group::OOM killer check" + dmesg 2>/dev/null | grep -i -E "killed process|out of memory" | tail -n 20 || \ + echo "dmesg unavailable or no OOM messages" + echo "::endgroup::" + exit "$status" + fi - name: Deploy to GitHub Pages run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 194264397..7ebad6f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. ## [unreleased] +### General +#### Fixed +- Retried the Zensical documentation build on transient crashes (e.g. SIGKILL/exit 247) so the docs release pipeline no longer fails intermittently (Thanks @Copilot) ## [2.2.363] - 2026-06-28 ### General diff --git a/scripts/zensical_build.py b/scripts/zensical_build.py index d3fdd4082..8bcac0f81 100644 --- a/scripts/zensical_build.py +++ b/scripts/zensical_build.py @@ -276,7 +276,23 @@ def main(argv: list[str]) -> int: cmd = [zensical, command, "-f", str(GENERATED_CONFIG), *passthrough] print(f"Running: {' '.join(cmd)}") - return subprocess.run(cmd, env=env, check=False).returncode + + # ``serve`` is interactive/long-running, so run it once. ``build`` can be + # killed by the OS (e.g. exit code 247 == 256-9, SIGKILL) when memory spikes + # while griffe imports the documented ML packages; that crash is transient, + # so retry a couple of times before giving up. + if command != "build": + return subprocess.run(cmd, env=env, check=False).returncode + + attempts = max(1, int(os.environ.get("ZENSICAL_BUILD_RETRIES", "3"))) + rc = 0 + for attempt in range(1, attempts + 1): + rc = subprocess.run(cmd, env=env, check=False).returncode + if rc == 0: + return 0 + print(f" zensical build failed (exit {rc}), attempt {attempt}/{attempts}") + print(f" zensical build still failing after {attempts} attempt(s)") + return rc if __name__ == "__main__":