Release Notes | v1.3.0: New users can get into the admin again (and how we'll catch the next one earlier)

1. The bug

  • Our new content strategist was invited to the admin, but couldn't get in.
  • No enrollment email arrived, so there was no link to set a password.
  • Setting a password by hand didn't help either. The page never sent the user on to the dashboard, and failures showed no error.

2. The fix

  • Root cause: the SendGrid API key used in production had been revoked. Every enrollment and password-reset email failed at the SMTP login (535 Authentication failed). We issued a new key and redeployed.
  • A backport, not a merge: production runs Meteor 2.7.2 and QA runs Meteor 3.3.2. QA's newer email system uses 3.x-only async APIs (Email.sendAsync, awaited Accounts.send*Email), so we rewrote it against 2.x's synchronous APIs.
  • Branded templates: enrollment and password-reset emails now use QA's HTML templates and send from our current domain.
  • Visible errors: SMTP failures now reach the admin as a readable message instead of a bare "Internal server error".
  • A broken alert library: production called Bert.alert about 64 times, but the package wasn't installed, so every call threw. On the login and reset screens that meant the redirect never ran. We replaced it on the auth path with a small notify helper.
  • Tested before shipping: we booted the production line locally against a copy of production data, then used Playwright to confirm that both the invite link and the reset link set a password, log the user in and redirect them.

3. What we uncovered

  • Version drift between branches. Production and QA have diverged across Meteor (2 vs 3), react-router (5 vs 6) and styled-components (5 vs 6). Code that's correct on one line can break on the other, and neither tests nor TypeScript catch it, because each branch only compiles against its own dependencies.
  • Stored data is part of the API. accounts-password 2.x stores enrollment tokens in a different field than the one our lookup read. A file-upload package's database index options differ between versions, which crashed boot whenever the old line was pointed at the new line's database.
  • CI images have a lifespan too. The deploy succeeded, but the step that tags each release failed. Debian 11 (bullseye) had reached end-of-life, so apt-get returned 404s. No tag meant no release announcement. We removed the unneeded package install and the tag went out.
  • Local production emulation is now documented. Running an older release line locally took long enough that it now has its own guide in the repo.

4. Catching this earlier: an agentic changelog-review skill

  • We built a Claude Code skill, changelog-review, that checks each pull request against the dependency versions its target branch actually runs.
  • Pointers, not copies: a registry records where each dependency's changelog, migration guide and end-of-life schedule live, and where our repo pins its version on each branch. Changelogs are read live, never pasted in, so nothing goes stale.
  • Evidence log: every pitfall we've actually hit is recorded with the ticket, the versions involved, a grep pattern and a source link. The next review checks those first.
  • It grows with use: a new dependency in a PR adds a pointer, and a confirmed mismatch adds a finding. It starts with Meteor, and React, Node and CI images will follow.
  • Advisory, not automatic: it reports each API as OK, mismatch, changed behaviour or unverified, then leaves the decision to the reviewer. Its first real job is our Meteor 3.3 → 3.5 upgrade.
[object Object]