HarperFast / HarperFast/studio

[RUM] Sign-up conflict is still a dead end after #1613 — 16 of 16 sessions in 7 days retried ~3× and none created an account

Open
#1,668 0 comments 0 reactions 0 assignees View on GitHub
blocked rum
Dominant language
TypeScript
Stars
5
Forks
4
Avg merge
1d 8h
Merged PRs (30d)
40

Description

## Summary

The client-side mitigation for #1612 shipped in #1613 and is live in production, and **the
outcome it was meant to change has not moved at all**. In the last 7 days, **16 of 16
sessions** that hit a `409` on `POST /User/` retried an average of 2.8 times and **not one
of them created an account** — statistically identical to the 2.5 retries / 7-of-8
abandonment that motivated the original issue.

The reason is known and recorded in #1613: the 409 handling was deliberately left
status-agnostic because HarperFast/central-manager#630 was going to remove the 409
shortly. **That was 19 days ago and #630 is still open**, so Studio is running the
narrowed client path against a server that still emits the conflict.

Surfaced by the daily automated RUM review (RUM app `f590deee-…`, `env:prod`, windows as noted).

## The numbers

`POST /User/`, production:

| Window | 409 events | 409 sessions | 201 events | 201 sessions |
| --- | --- | --- | --- | --- |
| 24h → now | 13 | 5 | 10 | 10 |
| 48h → 24h | 0 | 0 | 8 | 8 |
| 72h → 48h | 6 | 2 | 11 | 11 |
| 7d | 45 | 16 | 115 | 115 |
| 30d | 79 | 29 | 193 | 187 |

**Conversion, measured by session-id set intersection over the 7-day window: 0 of 16.**
No session that received a 409 subsequently received a `201` — not on a retry, not later
in the same session.

Before/after the #1613 fix, on the metric the fix targeted:

| | Retries per affected session | Sessions that never created an account |
| --- | --- | --- |
| Pre-fix (#1612, 24h to 2026-08-12) | 20 / 8 = **2.5** | 7 of 8 |
| Post-fix (7d to 2026-08-31) | 45 / 16 = **2.8** | **16 of 16** |

Volume is also up, not down: #1612 measured "~19 409s in the preceding 30 days total".
It is now **79 in 30 days**, and 76 of those 79 fall in the last 14 days.

The per-session signature is unchanged and remarkably uniform — five sessions in the last
24h, each exactly three `POST /User/` requests roughly 4.5 s apart, then nothing:

```
02:30:40 02:30:44 02:30:50
08:39:57 08:40:01 08:40:06
11:30:54 11:30:58 11:31:03
11:55:58 11:56:04 11:56:09
01:28:55 01:28:59 01:29:04
```

Server latency on these is 0.3–1.8 s, so the gaps are a person waiting for the button to
re-enable and pressing it again.

## This is not a deploy gap

Verified rather than assumed:

- RUM reports exactly one version across all 54 sessions in the last 24h: **v2.169.3**.
- `2bd2f64a`, `3d1795ad`, and `4a7b85fd` (the sign-up form commits) are all ancestors of
`v2.169.3`.
- The chunk hash in today's error stacks (`assets/index-D9wn3O9N.js`) matches what
`https://fabric.harper.fast/` serves right now.

So the `isPending` guard and the persistent inline message are definitely running.

## Mechanism

The server still answers a duplicate address with a hard conflict —
[`central-manager/src/resources/user/addUser.js:20`](https://github.com/HarperFast/central-manager/blob/main/src/resources/user/addUser.js#L20):

```js
throw new ClientError('User already exists', ERROR_CODE.CONFLICT);
```

Studio surfaces that text verbatim. `SignUp`'s `onError` is deliberately status-agnostic
and sets `root` from `describeError(error).message`, which for this response resolves to
the RFC 9457 `title` — i.e. the string the user reads above the submit button is exactly
**"User already exists"**. That path is covered by
`src/features/auth/SignUp.test.tsx` (`surfaces a %i rejection inline`, the `409` case), so
this is confirmed behavior, not inference.

So the person is told, persistently and in the right place, that the account exists — and
still resubmits the same details three times and leaves. The message states a fact and
names no next action; the "Already have an account? Sign in instead." link is below the
form, outside the block their attention is in. `#1613` could not add that instruction
because the wording comes from the server and the form maps no status codes by design.

## What to do

Ordered by how much each actually changes the outcome:

1. **Ship HarperFast/central-manager#630.** It replaces the 409 with a uniform
"check your email to continue" response, which ends the enumeration oracle *and* the
dead end in one edit — the address either gets an email or gets a "you already have an
account" email, and nobody is left staring at a form. Note its follow-on constraint,
HarperFast/central-manager#636: the uniform response must carry a body, or
`onSignUpSubmit`'s `if (data) … else throw` turns a successful signup into
"Something went wrong".
2. **Interim, status-agnostic, no oracle:** render the existing "Already have an account?
Sign in instead." link *adjacent to the inline error* whenever a submit error is
present. The link is already unconditionally on the page, so promoting it on any
failure leaks nothing new and maps no status codes — it stays within #1613's rule.
Worth doing only if #630 is not imminent; it is a mitigation, not the fix.

I have deliberately **not** opened a PR re-introducing 409-specific copy. That was
explicitly rejected on review of #1613 (encoding "409 means the address is taken" in the
UI would turn closing the enumeration oracle into "you're removing a helpful message"),
and nothing in this data changes that argument.

## Why this is worth reopening the thread rather than waiting

Sign-up is the top of the funnel and this is the only failure mode on it. A third of
sign-up sessions in the last 24h (5 of 15 that submitted) hit it, and the measured
recovery rate across 7 days is zero.

Contributor guide

Open the contributing guide

Research direction

Start with src/features/auth/SignUp.test.tsx and the SignUp onError path, then inspect central-manager/src/resources/user/addUser.js and the linked #630/#636 changes. Confirm which mitigation is accepted, update the relevant behavior and tests, and verify that the existing 409 flow gives users a next action without status-specific account-enumeration copy.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
api, authentication, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.