Skip to content

ci: fail when a uses: points at a redirect, so there is no third time - #803

Merged
github-actions[bot] merged 1 commit into
mainfrom
ci/workflow-ref-gate
Aug 28, 2026
Merged

ci: fail when a uses: points at a redirect, so there is no third time#803
github-actions[bot] merged 1 commit into
mainfrom
ci/workflow-ref-gate

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

Twice in two days an owner change stopped every merge and every deploy here: maonakamotocatomean (2026-08-26), then the move to the bitbaum org (fixed in #800). Each fix pointed at a name that became a redirect the next time.

Both outages were invisible everywhere anyone looks. REST and git follow a rename redirect — gh api answers, git push works — while the Actions resolver, the one consumer that does not follow it, dies before any step exists with no readable log. PRs stay green, clean and mergeable; the red run sits on main under a workflow nobody opens.

Two instances is where the rule says stop fixing instances.

This asks the REST API what each referenced repo is really called and fails when that disagrees with the workflow. That disagreement is the bug — REST resolves the redirect, Actions does not. A static allowlist could not catch it: after a rename the workflow and the allowlist would hold the same stale name and agree with each other.

It runs in CI rather than npm run verify (which must stay offline and deterministic), skips without a token rather than passing quietly, and exits 2 on a non-404 error — could-not-look is never reported as looks-fine.

Proven by mutation: restoring catomean/dotfiles fails with the canonical name to use.

Supersedes #801, which duplicated #800's work; this keeps only the part that was not.

🤖 Generated with Claude Code

https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5

Twice in two days an owner change stopped every merge and every deploy
in this repo: `maonakamoto` → `catomean` on 2026-08-26, then the move to
the `bitbaum` org, fixed in #800. Each fix pointed at a name that became
a redirect the next time.

Both outages were invisible in every place anyone looks. GitHub redirects
a renamed owner for the REST API and for git remotes, so `gh api` answers
and `git push` works; the Actions resolver is the one consumer that does
not follow the redirect. It fails before any step exists, with "This run
likely failed because of a workflow file issue" and no readable log,
while pull requests stay green, clean and mergeable. The red run is on
main, under a workflow nobody opens. Work simply stops shipping.

Two instances is where the rule says stop fixing instances. This asks the
REST API what each referenced repo is really called and fails when that
disagrees with what the workflow says — that disagreement IS the bug,
because REST resolves the redirect and Actions does not.

A static allowlist could not catch it: after a rename the workflow file
and the allowlist would both hold the same stale name and agree with each
other. Only asking GitHub what the repo is called today can tell.

Lives in CI rather than in `npm run verify`, which is the offline SSOT
bundle and must stay deterministic; without a token this skips rather
than passing quietly. A non-200 that is not a 404 exits 2 — could-not-look
is never reported as looks-fine.

Proven by mutation: restoring `catomean/dotfiles` fails with the
canonical name to use. Also fixes the last CLAUDE.md reference to the
2026-08-26 owner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5
catomean added a commit that referenced this pull request Aug 28, 2026
Both branches edit the same line; one owner avoids a conflict that would
stop CI running on whichever loses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
github-actions Bot pushed a commit that referenced this pull request Aug 28, 2026
* fix(cat): @cat has resolved to nobody for two days

The email-derived-handle retirement renamed the Cat. Its predicate —
`username = split_part(email, '@', 1)` — is exactly right for a person,
whose public crawlable handle then republishes their email local part.
It is exactly wrong for the Cat, because deriving the handle from the
address is HOW the Cat is created: cat-account.ts registers
`cat@orangecat.invalid` precisely so handle_new_user mints `cat`. So the
script matched, and at 2026-08-26 21:58:59Z the handle became
`user_0234d5e38e66`.

`@cat` is the platform's advertised interface and it resolves by
username, so every `@cat` in a message or under a post has since
resolved to nobody. No reply anywhere. Found by typing `@` in the
composer in production and seeing no Cat in the menu.

Nothing went red. The account still existed, /profiles/cat still 301'd
through profile_username_history, CI was green, health was 200. The only
symptom was silence from the feature whose job is to answer.

Three defects, three fixes:

POLICY — the retirement could not tell a leaked address from a system
identity. Now excluded by property, not by name: RFC 2606 reserves
`.invalid` for undeliverable addresses, so there is no mailbox, no
owner, and nothing personal in the local part. Hardcoding 'cat' in SQL
would have put a second definition of the handle where it could drift
from config/cat-identity.ts. Applied to the rename script and to
count_email_derived_usernames(), which would otherwise have gone from 0
to 1 and turned the nightly gate red for a profile that is fine.

CODE — ensureCatAccount looked the Cat up BY USERNAME, so its
self-healing walked straight past a rename: lookup missed, createUser
said "already registered", second lookup missed, returned null on every
tick. It now keys on the login address, which no policy has reason to
rewrite, and treats the handle as a field to assert rather than a key to
search by. A rename now repairs itself on the next tick.

GATE — nothing checked the product's own claim. checkCatHandle asserts
that the name we tell people to type reaches the Cat; a lookup by id
would have stayed green throughout. The literal it carries is pinned to
CAT_USERNAME by a test, so the duplicate cannot drift.

Proven by mutation: restoring the username lookup, removing the
re-assert, and drifting the gate's literal each turn the suite red.
The migration was rehearsed against production inside a transaction and
rolled back — UPDATE 1, DELETE 1, 28 real retired handles untouched,
gate still reads 0.

Also restores the github.com/catomean links lost when this worktree was
reset; the owner rename left them pointing at a redirect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5

* fix(docs): point at bitbaum, the canonical owner

catomean is itself a redirect now — the repos live in the bitbaum org.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5

* fix(mentions): the @ menu was offering rows nobody can read

Production has 84 profiles and 74 of them have no display name — the
correct consequence of no longer minting names out of email local parts,
where NULL is honest. The menu did not account for that, so what it
actually showed on a bare `@` was two rows of `user_d58c7dccec41`,
printed twice each: once as the name, because the name falls back to the
handle, and once as the subtitle.

Three changes, all following from the data rather than from taste.

A nameless row now shows its handle ONCE, in the position the eye reads
first, instead of the same hex string on two lines.

A nameless profile is not offered at all on a bare `@`. You cannot be
searching for a name that does not exist, so those rows were pure noise
in the one position that matters most — the first thing anyone sees when
they discover the feature. Type any part of the handle and they come
straight back through the existing prefix rules.

At equal match quality, a named profile outranks a nameless one. `@u`
matches `ursula` and `user_d58c7dccec41` equally well by anything the
score can see, and only one of them is a row a human can act on. The
tiebreak sits after match quality, never before it, so an exact handle
match still wins — a demotion that overrode "this is plainly the row
being asked for" would be a worse bug than the one it fixed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012dpTLxh5GJWeWTF1UEvcD5

* chore: leave the CLAUDE.md owner line to #803

Both branches edit the same line; one owner avoids a conflict that would
stop CI running on whichever loses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Georgy Butaev <41178744+g-but@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@github-actions
github-actions Bot merged commit 5c909fd into main Aug 28, 2026
6 checks passed
@github-actions
github-actions Bot deleted the ci/workflow-ref-gate branch August 28, 2026 07:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant