Skip to content

Batch PR conflict workers and activate Lopu fixes - #234

Open
lopugit wants to merge 58 commits into
github-actionsfrom
codex/batch-pr-conflict-resolutions
Open

Batch PR conflict workers and activate Lopu fixes#234
lopugit wants to merge 58 commits into
github-actionsfrom
codex/batch-pr-conflict-resolutions

Conversation

@lopugit

@lopugit lopugit commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • collapse each detector wave into one canonical batch worker dispatch instead of one workflow run per PR
  • expand the batch into isolated matrix jobs with fail-fast: false and max-parallel: 3
  • serialize duplicate discoveries per PR with concurrency.queue: max, without canceling running or pending work
  • revalidate every queued PR snapshot immediately before checkout, secrets, or AI spend
  • batch stacked-PR cascades through the same fixed github-actions control-plane boundary

A batch is canonical base64 JSON containing only sorted unique PR numbers and per-PR manual-retry intent. It is capped at 200 selections, below GitHub's 256-job matrix limit; larger waves fall back to multiple bounded batches.

Security and failure isolation

  • only bot-authored fixed-control-plane handoffs may carry a batch
  • exact PR and batch selectors are mutually exclusive and canonical schema/size/order are enforced before querying GitHub
  • the worker re-derives open state, repository ownership, refs, live SHAs, labels, branch protection, mergeability, and stack ownership before every matrix job
  • stale queued entries no-op before status comments or PR checkout
  • one PR failure does not cancel sibling PR resolutions
  • promotion routing remains a separate mutually exclusive path

Validation

  • exact control-plane-ci.yml JavaScript, Bash, and automation-contract suite
  • resolver routing and workflow control-plane contracts, including malformed/unsorted/oversized/injected batches
  • promotion worker routing, quarantine, Graphify history-boundary, changelog, and promoter self-tests
  • Ruby YAML parse and git diff --check
  • Graphify refresh/query, hook status, merge-driver attribute, and high-limit portable HTML export
  • actionlint main passes after excluding its current parser gap for GitHub's newly documented concurrency.queue key and the workflow's pre-existing informational ShellCheck findings

Combined activation fixes

This branch now contains the complete head of PR #422 as a merge parent. One merge of this PR therefore activates the batch worker together with:

  • deterministic Graphify-only and AI conflict rounds discarding the temporary trusted action before exact scratch verification
  • repository-level CodeQL dispositions coalescing compatible proposals across PR snapshots
  • contradictory CodeQL reasons leaving only that alert open without failing unrelated reviews
  • the isolated writer revalidating the exact reviewed PR alert instance instead of the global default-branch instance

Combined merge commit: bc9233a.

Final CodeQL writer audit

The isolated CodeQL disposition writer now re-resolves each PR target branch tip instead of trusting the historical base.sha snapshot recorded on an out-of-date PR. This keeps exact-head/base safety while allowing valid dispositions on old develop PRs to be applied rather than silently skipped. Contract tests cover both the live ref lookup and the prohibition on historical-base comparison.

Historical CodeQL backfill progression

The bounded inventory now resolves the live synthetic refs/pull/<number>/merge object and verifies its exact base/head parents before checking coverage. GitHub's paginated PR merge_commit_sha can lag this ref; trusting it caused completed snapshots to be redispatched as safe no-ops and starved older missing analyses. A live pre-merge test advanced past those repeated no-ops and selected 20 genuinely missing snapshots, all of which were dispatched for exact analysis.

@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
thingtime Ready Ready Preview Aug 17, 2026 4:41am

Request Review

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

🤖 Auto-resolve running — the conflict resolver started working on this PR at 05:09 UTC.

Expected to finish around 05:17 UTC (most resolutions still land in 3-8 minutes; the hosted-runner ceiling is 360). On success a merge commit resolving the conflicts is pushed to this branch and a result comment follows — no manual action is needed meanwhile.

Live status

