Back to Feed
article 3m read

New users logged in to nothing because sign-up waited so long to pick a workspace

Workspace creation waited for a later webhook so invited people wouldn't get a spare one, and self-serve people could log in before it fired. Now the creation handler asks the identity provider for memberships on the spot, a later event repairs misses, and the session lookup retries, but the comment that justifies it contradicts the one it replaced and nothing tests which is right.

Article DepthPractice

Flow

What should happen for each sign-up case

  1. 1Invited person

    Provider lists a team. No personal workspace gets made.

  2. 2Self-serve person

    Provider lists no team. One workspace exists before first login.

  3. 3Provider question fails

    Nothing gets made and it's logged. The later message makes the workspace.

  4. 4Both messages land close together

    Should still end with one workspace. That needs the database to refuse a second.

  5. 5Page loads before the rows exist

    Lookup waits and tries again, then shows no workspace if they never come.

People were signing up for my team analytics app, logging in, and getting an error about having no organization. Nothing had made them one yet.

Sign-up runs off messages from the login provider, which owns accounts and team memberships. My app keeps its own copy. When an account is made, my code has to pick: does this person get their own workspace, or were they invited to a team? Guess wrong one way and an invited person sits alone in a spare workspace. Guess wrong the other way and a new customer logs in to nothing.

Where the decision used to wait

The old code waited. Its comment said the "account made" message comes before the "joined a team" message on an invite, so checking early would miss the invite. So it decided later, after the email was confirmed.

But people log in when they want. That later message can show up after they do. Then they have a login and no workspace.

Asking at the moment of creation

Now, when the account is made, my code asks the provider right then what teams the person is on. On a team: do nothing. On no team: make their workspace now. If the question fails, it logs that and makes nothing.

The later message still gets handled, as a backup. It asks again, checks my database, makes a workspace only if none exists, and logs a warning.

The code that finds your workspace when you load a page only reads. It now tries again a few times, waiting longer each time, since the rows it needs may still be on the way. If they never show, it logs that and shows no workspace.

Which note to believe

The old comment said asking early misses invites. The new one says the provider answers right away, so it's fine. The old code was asking the same provider. They can't both be right.

I suspect the new one is too hopeful. A fast answer can still be missing something. Nothing in the change tests it, and a test that sends the two messages in the wrong order would settle it.

A few other spots in the diff I'd keep an eye on:

  • Any team counts, even one that isn't active, if the provider lists it.
  • Check-then-create can run twice at once and make two workspaces unless the database stops it.
  • A slow handler may get its message sent again.
  • Someone whose login points at a team they have no active place on waits through every retry, on every page.
  • If both messages fail, nobody comes back for that person.

The rest of that commit

The team page's search box and its Invite and Create buttons lived inside the teams table. They now sit above it, with a comment marking them always visible, so they show whatever the table is doing.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward
Related idea

Paying customers matched by email, even the addresses the billing code made up itself

The lookup that decided whether an organization already had a payment customer searched by an email my code sometimes guessed or generated, and could hand one org's billing record to another. I moved identity onto the identity provider's org ID and tied retries to it, but the same commit left an ignored retry flag, a delete check that reads error wording, a gutted seat counter, and no tests.

via #identity · #reliability