Skip to content

feat(ci): Build all branch β€” protected implementation - #313

Merged
lopugit merged 4 commits into
github-actionsfrom
claude/all-branch-builder
Aug 18, 2026
Merged

feat(ci): Build all branch β€” protected implementation#313
lopugit merged 4 commits into
github-actionsfrom
claude/all-branch-builder

Conversation

@lopugit

@lopugit lopugit commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Build all branch β€” protected implementation

Implementation half of the new Build all branch automation. It rebuilds the wildcard all branch β€” develop + main + every open, same-repo PR (stacked branch β†’ branch PRs included) β€” from scratch on every relevant event, merges everything newest-wins with automatic conflict resolution, and force-pushes only when the resulting tree actually changed. The companion thin listener for product branches is in the develop-side PR (linked below); merge this PR first β€” until it lands, triggered listener runs fail with "workflow not found".

Files

  • .github/workflows/all-branch.yml β€” reusable workflow_call implementation (plus workflow_dispatch on this ref). Full develop checkout with the default GITHUB_TOKEN, plus a depth-1 github-actions checkout for the controller script (same pattern as develop-pr-preview.yml). No PAT and no inherited secret is read.
  • .github/scripts/build-all-branch.mjs β€” the builder (full design notes in its header):
    • Plan β€” gh pr list, then skip fork PRs (routing unreviewed fork code into this repo's Vercel builds would bypass Vercel's fork-authorization step), no-all-labelled PRs, base-branch heads (e.g. the standing developβ†’main PR Promote develop to mainΒ #289), and duplicate heads. The rest merge in topological layers seeded by develop/main: parents before their stacked children, ascending PR number within a layer. Dangling stacks (base is neither develop/main nor an open PR head) are skipped β€” this same rule keeps the stripped github-actions-based PRs (Batch PR conflict workers and activate Lopu fixesΒ #234/[Promote] Sync main into develop (#221)Β #266) from tree-nuking the union with their thousands of deletions.
    • Merge β€” git merge --no-ff -X theirs (a later PR wins contested hunks) plus a matching theirs-biased fallback for the structural conflicts strategy options cannot settle (modify/delete, -merge-attributed paths). The repo's graphify union merge driver is overridden to take-theirs since CI has no graphify binary. A PR whose merge cannot complete is skipped and reported in the manifest β€” never fatal.
    • Invariants re-pinned after merging β€” .github is reset to develop's exact tree (workflow files never execute on all, and the pin keeps force-pushes within the default GITHUB_TOKEN's powers; if a pending workflow-file delta still gets refused, the builder re-pins to the previously pushed all state and retries once). Root AGENTS.md/CLAUDE.md are restored as symlinks to AI_ALL.md, and path~refs_all-build_pr-N collision artifacts from file-vs-symlink conflicts are dropped.
    • Manifest β€” ALL_BRANCH.md committed at the branch root: base tips, every merged PR with how it merged, every skip with its reason. Deliberately timestamp-free, so identical inputs produce identical trees and the push (and its Vercel rebuild) is skipped.
    • --self-test runs the pure planning/rendering examples.
  • .github/workflows/control-plane-ci.yml β€” advisory run_contract line for the new self-test.

Verified live

Three full local builds against the real 64-PR topology using exactly this code:

  • 61 PRs merged (clean merges plus 38 theirs-auto-resolutions), 4 skipped with correct reasons (2 github-actions-based, the standing developβ†’main PR, 1 dangling stack on a merged parent).
  • Junk-file and symlink invariants confirmed on the result; unmerged-path count zero.
  • origin/all was created from this code (e47bce9c), and an immediately following from-scratch rebuild produced a bit-identical tree and skipped the push β€” determinism and push-dedup both confirmed.
  • node --check, self-test, and YAML parses all green.

AI build doctor

