| name | fleet-fork-sync |
| description | Sync PMOVES forks from upstream and promote PMOVES.AI submodule gitlinks, driven by the PMOVES.AI GitHub App. Use when forks are behind upstream (CVE/feature drift), when "the submodules aren't synced", or before a gitlink-promotion commit. Covers the App token Known Road, the two-layer sync (fork←upstream, gitlink←fork), and the manual CRITICAL merge. |
fleet-fork-sync — PMOVES fork & submodule gitlink sync
Two layers, in order (doing layer 1 before layer 2 just re-pins stale commits):
- fork ← upstream —
fork-sync.yml (24 curated forks). App-token-driven, local-git merge → PR per fork.
- PMOVES.AI gitlink ← fork HEAD — promote the pointer once the fork's tracked branch advanced.
Full reference: pmoves/docs/operations/GITHUB_APP.md. Memory: reference_github_app_fleet_sync.
The one rule that breaks everything if missed
PMOVES gitlinks track PMOVES.AI-Edition-Hardened, not main. .gitmodules submodule.<path>.branch is the source of truth. Sync the wrong branch and the gitlink shows diverged and can't be promoted without dropping hardening. fork-sync now derives this from .gitmodules automatically — keep it that way.
Step 1 — audit drift (read-only)
bash .claude/skills/pmoves-submodule-fleet/scripts/fleet_audit.sh
gh workflow run fork-sync.yml --ref main -f dry_run=true -f max_forks=40
Read the dry-run job log: STALE/CRITICAL <fork>: N behind, M ahead. ahead = PMOVES hardening a merge must preserve.
Step 2 — run fork-sync (creates reviewable PRs)
gh workflow run fork-sync.yml --ref main -f dry_run=false -f max_forks=12
- Resolves each fork's branch from
.gitmodules (hardened for most).
- Local-git merge in the runner (NOT the
/merges API — it can't merge a cross-repo owner:branch head).
- Guard (
ahead_max=20 / behind_max=1000) skips giants/high-hardening forks as MANUAL without cloning. Override the inputs to force-attempt.
- Conflicting merges
--abort and skip → those need the manual merge (Step 4).
- Watch the log:
PR #N: <url> = success; MANUAL/CONFLICT = skipped.
Step 3 — review/merge fork PRs, then promote gitlinks
Merge fork sync PRs with a merge commit (gh pr merge <n> --repo POWERFULMOVES/<fork> --merge --admin) — NEVER squash (squashing severs shared upstream history → every future sync re-conflicts).
Promote each gitlink ONLY when it's a clean fast-forward:
cur=$(git ls-tree HEAD <submodule-path> | awk '{print $3}')
new=$(gh api repos/POWERFULMOVES/<fork>/git/ref/heads/PMOVES.AI-Edition-Hardened --jq .object.sha)
gh api repos/POWERFULMOVES/<fork>/compare/$cur...$new --jq .status
git update-index --cacheinfo 160000,$new,<submodule-path>
Commit as chore(submodules): promote … → PR (CI = submodule-smoke). Batch multiple gitlinks per PR.
Step 4 — manual CRITICAL merge (diverged / guard-skipped forks)
The forks fork-sync can't auto-merge. Pattern (proven on Archon):
find <dir> -delete 2>/dev/null
git clone --filter=blob:none --no-tags -b PMOVES.AI-Edition-Hardened https://github.com/POWERFULMOVES/<fork>.git <dir>
cd <dir> && git remote add upstream https://github.com/<upstream>.git
git fetch --filter=blob:none --no-tags upstream <ACTIVE_BRANCH>
git merge --no-ff --no-commit upstream/<ACTIVE_BRANCH>
git add -A && git -c core.autocrlf=false commit --no-verify --no-edit -m "Merge upstream …"
git push origin HEAD:sync/upstream-<date>-merge
gh pr create --repo POWERFULMOVES/<fork> --base PMOVES.AI-Edition-Hardened --head sync/upstream-<date>-merge ...
Gotchas that bite:
- Never chain
git commit with git push --force* — the classifier rejects the whole command, so the commit silently never runs. Commit alone, then push to a fresh branch (FF, no force).
- Verify the merge is 2-parent before pushing:
git show --no-patch --format='%h parents=%p' HEAD.
- The PR's CI (image build + Trivy) is the real gate, not the eslint
test job (upstream code often trips the fork's stricter lint — eslint --fix or relax).
App token Known Road
Mint via the reusable .github/workflows/_app-token.yml (SHA-pinned, scoped). Never re-implement create-github-app-token inline. owner + repositories: (named) + minimal permission-*; owner alone = ALL repos (over-broad). The App being installed ≠ the minted token having scope (add owner to span the installation).
Anti-patterns
- ❌ Promoting a
diverged gitlink (drops hardening).
- ❌ Syncing the fork's default branch when the gitlink tracks hardened.
- ❌ Squash-merging fork sync PRs.
- ❌
/merges API for cross-repo upstream→fork (use local git).
- ❌
rm -rf / git push --force (guard-blocked) / chaining commit+force-push.
Citations
pmoves/docs/operations/GITHUB_APP.md, .github/workflows/fork-sync.yml, _app-token.yml
research/SUBMODULE_SYNC_AUDIT_2026-06-07.md, .claude/skills/pmoves-submodule-fleet/
- Fix chain: PRs #1733/#1735/#1736/#1738/#1739/#1741; Headscale gitlink #1740; Archon manual merge PMOVES-Archon#17.