Skip to content

Paper Compile

Paper Compile #617

Workflow file for this run

name: Paper Compile
# Compiles project papers to PDF in the llmXive house style. Runs:
# - every 30 minutes (in case a paper was added without triggering paths)
# - on push to any project's paper/source/ or to the llmxive.cls style
#
# Strategy (per scripts/compile_paper.py):
# 1. Restyle the original arXiv source into a wrapper that loads llmxive.cls
# 2. Compile with lualatex (3 passes + bibtex when applicable)
# 3. On failure, fall back to fetching the canonical arXiv PDF so the
# paper modal always has SOMETHING to show.
#
# Per-paper failures never fail the workflow; the next tick retries them.
# Security: workflow_dispatch inputs are untrusted; surfaced to the shell
# via env: vars (never interpolated directly into run: steps) and validated
# by Python's argparse before reaching any subprocess.
on:
schedule:
- cron: "*/30 * * * *"
push:
branches: [main]
paths:
- 'projects/**/paper/source/**'
- 'papers/.style/**'
- 'scripts/compile_paper.py'
- 'scripts/restyle_arxiv_paper.py'
- '.github/workflows/paper-compile.yml'
workflow_dispatch:
inputs:
project:
description: "Restrict to one PROJ-NNN-... dir (empty = --all)"
required: false
default: ""
max:
description: "Cap on --all (default: 20)"
required: false
default: "20"
permissions:
actions: write # workflow_dispatch to pages.yml
contents: write
concurrency:
group: paper-compile
cancel-in-progress: false
jobs:
compile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Install TeX Live (lualatex + fontspec + standard collections)
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
texlive-luatex texlive-fonts-recommended texlive-fonts-extra \
texlive-latex-recommended texlive-latex-extra texlive-science \
texlive-publishers texlive-bibtex-extra biber \
ghostscript
which lualatex && lualatex --version | head -1
- name: Install llmxive package
run: pip install -e .
- name: Compile papers
env:
INPUT_PROJECT: ${{ github.event.inputs.project }}
INPUT_MAX: ${{ github.event.inputs.max }}
run: |
set -euo pipefail
if [ -n "${INPUT_PROJECT:-}" ]; then
python3 scripts/compile_paper.py "projects/${INPUT_PROJECT}"
else
MAX="${INPUT_MAX:-20}"
python3 scripts/compile_paper.py --all --max "$MAX"
fi
- name: Commit + push
id: commit
# Concurrent cron workflows write web/data/projects.json on every
# tick (each one regens it). A naive pull-after-commit conflicts on
# that file ~always when another tick raced ahead. Strategy:
# 1. PDFs land in projects/<pid>/paper/pdf/ (no-conflict files —
# only this cron writes those particular PDFs this minute).
# 2. Fetch + merge main FIRST so we have the latest world.
# 3. AFTER the pull, regenerate projects.json from the merged
# state — guarantees we never commit a stale projects.json.
# 4. Commit + push. On push race, repeat from step 2.
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Quick check: did the compile step produce any new PDFs?
if [ -z "$(git status --porcelain 'projects/*/paper/pdf/*.pdf' 2>/dev/null)" ]; then
echo "no PDF changes"
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
for i in 1 2 3 4 5; do
# Pull latest main BEFORE regenerating projects.json. Use stash
# so any uncommitted PDFs survive the pull.
git stash push --include-untracked --keep-index -m "compile-pdfs" 2>/dev/null || true
if ! git pull --rebase --autostash origin main; then
echo "pull/rebase failed on attempt $i; aborting + retrying"
git rebase --abort 2>/dev/null || true
git stash pop 2>/dev/null || true
sleep $((5 * i))
continue
fi
git stash pop 2>/dev/null || true
# Regenerate projects.json AFTER the pull so it reflects merged state.
python -c "from llmxive.agents.status_reporter import regenerate_web_data; from pathlib import Path; regenerate_web_data(repo_root=Path('.'))"
git add 'projects/*/paper/pdf/*.pdf' web/data/projects.json 2>/dev/null || true
if git diff --cached --quiet; then
echo "no changes after rebase (someone else compiled these)"
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git commit -m "paper-compile: produce/refresh paper PDFs"
if git push; then
echo "pushed=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "push attempt $i failed (race?); resetting commit + retrying"
git reset --soft HEAD~1
sleep $((5 * i))
done
echo "push failed after 5 attempts" >&2
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 1
- name: Trigger Pages deploy
# GitHub suppresses workflow triggers from GITHUB_TOKEN pushes, so
# pages.yml's `push` trigger never fires for bot commits. Explicit
# workflow_dispatch DOES fire regardless of token provenance.
if: steps.commit.outputs.pushed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh workflow run pages.yml --ref main