You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The rhodibot.yml migration to the report-only canary is 39 of 111 done. The
other 72 repos still run the old MUTATING variant on a weekly cron, with contents: write + pull-requests: write, and 71 of those 72 bulk-rewrite SPDX
headers — which the standing licence policy forbids.
⚠ CORRECTED 2026-09-09 — twice. See the correction comment below. This issue
originally said "Nothing has landed. Zero rhodibot PRs exist in any state."That was
false, and it came from gh search prs --state merged, a flag value gh rejects, with
stderr suppressed. The first correction then repeated the same error class — reporting a sample as a census. Both are now fixed. Every number below is a complete census:
all 37 repos enumerated by head.ref prefix, all 121 merged PRs resolved file-by-file,
stderr visible, positive control passed.
151 rhodibot PRs exist. 121 are MERGED into main across 36 repos (a 37th, vcl-ut, has one PR, unmerged). The remaining 30 are closed — 0 are open, so
there is nothing to close today. Merges span 2026-04-03 → 2026-08-24.
What landed, all 121 PRs, complete: 107 × CONTRIBUTING.adocremoved; 9 × .gitignore + 9 × .gitattributes modified (all 18 on the-nash-equilibrium); 4 × CONTRIBUTING.md added; 2 × SECURITY.md added; 1 × codeql.yml modified
(verisimiser). No merged PR touched LICENSE, REUSE.toml, or any other SPDX
header. The forbidden AGPL → MPL-2.0 rewrite (defect 1) has never landed.
A different SPDX defect did: a non-idempotents/AGPL-3.0/AGPL-3.0-or-later/g,
present in 3 files (2 live repos). It merged nine times on the-nash-equilibrium,
compounding to AGPL-3.0-or-later-or-later-or-later-or-later.
⏰ CORRECTED THREE TIMES — read the third correction comment before acting. The
defect is real and permanent, but it is not landing on main: Create PR with fixes
has failed with a deterministic quoting bug (exit 127) on both 2026-08-31 and 2026-09-07,
nothing has landed since 08-24, and main is clean today.
It is still pushing. The step fails aftergit push, so a corrupt branch is created
every week and both survive on the remote carrying AGPL-3.0-or-later-or-later: rhodibot/rsr-compliance-20260831 and rhodibot/rsr-compliance-20260907. Do not merge
either.main is clean by accident of a broken step, not by containment.
Ordering, not urgency, is the point. The disable below stops the weekly corrupt push
and is cheap insurance; run it before anyone repairs step 4, because that one-line
quoting fix looks cosmetic and re-arms corruption of main the same day: gh api -X PUT repos/hyperpolymath/the-nash-equilibrium/actions/workflows/rhodibot.yml/disable
Recommendation (a) is unchanged: this mechanism has a demonstrated ability to merge
deletions into main, and one repo generates an invalid-SPDX commit on a timer.
Measured 2026-09-09
Fact
Value
How
rhodibot*.yml files on disk
180
find, no maxdepth
…at an actual git repo root
111
tested -e "$d/.git"
…nested copies, never read by GitHub
69
remainder
Live files ACTIVE on GitHub
103
actions/workflows state, compared to the exact string active
Live files that are the safe canary
39
no gh pr create, permissions: contents: read
Live files that are the MUTATING variant
72
gh pr create present
…of those, bulk-rewrite SPDX
71
sed -i 's/AGPL
…delete files by glob
72
rm "
…carry the injection sink
72
${{ steps.fix.outputs.FIXES }} into a run:
…hardcode a personal e-mail
72
j.d.a.jewell@open.ac.uk
rhodibot PRs that exist
151 — 121 MERGED across 36 repos, 30 closed, 0 open
pulls?state=all filtered on head.ref prefix rhodibot/; the original 0 was wrong, and the first correction's 44 repos was also wrong (it counted 7 API-error rows as repos)
Distinct content variants
53
sha256sum; largest cluster 19
Not a stale-tree artefact. Spot-checked contents/.github/workflows/rhodibot.yml?ref=main
on bofj-kitt, contractiles, cookie-rebound, krl, panoply — all five are the
MUTATING variant on main right now.
Orphan branches. Sampling 10 active repos found rhodibot/rsr-compliance-* branches on
4 of them (bofj-kitt 11, contractiles 2, cookie-rebound 2, krl 2) with no PR —
the bot pushed the branch and no PR was opened for these particular branches. PR
creation worked in general — 151 were opened — so this is not the blanket mechanism the
first draft claimed. Still plausibly a large share of the ~170 orphan branches in the
branch-cleanup backlog (task #30), though I have not counted the total across all 111 repos.
One sampled branch (bofj-kitt, rhodibot/rsr-compliance-20260908) contains exactly one
change: CONTRIBUTING.adoc removed.
Defects in the MUTATING variant
Bulk SPDX rewrite on a weekly cron — sed -i 's/AGPL-3.0-or-later/MPL-2.0/g' over .gitignore, .gitattributes, .editorconfig. Directly contrary to the standing rule
that licence text is never swept and never changed without prior approval.
The third-party/fork check runs after the SPDX sweep, so vendored or forked licence
text is rewritten before anything decides whether the repo was in scope.
Comment and code disagree — the comment says "AGPL → PMPL"; the code writes MPL-2.0.
Script-injection sink — ${{ steps.fix.outputs.FIXES }} is interpolated straight into
a run: block. The content is repo-derived filenames, so it is attacker-influenceable in
any repo that accepts contributions.
Hardcoded personal e-mail written into generated SECURITY.md / CONTRIBUTING.md.
Nothing calls the real rhodibot. Every one of the 111 has exactly one uses: — actions/checkout. The logic is inlined bash, duplicated 53 ways.
The fix already exists
hyperpolymath/rsr-template-repo ships the canary at .github/workflows/rhodibot.yml (5,038 bytes). It is report-only: permissions: contents: read, no rm, no push, no PR. Its own header states the intent —
Rhodibot does NOT mutate this repository… if rhodibot "feels the desire to edit"… that is
itself a MAJOR WARNING… Rhodibot must never edit a licence header (estate directive).
So this is a half-finished propagation of an already-approved design, not a new
design problem. (Minor: the template has a duplicated # This workflow is managed by gh actions-lock. line; worth fixing in the same pass.)
The decision
Option
What it means
(a) Finish the canary migration(recommended)
Propagate the template canary to the remaining 72 as an ordinary PR-per-repo sweep. Removes the mutation, keeps the drift signal, needs no new design.
(b) Disable the 103 now, migrate later
Stops the cron immediately, but disable-ci-checks is on the trust-deny list — owner-run or explicitly authorised, not something an agent does on its own judgement. Reasonable as a stopgap before (a) if the weekly window is a concern.
(c) Delete the workflow from the 72
Simplest, loses the drift signal the canary was built to provide.
(d) Leave running and fix gh pr create
Originally described here as "the option that makes the SPDX sweep actually land". That framing rested on the false zero: PR creation already works. What has limited the blast radius is that the destructive sed branch has not been triggered. Named so it is not chosen by silence.
Separately: the 69 nested copies are dead files that GitHub never reads. They can be deleted
with no CI effect, but that is >5 files and therefore a bulk-delete question of its own.
Not asked here
Wiring these to the real gitbot-fleet/bots/rhodibot is a different question and is not
in this decision. It is also mis-scoped in the current backlog as "93 workflows to wire" —
the true figure is 111 live inline scripts calling no bot at all.
⏰ Dated recurrence — the-nash-equilibrium generates a corrupt commit every Monday 06:00 UTC
This is the one item with a clock on it, and my earlier "repaired; clean today" understated
it. Measured on main 2026-09-09:
.github/workflows/rhodibot.yml state is active; trigger cron: '0 6 * * 1'.
Its sweep is guarded by grep -q "AGPL-3.0" "$dotfile". The dotfiles currently read AGPL-3.0-or-later — which contains the string AGPL-3.0, so the guard passes.
The sed then runs s/AGPL-3.0-or-later/AGPL-3.0-or-later/g; s/AGPL-3.0/AGPL-3.0-or-later/g.
The second clause rewrites the AGPL-3.0prefix inside the already-correct identifier,
appending another -or-later. The guard can never stop it and the fix is its own input.
Next firing Monday 2026-09-14 06:00 UTC — expected to push a third corrupt branch and,
as on 08-31 and 09-07, to fail before opening a PR. It rewrites .gitignore and .gitattributes to AGPL-3.0-or-later-or-later, an invalid SPDX identifier that fails
REUSE, commits it and pushes it; only gh pr create is unreachable. See the third
correction comment for the measured failure and why repairing it re-arms main.
The same non-idempotent sed is in game-server-admin, also active on the same cron,
but there it will not fire: that repo's dotfiles read SPDX-License-Identifier: MPL-2.0,
so the AGPL-3.0 guard is false.
Which surfaces a separate, pre-existing inconsistency worth a ruling: game-server-admin
has an AGPL-3.0-or-laterLICENSE file but MPL-2.0 SPDX headers in its dotfiles, and
GitHub reports its licence as NOASSERTION. None of its 3 merged rhodibot PRs touched
those files (all three deleted CONTRIBUTING.adoc), so rhodibot's merged PRs are not the
vector. How the MPL-2.0 got there is unresolved and I have not guessed.
Disabling that one workflow stops the only bleeding on a timer and is independent of the
larger (a)/(b)/(c)/(d) decision.
The one-line stop, written out so it need not be reconstructed
I have not run this. Disabling a workflow is on the estate trust-deny list
(disable-ci-checks), so it is an owner action, not mine. But it is one reversible call and
the precondition arrives on a date, so the command belongs here rather than in someone's
reconstruction of it:
# STOP the weekly corrupt push (reversible, no code change, no PR).# Run this BEFORE repairing "Create PR with fixes" — that repair re-arms main the same day:
gh api -X PUT repos/hyperpolymath/the-nash-equilibrium/actions/workflows/rhodibot.yml/disable
# verify it took (expect: disabled_manually):
gh api repos/hyperpolymath/the-nash-equilibrium/actions/workflows/rhodibot.yml --jq .state
# to undo at any time:
gh api -X PUT repos/hyperpolymath/the-nash-equilibrium/actions/workflows/rhodibot.yml/enable
Workflow id 256226305, name 🤖 Rhodibot — RSR Auto-Fix, state re-verified active at
2026-09-09 02:08 UTC. This disables one workflow in one repo; it changes nothing about the
other 110 and pre-empts none of the (a)/(b)/(c)/(d) decision. It is safe to run before that
decision and safe to reverse after it.
Why it is spelled out rather than merely flagged. A ruling that lives only in an issue
goes unread precisely when its precondition arrives — this estate has the trap on file
(standards#331 sat twelve days). The cron does not care whether this issue has been read.
Raised by a peer session reviewing the A6 batch; the judgement to leave the call to the owner
is mine.
Two provenance caveats, stated rather than smoothed over
Merge attribution.merged_by reads hyperpolymath on the 5 PRs I sampled. I have not censused this, and the REST auto_merge field is populated only while auto-merge is pending — on an already-merged PR it is always null, so "auto=false" was guaranteed by
construction and proves nothing. merged_by also names the enabler under auto-merge.
Combined with the shared PAT, actor.login cannot distinguish the owner from an agent
session. Treat "the owner accepted these" as unproven.
Orphan branches. The rhodibot/rsr-compliance-* branches with no PR are real, but they
are not evidence of a blanket PR-creation failure — 151 PRs were opened. I previously drew
the opposite conclusion from them. A mechanism that explains a measurement is not
evidence for it.
Summary
The
rhodibot.ymlmigration to the report-only canary is 39 of 111 done. Theother 72 repos still run the old MUTATING variant on a weekly cron, with
contents: write+pull-requests: write, and 71 of those 72 bulk-rewrite SPDXheaders — which the standing licence policy forbids.
Measured 2026-09-09
rhodibot*.ymlfiles on diskfind, no maxdepth-e "$d/.git"actions/workflowsstate, compared to the exact stringactivegh pr create,permissions: contents: readgh pr createpresentsed -i 's/AGPLrm "${{ steps.fix.outputs.FIXES }}into arun:j.d.a.jewell@open.ac.ukpulls?state=allfiltered onhead.refprefixrhodibot/; the original0was wrong, and the first correction's44 reposwas also wrong (it counted 7 API-error rows as repos)sha256sum; largest cluster 19Not a stale-tree artefact. Spot-checked
contents/.github/workflows/rhodibot.yml?ref=mainon
bofj-kitt,contractiles,cookie-rebound,krl,panoply— all five are theMUTATING variant on
mainright now.Orphan branches. Sampling 10 active repos found
rhodibot/rsr-compliance-*branches on4 of them (
bofj-kitt11,contractiles2,cookie-rebound2,krl2) with no PR —the bot pushed the branch and no PR was opened for these particular branches. PR
creation worked in general — 151 were opened — so this is not the blanket mechanism the
first draft claimed. Still plausibly a large share of the ~170 orphan branches in the
branch-cleanup backlog (task #30), though I have not counted the total across all 111 repos.
One sampled branch (
bofj-kitt,rhodibot/rsr-compliance-20260908) contains exactly onechange:
CONTRIBUTING.adocremoved.Defects in the MUTATING variant
sed -i 's/AGPL-3.0-or-later/MPL-2.0/g'over.gitignore,.gitattributes,.editorconfig. Directly contrary to the standing rulethat licence text is never swept and never changed without prior approval.
text is rewritten before anything decides whether the repo was in scope.
MPL-2.0.${{ steps.fix.outputs.FIXES }}is interpolated straight intoa
run:block. The content is repo-derived filenames, so it is attacker-influenceable inany repo that accepts contributions.
mutations" guardrail (same class as [charter] git-logistics-office — the estate control plane (repo lifecycle: birth, enrollment, life, death) #636).
SECURITY.md/CONTRIBUTING.md.uses:—actions/checkout. The logic is inlined bash, duplicated 53 ways.The fix already exists
hyperpolymath/rsr-template-repoships the canary at.github/workflows/rhodibot.yml(5,038 bytes). It is report-only:permissions: contents: read, norm, no push, no PR. Its own header states the intent —So this is a half-finished propagation of an already-approved design, not a new
design problem. (Minor: the template has a duplicated
# This workflow is managed by gh actions-lock.line; worth fixing in the same pass.)The decision
disable-ci-checksis on the trust-deny list — owner-run or explicitly authorised, not something an agent does on its own judgement. Reasonable as a stopgap before (a) if the weekly window is a concern.gh pr createsedbranch has not been triggered. Named so it is not chosen by silence.Separately: the 69 nested copies are dead files that GitHub never reads. They can be deleted
with no CI effect, but that is >5 files and therefore a bulk-delete question of its own.
Not asked here
Wiring these to the real
gitbot-fleet/bots/rhodibotis a different question and is notin this decision. It is also mis-scoped in the current backlog as "93 workflows to wire" —
the true figure is 111 live inline scripts calling no bot at all.
⏰ Dated recurrence —
the-nash-equilibriumgenerates a corrupt commit every Monday 06:00 UTCThis is the one item with a clock on it, and my earlier "repaired; clean today" understated
it. Measured on
main2026-09-09:.github/workflows/rhodibot.ymlstate isactive; triggercron: '0 6 * * 1'.grep -q "AGPL-3.0" "$dotfile". The dotfiles currently readAGPL-3.0-or-later— which contains the stringAGPL-3.0, so the guard passes.s/AGPL-3.0-or-later/AGPL-3.0-or-later/g; s/AGPL-3.0/AGPL-3.0-or-later/g.The second clause rewrites the
AGPL-3.0prefix inside the already-correct identifier,appending another
-or-later. The guard can never stop it and the fix is its own input.mainwent 1× → 4× (PRs Welcome to standards Discussions! #2–chore(deps): bump the actions group across 1 directory with 5 updates #9, repaired),then 1× → 2× (PR ci(hypatia-scan): add --exit-zero + GITHUB_TOKEN to canonical template #54, repaired), and is at 1× now.
Next firing Monday 2026-09-14 06:00 UTC — expected to push a third corrupt branch and,
as on 08-31 and 09-07, to fail before opening a PR. It rewrites
.gitignoreand.gitattributestoAGPL-3.0-or-later-or-later, an invalid SPDX identifier that failsREUSE, commits it and pushes it; only
gh pr createis unreachable. See the thirdcorrection comment for the measured failure and why repairing it re-arms
main.The same non-idempotent sed is in
game-server-admin, alsoactiveon the same cron,but there it will not fire: that repo's dotfiles read
SPDX-License-Identifier: MPL-2.0,so the
AGPL-3.0guard is false.Which surfaces a separate, pre-existing inconsistency worth a ruling:
game-server-adminhas an
AGPL-3.0-or-laterLICENSEfile butMPL-2.0SPDX headers in its dotfiles, andGitHub reports its licence as
NOASSERTION. None of its 3 merged rhodibot PRs touchedthose files (all three deleted
CONTRIBUTING.adoc), so rhodibot's merged PRs are not thevector. How the MPL-2.0 got there is unresolved and I have not guessed.
Disabling that one workflow stops the only bleeding on a timer and is independent of the
larger (a)/(b)/(c)/(d) decision.
The one-line stop, written out so it need not be reconstructed
I have not run this. Disabling a workflow is on the estate trust-deny list
(
disable-ci-checks), so it is an owner action, not mine. But it is one reversible call andthe precondition arrives on a date, so the command belongs here rather than in someone's
reconstruction of it:
Workflow id
256226305, name🤖 Rhodibot — RSR Auto-Fix, state re-verifiedactiveat2026-09-09 02:08 UTC. This disables one workflow in one repo; it changes nothing about the
other 110 and pre-empts none of the (a)/(b)/(c)/(d) decision. It is safe to run before that
decision and safe to reverse after it.
Why it is spelled out rather than merely flagged. A ruling that lives only in an issue
goes unread precisely when its precondition arrives — this estate has the trap on file
(standards#331 sat twelve days). The cron does not care whether this issue has been read.
Raised by a peer session reviewing the A6 batch; the judgement to leave the call to the owner
is mine.
Two provenance caveats, stated rather than smoothed over
merged_byreadshyperpolymathon the 5 PRs I sampled. I havenot censused this, and the REST
auto_mergefield is populated only while auto-merge ispending — on an already-merged PR it is always null, so "auto=false" was guaranteed by
construction and proves nothing.
merged_byalso names the enabler under auto-merge.Combined with the shared PAT,
actor.logincannot distinguish the owner from an agentsession. Treat "the owner accepted these" as unproven.
rhodibot/rsr-compliance-*branches with no PR are real, but theyare not evidence of a blanket PR-creation failure — 151 PRs were opened. I previously drew
the opposite conclusion from them. A mechanism that explains a measurement is not
evidence for it.