Textually-clean merges can still collide semantically β€” two PRs each declaring the same helper merges without any git conflict, then the union fails to build (and develop would break identically when the second PR lands; the resolver can never see it because there is no conflict to resolve). The doctor heals exactly this, on the union only β€” source PRs keep healing one-by-one through the existing resolver as they merge:

  • After each input-changed rebuild, the union build runs: pnpm install (with a deterministic lockfile-reset repair before any AI is spent), then the full Vercel-parity remix build (pre-dev, client + embed + verify, nitro-template sync, vercel-preset server build, output patch + verify) β€” anything lighter let a collision in the later stages reach the deployment. The failure log is captured for the model. Unchanged inputs skip everything.
  • On failure: up to three edit-files-only Claude rounds using the resolver's exact action pin, admin model waterfall (pr-conflict-auto-resolver-model-waterfall β€” one dial governs both automations), and security posture (no shell/git, persist-credentials: false everywhere, tool-level path bans). After each round a mechanical step reverts out-of-scope edits, scans staged content for raw/base64 credentials, commits, and re-runs the build.
  • Doctor commits ride on all above the manifest commit and are cherry-pick-replayed onto the next rebuild; a fixup that stops applying (the source PRs healed for real, as intended) is dropped silently. An exhausted doctor leaves a non-replayable marker commit: identical inputs get exactly one retry, then wait for any PR/base movement β€” an unfixable union can never burn model rounds hourly.
  • Pushes now authenticate via an ephemeral GH_TOKEN credential helper (no token ever sits in .git/config while the model runs).

Live-fired locally against the real 64-PR union (doctor rounds executed by Claude locally with the exact workflow prompts; the guard/commit/replay/push plumbing is the shipped code): five collision layers healed in sequence β€” duplicate searchParams/formatCompactCount/formatDwell declarations (Feed.tsx, PostCard.tsx), a verify:embed script lost to a package.json hunk race, an embed-bundle verifier false-positive on bundled CSS-injector strings, duplicate server-side notification-prefs helpers plus a duplicate JSON key, and a dropped previewFreshnessHtmlPlugin() in a vite plugin-array hunk race (caught only by the Vercel-parity verify stage β€” which is why the check now runs the full chain) β€” ending in a fully green install + client + embed + server + output-verify build. The healed branch was pushed, and a following rebuild on drifted inputs replayed the fixups cleanly and stayed green with zero AI spend (origin/all @ 56cb7e8b). The five-layer day is also why rounds went from two to three (deeper stacks converge across scheduled runs via replay).

Knobs

ALL_BRANCH_NAME (all), ALL_BASE_BRANCHES ("develop main"), ALL_SKIP_LABEL (no-all), ALL_INCLUDE_FORKS=1, ALL_PUSH=0; outside Actions the script refuses to run without ALL_BRANCH_FORCE=1 since it hard-switches branches.

Listener PR (merge second): #314

πŸ€– Generated with Claude Code

Deterministically rebuilds the wildcard `all` branch β€” develop + main +
every open same-repo PR (stacks included) β€” with stack-aware ordering,
-X theirs newest-wins conflict policy plus a matching structural-conflict
fallback, per-PR skip-not-fail, a committed ALL_BRANCH.md manifest,
tree-identical push dedup, and .github pinned to develop so the default
GITHUB_TOKEN can force-push. Self-test wired into control-plane CI
advisories.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lopugit and others added 2 commits August 19, 2026 00:39
Textually-clean merges can still collide semantically (duplicate helper
declarations across PRs), breaking the union build with no git conflict
anywhere. After each input-changed rebuild the union build now runs
(install β†’ vite client β†’ nitro server) with a deterministic lockfile
repair, then up to two edit-files-only Claude rounds (resolver action
pin, model waterfall, and security posture) fix the tree; a mechanical
step reverts out-of-scope edits, scans for credential leaks, commits,
and re-verifies. Doctor commits ride on the all branch and are
cherry-pick-replayed onto the next rebuild, dropping silently once the
source PRs heal. Unchanged inputs skip the check and every model round.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Local live-fire against the real 64-PR union showed collision layers
surface one build-stage at a time (4 layers today), so a third unrolled
round is added; deeper stacks converge across scheduled runs via replay.
An exhausted doctor now pushes its failure marker even when the tree is
otherwise identical, and the leading marker streak on the tip is a retry
ledger: identical inputs get exactly one more attempt before waiting for
input movement, so an unfixable union cannot burn model rounds hourly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Deterministic CI contract advisory