# Conflicts:
#	graphify-out/GRAPH_REPORT.md
#	graphify-out/graph.json
#	graphify-out/manifest.json
`graphify extract` with LLM semantic extraction (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/31370509164
@github-actions

Copy link
Copy Markdown
Contributor

🤝 Merged github-actions into codex/batch-pr-conflict-resolutions — conflicts auto-resolved by the resolve-pr-conflicts workflow.

No AI resolution was needed by merge time; the branch was updated with a plain merge commit.

graphify-out/ was reset wholesale to the github-actions side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).
Then re-ran graphify on the merged code WITH LLM semantic extraction (graphify extract, claude-cli backend) and committed the result — content new to this merge is semantically indexed; unchanged content came from the tracked cache.

Please review the merge commit before relying on it.

# Conflicts:
#	graphify-out/GRAPH_REPORT.md
#	graphify-out/graph.json
#	graphify-out/manifest.json
`graphify extract` with LLM semantic extraction (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/31554712871
@github-actions

Copy link
Copy Markdown
Contributor

🤝 Merged github-actions into codex/batch-pr-conflict-resolutions — conflicts auto-resolved by the resolve-pr-conflicts workflow.

No AI resolution was needed by merge time; the branch was updated with a plain merge commit.

graphify-out/ was reset wholesale to the github-actions side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).
Then re-ran graphify on the merged code WITH LLM semantic extraction (graphify extract, claude-cli backend) and committed the result — content new to this merge is semantically indexed; unchanged content came from the tracked cache.

Please review the merge commit before relying on it.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Could not auto-resolve conflicts with github-actions — manual resolution needed. See the workflow run.

# Conflicts:
#	graphify-out/GRAPH_REPORT.md
#	graphify-out/graph.json
#	graphify-out/manifest.json
`graphify extract` with LLM semantic extraction (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/31572718558
@github-actions

Copy link
Copy Markdown
Contributor

🤝 Merged github-actions into codex/batch-pr-conflict-resolutions — conflicts auto-resolved by the resolve-pr-conflicts workflow.

No AI resolution was needed by merge time; the branch was updated with a plain merge commit.

graphify-out/ was reset wholesale to the github-actions side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).
Then re-ran graphify on the merged code WITH LLM semantic extraction (graphify extract, claude-cli backend) and committed the result — content new to this merge is semantically indexed; unchanged content came from the tracked cache.

Please review the merge commit before relying on it.

# Conflicts:
#	graphify-out/GRAPH_REPORT.md
#	graphify-out/graph.json
#	graphify-out/manifest.json
`graphify extract` with LLM semantic extraction (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/31952192730
@github-actions

Copy link
Copy Markdown
Contributor

🤝 Merged github-actions into codex/batch-pr-conflict-resolutions — conflicts auto-resolved by the resolve-pr-conflicts workflow.

No AI resolution was needed by merge time; the branch was updated with a plain merge commit.

graphify-out/ was reset wholesale to the github-actions side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).
Then re-ran graphify on the merged code WITH LLM semantic extraction (graphify extract, claude-cli backend) and committed the result — content new to this merge is semantically indexed; unchanged content came from the tracked cache.

Please review the merge commit before relying on it.

github-actions Bot and others added 2 commits August 17, 2026 01:10
…olved conflicts)

Conflicted paths: .github/workflows/resolve-pr-conflicts.yml, graphify-out/GRAPH_REPORT.md, graphify-out/graph.json, graphify-out/manifest.json

Resolved by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/31983846915

Co-Authored-By: Claude <noreply@anthropic.com>
`graphify extract` with LLM semantic extraction (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/31983846915
@github-actions

Copy link
Copy Markdown
Contributor

🤝 Merged github-actions into codex/batch-pr-conflict-resolutions — conflicts auto-resolved by the resolve-pr-conflicts workflow.

Conflicted files:

  • .github/workflows/resolve-pr-conflicts.yml
  • graphify-out/GRAPH_REPORT.md
  • graphify-out/graph.json
  • graphify-out/manifest.json

graphify-out/ was reset wholesale to the github-actions side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).
Then re-ran graphify on the merged code WITH LLM semantic extraction (graphify extract, claude-cli backend) and committed the result — content new to this merge is semantically indexed; unchanged content came from the tracked cache.

