fix(cli): set_admin --email disambiguates instead of crashing (#436) #480
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/436-set-admin-duplicate-email"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #436.
The defect
_set_admin_by_emaillooked users up by email alone and calledscalar_one_or_none(). Butusersis unique on(oidc_sub, oidc_issuer), not on email, so one person can legitimately own several rows — and two matches raiseMultipleResultsFound, handing the operator a SQLAlchemy traceback instead of an answer.Verified against the current code: the issue's claims hold exactly, and
_set_admin_by_oidcis already correct and untouched.The circumstance is the one that makes you run the command
Restoring production onto dev brings production's user rows across. The operator's dev identity authenticates against a different OIDC application, so it is a separate row that was never granted admin. So "I restored prod onto dev and I am not an admin any more" is precisely the case
--emailcould not handle.It also presents as lost admin when the flag is intact on a different row — and
make set-adminonly acceptsEMAIL, so the documented convenience path is the broken one.The fix
Ambiguity becomes an outcome rather than an exception. The candidates are listed with their
oidc_sub, issuer and current admin status, the message names the flags needed to pick one, it exits non-zero so a script cannot read it as success, and nothing is modified:Current admin status is in there because it is usually the answer: the operator often already holds admin on one row and needs the other one. Results are ordered by issuer so the listing is stable between runs.
Guessing would be worse than failing. The rows differ precisely in which identity they are, so granting to the wrong one leaves the operator still locked out with no sign of why.
Also adds an
OPERATIONS.mdtroubleshooting section — the issue suggested it, and it is worth having because the symptom reads as lost access rather than as two accounts.Verification
7 mutations, all caught. The one that matters most is reverting to
scalar_one_or_none(): that raises rather than exiting, sopytest.raises(SystemExit)pins the actual behaviour change and not merely the wording. The others cover granting to the first match instead of refusing, exiting zero on ambiguity, dropping the flags or the admin status from the listing, and both of the paths that had to keep working.test_ambiguity_does_not_grant_admin_to_anyoneis deliberately separate from the main case: in that one both rows end[False, True], which is indistinguishable from "granted to the row that already had it". Seeding two non-admin rows makes any grant at all visible.The tests use real commits with cleanup rather than the transactional
dbfixture, because the CLI opens its ownAsyncSessionLocal()— a different connection, which cannot see uncommitted fixture rows. Without that, every "no user found" assertion would pass for the wrong reason.Backend 1613 passed;
ruff check/formatclean. No frontend or migration changes.🤖 Generated with Claude Code
set_admin --emailcrashes instead of disambiguating when two identities share an email #436