These examples are warning-only. They do not fail a required check or block this PR.

  • βœ… Develop-preview controller examples
  • ⚠️ Workflow control-plane examples (exit 1)
Sanitized tail
    '.github/actions/rebase-conflict-round/action.yml',
+   '.github/workflows/all-branch.yml',
    '.github/workflows/rebase-pr-stacks.yml',
    '.github/workflows/resolve-pr-conflicts.yml'
  ]

    at assertAdminModelRouting (file:///home/runner/work/thingtime/thingtime/.github/scripts/workflow-control-plane-contract.mjs:358:10)
    at assertControlPlaneContract (file:///home/runner/work/thingtime/thingtime/.github/scripts/workflow-control-plane-contract.mjs:744:3)
    at file:///home/runner/work/thingtime/thingtime/.github/scripts/workflow-control-plane-contract.mjs:874:3
    at ModuleJob.run (node:internal/modules/esm/module_job:343:25)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:681:26)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5) {
  generatedMessage: false,
  code: 'ERR_ASSERTION',
  actual: [
    '.github/actions/rebase-conflict-round/action.yml',
    '.github/workflows/all-branch.yml',
    '.github/workflows/rebase-pr-stacks.yml',
    '.github/workflows/resolve-pr-conflicts.yml'
  ],
  expected: [
    '.github/actions/rebase-conflict-round/action.yml',
    '.github/workflows/rebase-pr-stacks.yml',
    '.github/workflows/resolve-pr-conflicts.yml'
  ],
  operator: 'deepStrictEqual',
  diff: 'simple'
}

Node.js v22.23.2
- ⚠️ Conflict-resolver routing examples (exit 1)
Sanitized tail
    '.github/workflows/resolve-pr-conflicts.yml'
  ]

    at assertAdminModelRouting (file:///home/runner/work/thingtime/thingtime/.github/scripts/resolve-pr-conflicts-routing-contract.mjs:291:10)
    at assertWorkflowSource (file:///home/runner/work/thingtime/thingtime/.github/scripts/resolve-pr-conflicts-routing-contract.mjs:207:3)
    at selfTest (file:///home/runner/work/thingtime/thingtime/.github/scripts/resolve-pr-conflicts-routing-contract.mjs:440:3)
    at file:///home/runner/work/thingtime/thingtime/.github/scripts/resolve-pr-conflicts-routing-contract.mjs:445:3
    at ModuleJob.run (node:internal/modules/esm/module_job:343:25)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:681:26)
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5) {
  generatedMessage: false,
  code: 'ERR_ASSERTION',
  actual: [
    '.github/actions/rebase-conflict-round/action.yml',
    '.github/scripts/rebase-stack/refresh-promotion-graphify.sh',
    '.github/workflows/all-branch.yml',
    '.github/workflows/rebase-pr-stacks.yml',
    '.github/workflows/resolve-pr-conflicts.yml'
  ],
  expected: [
    '.github/actions/rebase-conflict-round/action.yml',
    '.github/scripts/rebase-stack/refresh-promotion-graphify.sh',
    '.github/workflows/rebase-pr-stacks.yml',
    '.github/workflows/resolve-pr-conflicts.yml'
  ],
  operator: 'deepStrictEqual',
  diff: 'simple'
}

Node.js v22.23.2
- βœ… Rebase ownership routing examples - βœ… Promotion-worker routing examples - βœ… Promotion-worker behavior examples - βœ… Promotion changelog examples - βœ… Feature promoter examples - βœ… All-branch builder examples

The lighter client+server check let a plugin-array hunk race (dropped
previewFreshnessHtmlPlugin) survive to Vercel's verify:vercel-output
stage. The check now runs remix's full build script β€” Vercel's exact
chain β€” so nothing lighter can leak past the doctor again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@lopugit
lopugit merged commit 4ff7ab8 into github-actions Aug 18, 2026
23 checks passed
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