Please review the merge commit before relying on it.

# Conflicts:
#	graphify-out/GRAPH_REPORT.md
#	graphify-out/graph.json
#	graphify-out/manifest.json
`graphify extract` with LLM semantic extraction (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/31988443004
@github-actions

Copy link
Copy Markdown
Contributor

🤝 Merged github-actions into codex/batch-pr-conflict-resolutions — conflicts auto-resolved by the resolve-pr-conflicts workflow.

No AI resolution was needed by merge time; the branch was updated with a plain merge commit.

graphify-out/ was reset wholesale to the github-actions side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).
Then re-ran graphify on the merged code WITH LLM semantic extraction (graphify extract, claude-cli backend) and committed the result — content new to this merge is semantically indexed; unchanged content came from the tracked cache.

Please review the merge commit before relying on it.

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

⚠️ The resolver stopped in a later safety, verification, or publication step. No residual conflicted paths were confirmed; see the workflow run.

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

⚠️ The resolver stopped in a later safety, verification, or publication step. No residual conflicted paths were confirmed; see the workflow run.

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

🤝 Merged github-actions into codex/batch-pr-conflict-resolutions — conflicts auto-resolved by Lopu's PR manager.

Conflicted files:

  • .github/scripts/resolve-pr-conflicts-routing-contract.mjs
  • .github/scripts/workflow-control-plane-contract.mjs
  • .github/workflows/resolve-pr-conflicts.yml
  • graphify-out/GRAPH_REPORT.md
  • graphify-out/graph.json
  • graphify-out/manifest.json

graphify-out/ was reset wholesale to the github-actions side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).

Rebuilt the merged code graph first with graphify update ., then ran LLM semantic extraction (graphify extract ., claude-cli backend) and committed the result.

Please review the merge commit before relying on it.

github-actions Bot and others added 2 commits August 26, 2026 05:45
…olved conflicts)

Conflicted paths: .github/scripts/resolve-pr-conflicts-routing-contract.mjs, .github/scripts/workflow-control-plane-contract.mjs, .github/workflows/resolve-pr-conflicts.yml, graphify-out/GRAPH_REPORT.md, graphify-out/graph.json, graphify-out/manifest.json

Resolved by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/32910735947

Co-Authored-By: Lopu <github-actions[bot]@users.noreply.github.com>
Structural `graphify update` followed by LLM semantic `graphify extract` (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/32910735947
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

🤝 Merged github-actions into codex/batch-pr-conflict-resolutions — conflicts auto-resolved by Lopu's PR manager.

No AI resolution was needed by merge time; the branch was updated with a plain merge commit.

graphify-out/ was reset wholesale to the github-actions side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).

Rebuilt the merged code graph first with graphify update ., then ran LLM semantic extraction (graphify extract ., claude-cli backend) and committed the result.

Please review the merge commit before relying on it.

