fix(actions): Lopu repairs failed PR checks - #425
Closed
lopugit wants to merge 1 commit into
Closed
Conversation
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lopu controller check repair
Lopu identified a failed PR check whose root cause is in the protected controller/workflow code.
Lopu control-plane fix — widen the AI model waterfall grammar
Diagnosis
PR #388 (
feat(admin): unlimited AI model waterfall with full Claude + OpenAI catalog, per-entry effort and fast mode) turns the Admin "AI workflow modelorder" into an open catalog whose entries are composed ids
(
<model>[:<effort>][:fast], e.g.claude-opus-5:high:fast,gpt-5.6-sol:ultra)with no length cap.
The protected control plane still validated that setting against the historical
closed trio and a 1..3 length bound:
Because that is a single whole-array
select, one new-style entry made thewhole filter fail, so the loader fell back to
["default"]— and the bashmapping loop then hard-failed closed on the first unrecognised id as well.
Net effect once #388 ships: the moment an administrator uses the new feature at
all, the conflict resolver, the rebase/stack-cascade engine, and the all-branch
build doctor silently discard their entire model preference — including the
plain Claude entries the control plane can perfectly well run — and drop to the
Claude Code default. The PR's own live-browser test order
(
['claude-opus-5:high:fast','default','gpt-5.6-sol:ultra','gpt-5.3-codex:xhigh','claude-haiku-4-5'])reproduced exactly this:
--model default, admin preference lost.This is a control-plane defect, not a PR-branch defect: nothing in #388's
worktree can fix it, and #388's description explicitly defers it here.
Change
Three loaders (
resolve-pr-conflicts.yml,rebase-pr-stacks.yml,all-branch.yml) now share one design:(1..512 — the catalog admits 226 unique composed ids today, so a legitimate
order can never trip it;
--max-filesize 65536remains the real bound),requires uniqueness, and gates every entry on
^[a-z0-9][a-z0-9.-]{0,47}(:[a-z]{1,8}){0,2}$. No space, quote, comma, orleading dash survives that charset, which is precisely what makes a value
safe to place next to a CLI flag.
reads
.models[].providerfrom the same payload and keeps onlyanthropic/defaultbases. A pre-feat(admin): unlimited AI model waterfall with full Claude + OpenAI catalog, per-entry effort and fast mode #388 deployment publishes noproviderfield and was Claude-only, so every id it lists stays eligible — the change
is backward compatible with production today.
base="${id%%:*}") because Claude Codeconsumes a bare model id, then collapsed order-preservingly so
claude-opus-5:highandclaude-opus-5:maxyield one CLI entry.model, or one absent from the catalog) is skipped with a
::notice::; therest of the administrator's order survives. Only an empty result falls back
to
["default"].defaultis re-appended if dedup/truncation would drop it, and the emittedchain is capped at 16 with an explicit
::warning::(no silent cap) soMODEL_ARGSstays inside the downstream 2048-character audit budget evenfor a hostile catalog response.
rebase-pr-stacks.ymladditionally gains the literal-rebuild path the other twoalready had — it previously interpolated
jq -r '.[0]'straight into--model $primaryand leaned entirely on the closed name allowlist for safety.No model name is pinned anywhere. That was the point of the pre-existing
contains no legacy model/contains no hardcoded --model selectionassertions, and enumerating the eight current Claude models would have
reintroduced exactly the drifting second catalog they guard against. Both
contract scripts were updated to assert the new invariant, and the model-name
check is now a negative assertion (
pins no model id) — strictly strongerthan the positive allowlist it replaces.
Validation
python3 -c "yaml.safe_load(...)"on all three workflows — parse clean.node .github/scripts/workflow-control-plane-contract.mjs --self-test— OK.node .github/scripts/resolve-pr-conflicts-routing-contract.mjs --self-test— OK.Behavioural test of the real step scripts, extracted from the YAML with
only
curlswapped for a fixture copy, run for all three loaders:["default"]--model default(unchanged)--model claude-opus-5 --fallback-model claude-fable-5,default(unchanged)--model claude-opus-5 --fallback-model default,claude-haiku-4-5+::notice::Skipped 2— was--model default--model default+ skip noticeclaude-opus-5:high+claude-opus-5:maxclaude-opus-5provider)--model claude-opus-5 --fallback-model default"claude-opus-5; rm -rf /"default"--dangerously-skip-permissions"defaultdefaultdefaultlast::warning::,defaultpreserved,MODEL_ARGS774 charsNot changed
Effort and fast-mode segments are parsed and stripped, not forwarded: the
Claude Code CLI handoff in this plane takes
--modelonly, and its--effortis set per-invocation by the workflow. Plumbing per-entry effort through to the
CLI is a separate change with its own argument-grammar surface.
Source Lopu workflow run