| name | pullrequest |
| description | Open a PR and merge it โ and the NON-NEGOTIABLE rule that the PR's CI must be GREEN before you merge, because the pull-based self-update (memex-local autoroll + the AKS portals) deploys main's image. A red or still-pending main blocks the self-update from rolling forward and can wedge the deployment. Use whenever you create/review/merge a PR, when a merge "went through" but CI later failed, or when main is red and the auto-update is stuck. Covers the exact gh/API commands, the automatic ruleset-driven Copilot review (never hand-request or withdraw it; a 402 quota error leaves the PR unreviewed), the merge-only-when-green gate, and the half-committed-WIP trap that turns main red on a clean CI checkout. |
| user-invocable | true |
| allowed-tools | ["Bash","Read","Grep"] |
/pullrequest โ open โ wait for CI โ merge, and NEVER merge a red or pending main
๐จ๐จ๐จ The one rule: main must be GREEN before you merge
The pull-based self-update deploys main. The memex-local autoroll watches the moving
*-local:latest image, and the AKS portals (atioz / memex / memex-cloud) self-roll to the latest
green CI image. So main's CI is not a formality โ it is the source of the image that ships.
- Red main (build/test failure) โ no valid image is produced โ the self-update cannot roll
forward (it's stuck on the last good version) and the auto-update is failing. If a broken
image does get published, the portals CrashLoop / 502 โ the wedge.
- Therefore: NEVER merge with CI pending or red. Merging before CI finishes is the exact
mistake that turned main red after #136 (merged with "Build solution" still pending; it then
failed on a clean-checkout
-warnaserror build, and the self-update was blocked until the
hotfix landed). The merge succeeding is not the goal โ a green main after the merge is.
If you only remember one thing: poll the CI via GraphQL until the check SUITE is COMPLETED, and
merge only on conclusion == SUCCESS (step 3). Do NOT use gh run watch โ it polls REST and
drains the shared token budget into 403s that masquerade as CI-red.
The procedure
git status --porcelain | grep '^??'
dotnet build src/<TheProjectYouTouched> -c Release -warnaserror --no-restore
DATE=$(date -u +%Y-%m-%d)
cat > src/MeshWeaver.Documentation/Data/WhatsNew/${DATE}-<slug>.md <<'NOTE'
---
Name: <short human title of the change>
Category: What's New
Description: <one-line summary shown in the What's New list>
Icon: Sparkle
---
<2โ5 plain-language sentences on what changed and why it matters to a user โ not the how.>
NOTE
git add src/MeshWeaver.Documentation/Data/WhatsNew/${DATE}-<slug>.md
git push -u origin "$(git branch --show-current)"
gh pr create --base main --head "$(git branch --show-current)" --title "โฆ" --body "โฆ"
PR=<PR>
Q='query($o:String!,$r:String!,$p:Int!){repository(owner:$o,name:$r){pullRequest(number:$p){commits(last:1){nodes{commit{checkSuites(first:20){nodes{status conclusion workflowRun{workflow{name}}}}}}}}}}'
suite(){ gh api graphql -f query="$Q" -f o=Systemorph -f r=MeshWeaver -F p=$PR \
--jq "[.data.repository.pullRequest.commits.nodes[0].commit.checkSuites.nodes[]|select(.workflowRun.workflow.name==\"MeshWeaver Build and Test\")]|last|.$1 // empty"; }
until [ "$(suite status)" = "COMPLETED" ]; do sleep 45; done
c=$(suite conclusion); echo "PR $PR CI: $c"; [ "$c" = "SUCCESS" ]
gh pr merge <PR> --merge
git fetch origin main:main
Why GraphQL, not gh run watch โ the rate limit and the late-shard race, solved together
gh run watch and gh pr checks --watch poll the REST API every few seconds. Two distinct
failures come from that, and the GraphQL poll in step 3 kills both:
- Rate limit โ false CI-red. The
gho_โฆ CLI login is a user OAuth token: 5000 REST req/hour
shared across every session and tool under your account. One 20-min run (build + 6 shards) is
hundreds of polls, and several concurrent worktree sessions drain the pool together โ 403 API rate limit exceeded, whose exit=1 looks like CI-red but is not. Never merge or abort off a 403 โ
check the reset with the exempt gh api /rate_limit and wait. GraphQL draws on a separate
5000-point budget at ~1 point/query, so a whole run costs a few dozen points; it does not compete
with your interactive gh.
- Late-shard race โ merged before tests ran.
gh pr checks --watch returns "all passed" as soon
as the currently visible checks complete, but the test shards register after Build solution
โ so it exits green while shards are still pending and you merge before tests ran (this turned main
red after #138). Gating on the check SUITE status (step 3) has no such window: the suite is
COMPLETED only when every job/shard in the run has finished.
After the merge (step 5โ6): poll main's post-merge run the SAME way โ re-target the step-3
suite() helper at the ref (repository.ref("refs/heads/main").target โฆ checkSuites) โ because a
test that only runs post-merge can still turn main red. Then git fetch origin main:main (step 6).
Durable fix for heavy parallel CI-watching: authenticate agent/automation gh as the repo's
GitHub App installation (its own rate-limit budget โ org apps up to ~15k/hr) instead of the
human's shared 5000; see the GitHub App machine-identity notes.
Push instead of poll โ why step 3 runs in the background, and the future upgrade. GitHub can't
webhook a local CLI session (no inbound endpoint), so the closest thing to "subscribe to a CI-done
event" is to run the step-3 loop as a background task: it exits when the suite completes and the
harness delivers a single completion notification โ a push from the session's POV, off the REST
budget. A true server-side push is possible but not built: the portal already receives GitHub
webhooks at POST /webhooks/github (GitHubWebhookProcessor, currently issues/issue_comment
only); adding a workflow_run branch + a raw-WS /events/ci endpoint would let a session subscribe
with the harness Monitor ws: source for zero-poll delivery. Deferred โ the background loop is
enough today.
Do not rely on gh pr merge --auto either: GitHub auto-merge only waits for required status
checks. This repo currently merges with checks pending (no branch protection), so --auto merges
immediately โ defeating the rule. Gate it yourself with the step-3 GraphQL check-suite poll.
What's New entry (step 0.5) โ one doc node per user-facing PR
The platform's What's New feed is not a hand-maintained changelog: it's the set of per-entry
markdown nodes under src/MeshWeaver.Documentation/Data/WhatsNew/ (shipped in the Doc partition,
so every self-updating deployment shows the same feed). The What's New settings tab lists them
newest-first; each entry is a normal doc node you can open.
- One file per PR (
<YYYY-MM-DD>-<slug>.md) โ the date prefix drives newest-first ordering, and
a distinct filename per PR means two concurrent PRs never conflict on the feed (the reason we do
NOT prepend to a single rolling file).
- Front-matter:
Name (title shown in the list), Category: What's New, Description
(one-liner), Icon (a Fluent icon name, e.g. Sparkle). Body is plain-language user-facing prose.
- When to skip: pure-internal PRs (refactors, tests, CI, dependency bumps) with no user-visible
change don't need an entry โ note the skip in the PR body so a reviewer knows it was deliberate.
The half-committed-WIP trap (how main went red on a clean CI)
A file that is referenced by a committed file but never git added builds fine locally (the
untracked source sits in your working tree) and fails on CI's clean checkout with
-warnaserror:
ThreadMessageBubbleView.razor(57): error CS0103: The name 'ToolCallVisibility' does not exist โฆ
ThreadMessageBubbleView.razor (committed) used ToolCallVisibility.Partition(...), but
src/MeshWeaver.Layout/ToolCallVisibility.cs was untracked. The fix is to track the missing
source file, not to revert the reference. Catch it in pre-flight:
git status --porcelain | grep '^??'
rg -l '\bToolCallVisibility\b' src | xargs git ls-files
CI builds with dotnet build --no-restore -c Release -p:CIRun=true -warnaserror โ reproduce that
exact line locally before pushing if anything feels half-committed.
Why this matters here specifically
This repo's portals auto-update from main. A green main is the contract that keeps the
self-update healthy; a red main silently stalls every portal's roll-forward. That is why the
merge gate is a hard rule, not a nicety โ see the self-update rollout notes and
DeploymentAKS.md
(auto-baked feed + self-roll) and the autoroll subcommand in deploy/homebrew/bin/memex-local.