# Conflicts:
#	graphify-out/GRAPH_REPORT.md
#	graphify-out/graph.json
#	graphify-out/manifest.json
Structural `graphify update` followed by LLM semantic `graphify extract` (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/32945987104
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Lopu review — batching design is right, one fatal regression in handoff

Compared 75f0d9d8 against github-actions 1cdf9900 in full. The batch
design holds up, and it targets a problem that is actively firing in this
repository right now. But there is one introduced defect that takes the whole
resolver offline, and it is fixed in my worktree for the trusted publisher.

🔴 Blocking: handoff aborts on an unbound variable — no worker is ever dispatched

In Dispatch one trusted github-actions worker per bounded PR batch, the
refactor deleted the per-PR loop that assigned
manual_retry="$(jq -r '.manual_retry' <<<"$selected")" and pinned the payload
to the constant manual_retry:false — but left the binding behind:

              --arg routing_proof_issued_at "$ROUTING_PROOF_ISSUED_AT" \
              --argjson manual_retry "$manual_retry" '

The block runs set -euo pipefail, so -u makes that expansion fatal. I pulled
the step's exact run: script out of the YAML and executed it with a
representative detector payload and a stubbed gh:

Dispatching 2 selected PR(s), #68 through #414, to one fixed-control-plane worker.
handoff.sh: line 17: manual_retry: unbound variable
exit=1
captured dispatch payload: (empty — no dispatch was ever made)

Every detector wave — push, schedule, PR lifecycle, comment, check_run — would
die in handoff before the first .../dispatches call. No conflict resolution,
no stacked-PR cascade, no batch worker. bash -n cannot catch it; the syntax is
valid and the failure is at runtime.

shellcheck -i SC2154 over all 46 extracted run: blocks flags exactly one
finding on this head and zero on the base — worth knowing given the PR notes
that main's actionlint run excludes "pre-existing informational ShellCheck
findings". This one was neither pre-existing nor informational.

Fixed in the worktree:

  1. Removed the dead --argjson manual_retry "$manual_retry" line — the jq
    program already hardcodes manual_retry:false and per-PR intent lives inside
    the batch.
  2. Removed manual_retry_for_pr(), which this PR adds to detect and never
    calls (the real lookup is the $batch expression in the conflicting jq) —
    a second, divergent source of truth for the same value.
  3. Added one scoped guard to resolve-pr-conflicts-routing-contract.mjs, beside
    the existing batch assertions:
assert.doesNotMatch(
  source,
  /--argjson manual_retry "\$manual_retry"/u,
  "batch dispatchers pin top-level manual_retry to false instead of a per-PR variable",
);

Scoped to the lowercase per-PR variable on purpose — the detector's own
--argjson manual_retry "$MANUAL_RETRY" in the conflicting query is correct.
I re-introduced the exact regression in a scratch copy to confirm the guard
bites, then restored.

After the fix the same harness dispatches cleanly:

inputs.pr_number    = ""
inputs.manual_retry = false
inputs.pr_batch_b64 -> [{"manual_retry":true,"number":68},{"manual_retry":false,"number":414}]

Sorted, deduplicated, canonical key order, per-PR retry preserved — and that
exact payload is accepted by the worker's inline validator and by route()
(batchSize: 2, selector: "batch:2", concurrency: "resolve-worker-<run>").
Unsorted, wrong-key-order, duplicate-number and non-base64 batches are all
rejected by both ends.

🟡 The one red check is not this PR

control-plane / Lopu builds and repairs the all branch reports fail, but the
job's real conclusion is cancelled and it ran zero steps — it spent its
whole 32m11s waiting in the shared lopu-agent-fleet-<repo> queue. It was
cancelled alongside 13 other all-branch runs on 9 unrelated branches inside a
22-second window (09:50:31 → 09:50:53), in creation order, right after an
unrelated resolver released the fleet slot at 09:50:21; the oldest queued entry
(08:25:07) was then admitted and succeeded at 09:55:01. The run's
referenced_workflows points at all-branch.yml@github-actions under a
control-plane / job prefix, so the file that executed is the listener on
main/develop, not anything on this branch. Nothing here is reproducible from
this diff, and I changed nothing on this branch for it.

🔎 And this PR is the fix for half of that

Two things filled the queue:

  1. What you are fixing. The 09:41:59 detector wave fanned out six separate
    runs — Lopu resolves PR #68 / #180 / #388 / #398 / #410 / #414 from the control plane — each holding its own durable fleet entry. All six were
    cancelled. Under this PR that wave is one worker run with a canonical six-PR
    batch and max-parallel: 3, so it occupies ≤3 slots instead of six run
    fan-outs. queue: max caps a group at 100 pending and cancels the surplus,
    so that reduction is exactly the right lever.

  2. What is still outside this PR. handoff_all_branch_event already funnels
    every automatic union signal into one coalesced dispatch
    (branch: lopu-internal-all-branch) precisely "so a burst of PR lifecycle
    events never creates one durable model-fleet waiter per PR", and
    build-all-branch.mjs keeps the github-actions copy of all-branch.yml
    workflow_call-only. But main and develop still carry a Build all branch listener wired straight onto push, pull_request_target (7 types),
    schedule "53 * * * *" and workflow_dispatch, calling
    all-branch.yml@github-actions directly with no concurrency group — one
    durable fleet waiter per PR event, which is the thing that contract exists to
    prevent. The github-actions controller itself is already correct, so
    there's nothing for me to change there; retiring that listener is a
    main/develop change I won't push from a review session.

✅ Design review — no further changes needed

  • Both validators enforce the same canonical form: size caps, base64 round-trip,
    fatal: true UTF-8 decode, exact key set and order, types, strict
    sort/uniqueness, and JSON.stringify(value) === text.
  • Authority is unchanged: pr_batch_b64 is mutually exclusive with pr_number,
    requires detector_handoff + bot actor + control-plane revision + empty
    branch; a human-supplied batch lands on resolve-invalid-batch-<run_id> with
    modelAndResolve: false. BATCH_JSON only reaches jq through --argjson.
    Extending the promotion mutual-exclusion check with ORDINARY_BATCH is right.
  • max-parallel: 3 is the right call, and worth saying out loud: the fleet runs
    one job at a time repository-wide, so a 200-PR batch drains serially. That's
    affordable only because admission revalidates state, ownership, both refs,
    labels and live SHAs before checkout/secrets/AI — which it does.
  • The <<'NODE' heredoc terminator lands in column 0 after YAML block-scalar
    stripping. Verified, not assumed.
  • Nit, no change made: sort_by | unique_by keeps the first duplicate (jq
    1.7), so a repeated PR number with differing retry intent could drop a
    manual_retry: true. Theoretical while PRS comes from one detector query.
  • graphify-out/** intentionally left alone — it's an mtime/AST-hash cache the
    resolver refreshes post-merge, and control-plane-ci.yml has no graphify gate.

No open CodeQL alerts on this head, so nothing to fix or dispose there.

Full contract suite green after my changes: routing contract, control-plane
contract, all-branch, develop-preview (82/82), promotion routing/worker/changelog,
promote-features, electron release, credential classifier, rebase ownership,
node --check across all 10 scripts, Python + Ruby YAML parse, bash -n on all
46 run blocks, and git diff --check.

Fix the handoff line and this is good to land.

— Lopu, Thingtime's PR manager

@github-actions

Copy link
Copy Markdown
Contributor

Lopu repository review

Lopu reviewed this PR against github-actions as Thingtime's principal PR and repository manager. Backend: Claude Code default.

Lopu made justified improvements and pushed 00d9348 to codex/batch-pr-conflict-resolutions.

Lopu review — PR #234 ci: batch PR conflict resolution workers

Compared: head 75f0d9d8 against base github-actions 1cdf9900 (full
worktree diff, not just the incremental push).
Scope of the diff: .github/workflows/resolve-pr-conflicts.yml,
.github/scripts/resolve-pr-conflicts-routing-contract.mjs,
.github/scripts/workflow-control-plane-contract.mjs, plus regenerated
graphify-out/** (14 files, +8412 / −7005).

Verdict

The design is sound and it is aimed at a real, currently-firing production
problem. One introduced defect is fatal to the whole resolver pipeline and
is fixed in this worktree. The single red check is not caused by this PR.

Blocking defect found and fixed

handoff aborts with an unbound variable — no worker is ever dispatched

.github/workflows/resolve-pr-conflicts.yml, job handoff, step Dispatch one
trusted github-actions worker per bounded PR batch
.

The batch refactor deleted the per-PR loop that assigned
manual_retry="$(jq -r '.manual_retry' <<<"$selected")", and changed the
dispatch payload to the constant manual_retry:false — but left the jq binding
--argjson manual_retry "$manual_retry" behind. The block runs under
set -euo pipefail, so -u makes that expansion fatal.

Reproduced by extracting the step's exact run: script from the YAML and
executing it with a representative detector payload and a stubbed gh:

Dispatching 2 selected PR(s), #68 through #414, to one fixed-control-plane worker.
handoff.sh: line 17: manual_retry: unbound variable
exit=1
captured dispatch payload: (empty — no dispatch was ever made)

Impact: every detector wave (push, schedule, PR lifecycle, comment, check_run)
dies in handoff before the first gh api .../dispatches call. No conflict
resolution, no stacked-PR cascade, no batch worker — on the control-plane branch
this PR targets. bash -n cannot see it (the syntax is valid); it is a runtime
set -u failure.

Change made: removed the dead --argjson manual_retry "$manual_retry" line.
The jq program already hardcodes manual_retry:false; per-PR retry intent lives
inside the canonical batch.

Post-fix validation (same harness, plus the duplicate-PR case):

exit=0
inputs.pr_number       = ""
inputs.manual_retry    = false
inputs.pr_batch_b64    -> [{"manual_retry":true,"number":68},{"manual_retry":false,"number":414}]

Sorted, deduplicated, canonical key order, per-PR retry preserved.

Second change — dead code from the same refactor

manual_retry_for_pr() was added to the detect step and is called nowhere
(confirmed across every .yml / .mjs / .sh / .md in the tree). Per-PR
retry is actually resolved by the $batch lookup inside the conflicting jq
program. Leaving the helper in place is a second, divergent source of truth for
the same value. Removed.

Third change — regression guard

Added one assertion to .github/scripts/resolve-pr-conflicts-routing-contract.mjs
next to the existing batch-payload assertions:

assert.doesNotMatch(
  source,
  /--argjson manual_retry "\$manual_retry"/u,
  "batch dispatchers pin top-level manual_retry to false instead of a per-PR variable",
);

Deliberately scoped to the lowercase per-PR shell variable — the detector's own
--argjson manual_retry "$MANUAL_RETRY" in the conflicting query is correct
and must keep working. Proved the guard actually bites by re-introducing the
exact regression in a scratch copy (contract fails with the new message) and
then restoring (contract passes).

Failing check: diagnosed as an external, repository-wide queue eviction

control-plane / Lopu builds and repairs the all branch — run 32952298941,
job 98126202539.

Facts from the live check state and API:

  • The job's real conclusion is cancelled, not failure; gh pr checks
    renders that as fail.
  • It executed zero steps (steps: []). It sat in the shared
    lopu-agent-fleet-<repo> job concurrency queue for its entire 32m11s.
  • It was cancelled together with 13 other all-branch runs across 9 unrelated
    branches
    , all inside a 22-second window (09:50:31 → 09:50:53), in strict
    creation order — immediately after an unrelated resolver run released the
    fleet slot at 09:50:21. The oldest queued entry (created 08:25:07) was then
    admitted and completed successfully at 09:55:01.
  • The executed workflow file is not this branch's. The run's
    referenced_workflows is lopugit/thingtime/.github/workflows/all-branch.yml@github-actions
    and the check is prefixed control-plane /, i.e. the caller is the legacy
    Build all branch listener that still exists on main and develop. This PR
    does not touch all-branch.yml or any fleet concurrency declaration.

Not a defect in PR #234, not reproducible from its diff, and no fix was made on
this branch for it.

Why the queue overflowed — and how it relates to this PR

Two independent contributors, both visible in the run history:

  1. The problem this PR fixes. The 09:41:59 detector wave fanned out six
    separate workflow runs
    Lopu resolves PR #68 / #180 / #388 / #398 / #410 / #414 from the control plane (09:42:38 → 09:42:50) — each holding its own
    durable fleet-queue entry. All six were cancelled. Under this PR that same
    wave becomes one worker run carrying a canonical six-PR batch, capped at
    three concurrent matrix jobs, so it occupies ≤3 queue slots instead of 6 run
    fan-outs. queue: max allows at most 100 pending entries per group and
    cancels the surplus, so this reduction is exactly the right lever.

  2. A stale listener, outside this PR's reach. The github-actions control
    plane already routes every automatic union signal through
    handoff_all_branch_event → one coalesced bot workflow_dispatch
    (branch: lopu-internal-all-branch, maintenance_operation: manage-prs) →
    maintain_all_branch, precisely "so a burst of PR lifecycle events never
    creates one durable model-fleet waiter per PR", and
    build-all-branch.mjs enforces that the github-actions copy of
    all-branch.yml stays workflow_call-only. But main and develop still
    carry a Build all branch listener wired straight onto push,
    pull_request_target (7 types), schedule "53 * * * *" and
    workflow_dispatch, calling all-branch.yml@github-actions directly with no
    concurrency group — one durable fleet waiter per PR event, which is what the
    contract exists to prevent.

The trusted github-actions controller files are already correct, so there is
nothing to change in the protected controller checkout and no
lopu-workflow-fix.md was written. Retiring or coalescing that listener is a
change to main/develop, which this session must not push.

Review of the batch design itself (no changes needed)

  • Canonical encoding is genuinely canonical. Both validators — the worker's
    inline Node block and decodeBatch() in the routing contract — enforce
    length caps, the standard base64 alphabet, a base64 round-trip, fatal: true
    UTF-8 decode, exact key set and order (manual_retry,number), boolean +
    positive safe-integer types, strict sort/uniqueness, and
    JSON.stringify(value) === text. Verified end to end: the batch the fixed
    handoff emits is accepted by the worker validator and by route()
    (valid, internalWorker, batchSize: 2, selector: "batch:2",
    concurrency: "resolve-worker-999"). Tampered inputs — wrong key order,
    unsorted, duplicated numbers, non-base64 — are all rejected.
  • Authority is unchanged. pr_batch_b64 is mutually exclusive with
    pr_number, requires detector_handoff, a bot actor, the control-plane
    revision, and an empty branch; a human-supplied batch routes to
    resolve-invalid-batch-<run_id> with modelAndResolve: false. BATCH_JSON
    only ever reaches jq via --argjson, never shell interpolation. The promotion
    path's mutual-exclusion check was correctly extended with ORDINARY_BATCH.
  • max-parallel: 3 is the right call, and worth stating explicitly: the
    fleet group runs one job at a time repository-wide, so a 200-PR batch drains
    strictly serially. That is affordable because the admission step
    revalidates PR state, repository ownership, both refs, ownership labels and
    live head/base SHAs before checkout, secrets, AI or Graphify spend, so a
    long stale tail costs almost nothing.
  • Heredoc indentation is correct. The <<'NODE' body is de-indented so
    that, after YAML block-scalar stripping, the NODE terminator lands in column
    0. Confirmed by extracting all 46 run: blocks and parsing them.
  • Minor, no change made: sort_by(.number) | unique_by(.number) keeps the
    first duplicate (verified with jq 1.7), so if a detector ever emitted the
    same PR twice with different retry intent, a manual_retry: true could lose
    to an earlier false. The PRS source is a single detector query, so this is
    theoretical today.
  • graphify-out/** deliberately not regenerated. The manifest is an
    mtime/AST-hash cache refreshed by the resolver's own post-merge
    graphify update / graphify extract step; control-plane-ci.yml has no
    graphify gate, and the graphify CLI is not present in this session. Leaving it
    alone matches how the repository maintains it.

CodeQL

The trusted snapshot for this head (lopu-codeql-alerts/234.json) contains no
open alerts
. lopu-codeql-dispositions/234.json is left as [] — nothing to
fix and nothing to dispose.

Validation run

All commands run in the PR worktree at 75f0d9d8 after the changes above.

Check Result
resolve-pr-conflicts-routing-contract.mjs --self-test pass
workflow-control-plane-contract.mjs --self-test pass
build-all-branch.mjs --self-test pass
deploy-develop-pr-preview.mjs --self-test pass (82/82)
promotion-worker-routing-contract.mjs pass
promotion-pr-changelog.mjs --self-test pass
promote-features-to-main.mjs --self-test pass
electron-pr-release-contract.mjs pass
classify-claude-credential-failure.mjs --self-test pass
rebase-ownership-routing-contract.sh pass
promotion-worker-contract.sh pass
node --check over all 10 control-plane scripts pass
Python + Ruby YAML parse of resolve-pr-conflicts.yml pass
bash -n over all 46 extracted run: blocks pass
shellcheck -i SC2154 over all 46 blocks 1 finding before the fix, 0 after; base 1cdf9900 has 0
handoff step executed end to end with a stubbed gh fails with unbound variable before the fix; emits a valid canonical batch after
Emitted batch fed back through both validators + route() accepted, correct batchSize / selector / concurrency
Tamper cases (unsorted / wrong key order / duplicate / non-base64) all rejected
git diff --check clean

shellcheck -i SC2154 across all 46 run blocks reported exactly one finding on
the incoming head and zero on the base — a precise, cheap signal that the
main actionlint job's blanket ShellCheck suppression happened to hide a new
fatal finding rather than pre-existing noise.

Uncommitted worktree changes left for the trusted publisher

  • .github/workflows/resolve-pr-conflicts.yml — drop the leftover
    --argjson manual_retry "$manual_retry"; drop the unused
    manual_retry_for_pr() helper.
  • .github/scripts/resolve-pr-conflicts-routing-contract.mjs — add the
    regression guard.

View Lopu workflow run

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

🤝 Merged github-actions into codex/batch-pr-conflict-resolutions — conflicts auto-resolved by Lopu's PR manager.

No AI resolution was needed by merge time; the branch was updated with a plain merge commit.

graphify-out/ was reset wholesale to the github-actions side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).

Rebuilt the merged code graph first with graphify update ., then ran LLM semantic extraction (graphify extract ., claude-cli backend) and committed the result.

Please review the merge commit before relying on it.

github-actions Bot and others added 4 commits August 26, 2026 20:24
# Conflicts:
#	graphify-out/GRAPH_REPORT.md
#	graphify-out/graph.json
#	graphify-out/manifest.json
Structural `graphify update` followed by LLM semantic `graphify extract` (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/32980911546
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

🤝 Merged github-actions into codex/batch-pr-conflict-resolutions — conflicts auto-resolved by Lopu's PR manager.

No AI resolution was needed by merge time; the branch was updated with a plain merge commit.

graphify-out/ was reset wholesale to the github-actions side (repo rule: one side, never mixed — the graph merge driver is unavailable in CI).

Rebuilt the merged code graph first with graphify update ., then ran LLM semantic extraction (graphify extract ., claude-cli backend) and committed the result.

Please review the merge commit before relying on it.

github-actions Bot and others added 5 commits August 26, 2026 23:15
# Conflicts:
#	graphify-out/GRAPH_REPORT.md
#	graphify-out/graph.json
#	graphify-out/manifest.json
Structural `graphify update` followed by LLM semantic `graphify extract` (graphify 0.9.4, claude-cli backend); unchanged content served from the tracked semantic cache.

Refreshed by the resolve-pr-conflicts workflow: https://github.com/lopugit/thingtime/actions/runs/33017021640
@lopugit lopugit changed the title ci: batch PR conflict resolution workers Batch PR conflict workers and activate Lopu fixes Aug 26, 2026
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