Skip to content

[decision] 72 of 111 rhodibot workflows still run the MUTATING variant on a weekly cron — the canary migration is 39/111 done #759

Description

@hyperpolymath

Summary

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 closed0 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.adoc removed; 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-idempotent s/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 after git 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

  1. Bulk SPDX rewrite on a weekly cronsed -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.
  2. 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.
  3. Comment and code disagree — the comment says "AGPL → PMPL"; the code writes MPL-2.0.
  4. 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.
  5. Unattended mutation on a schedule, which crosses the standing "no unattended
    mutations" guardrail (same class as [charter] git-logistics-office — the estate control plane (repo lifecycle: birth, enrollment, life, death) #636).
  6. Hardcoded personal e-mail written into generated SECURITY.md / CONTRIBUTING.md.
  7. 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:

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-later LICENSE 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    cicdCI/CD: workflows, actions, lockfiles, pins, runners, release gatesdecisionA ruling is required before work can proceed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions