Skip to content

org-migration: three conditions the design's failure table does not cover #84

Description

@twistedmelonman

The 2026-09 org migration (design: docs/superpowers/specs/2026-09-03-org-migration-design.md) is
functionally complete, but execution hit three conditions the design's
"Failure handling" table does not cover. Two of them would mislead someone
following the table literally. Recording them so the spec matches what
actually happens, and so a future migration does not repeat them.

1. Popular-repository namespace retirement blocks transfers permanently

What happened. Step 4 transferred 25 of 30 repos. Five failed with
HTTP 422:

Repository name smartwatermelon/<name> has been retired and cannot be reused

Affected: dotfiles, claude-config, personify, huddle-transcribe,
projectinsomnia. All five remain intact under twistedmelonman with
working redirects, so nothing is broken — but those five paths under the
org are permanently unavailable, including to gh repo create.

Why the table is wrong here. It says:

| Transfer fails mid-loop | Re-run transfer.sh; it skips completed repos. Report the refusing repo. |

That is correct for a transient error and useless for a 422. Retrying
confirms the block; it cannot clear it. Reversal requires GitHub Support
intervention with no self-service path and uncertain outcome (ticket
4729524 is open).

Cause. GitHub retires an OWNER/REPO path when, in the week before a
rename or transfer, the repo had >100 clones, >100 Actions runs, or
contained a Marketplace action. The retirement binds the path string, so
a new org inherits the retired paths of the user whose name it took.

Avoidable? Yes, by ordering: create the org under a temporary name →
transfer all repos → rename the user → rename the org. No official doc
prescribes a safe sequence (github/docs#17617, asking for exactly this,
was closed as not planned).

2. A post-transfer diff can be a gain, not a loss

What happened. Part F reported cleanroom with
fields changed besides owner: protection,rulesets. Its baseline value for
both was null. The repo did not lose settings — it gained branch
protection (claude-review / run-review) from the destination org's
ruleset.

Why the table is wrong here. It says:

| Post-transfer diff shows more than owner | Finding. Restore the setting by hand from the snapshot; record which setting GitHub dropped. |

"Restore from the snapshot" here means restoring null over working branch
protection — actively removing a protection the transfer correctly added.
The row assumes every diff is a loss.

Fix. Compare direction before acting: null in baseline and non-null
after is a gain, and the correct response is to accept it.

3. GitHub App installations do not follow transferred repos

What happened. After Step 4 and Step 5, claude-blocking-review failed
on a transferred repo at 36s:

Action failed with error: Claude Code is not installed on this repository.
Please install the Claude Code GitHub App at https://github.com/apps/claude

orgs/smartwatermelon/installations was empty. The Claude app installation
was bound to the user account and did not migrate with the repos.
nightowlstudiollc, never renamed, kept its installation
(claude id=107089099 repos=all) and passed the identical workflow
throughout.

Why this is easy to miss. The failure is silent until the next PR in
any of the 25 transferred repos, and it looks like a secrets problem. It is
not — the org secret was delivered correctly in the same run. It is also
distinct from the action install: the workflow downloads
anthropics/claude-code-action on every run and that succeeded; the app
installation
is a separate persistent grant the action authenticates
against, and no workflow can create it.

Fix. Install the app on the org (done: claude id=159138570 repos=all).
Verified by PR #30 on archive-resolver — a repo with no repo-level
secret — where the claude app posted a real VERDICT: PASS review.

Also worth recording

Step 5 says to delete the repo-level CLAUDE_CODE_OAUTH_TOKEN on
networth-agent, photo-game-poc, and cleanroom. In practice:

  • networth-agent had no copy to delete.
  • photo-game-poc and cleanroom are private; they keep their repo-level
    copies for the same reason scripts does. Both orgs currently report
    plan=team, which would make org-secret delivery to private repos work
    today, but that plan is temporary and nothing should depend on it.

Proposed changes

  • Add a namespace-retirement row to "Failure handling", stating a 422 is
    terminal and naming the reordering that avoids it
  • Rewrite the "diff shows more than owner" row to check direction first
  • Add an app-installation row, and a Step 4 verification that
    orgs/<org>/installations is non-empty before declaring the transfer done
  • Correct Step 5's repo list: private repos keep repo-level tokens

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions