HarperFast / HarperFast/studio
Deleting a user leaves dangling references in OrganizationRole.userIds
- Dominant language
- TypeScript
- Stars
- 5
- Forks
- 4
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 40
Description
## Summary
When a user record goes away, the organization roles that referenced it keep the stale id in `OrganizationRole.userIds`. The relationship resolution then emits a literal `null` in the `users` array for the id it can't resolve, so every consumer has to defend against holes in what the schema presents as a dense array of users.
This is the server-side half of the Users-page crash fixed in #1585 (UI-only mitigation).
## Evidence
`GET /OrganizationRole/org-qpz5akmyrp1d0opj` on `stage` (Acme, Inc.) returns 6 roles. Five resolve cleanly; `roletwo` does not:
```json
{
"id": "rol-db1unfn9kthuug1b",
"roleName": "roletwo",
"organizationId": "org-qpz5akmyrp1d0opj",
"userIds": ["usr-1kmtpihk58mkgwdr"],
"users": [null]
}
```
`userIds` still points at `usr-1kmtpihk58mkgwdr`, but that id no longer resolves to a user. In the same payload the other roles' ids resolve to full user objects, so this is specific to that id rather than a general resolution failure.
## Impact
- **Users page crashed outright** until #1585 — `TypeError: Cannot read properties of null (reading 'id')`, error boundary replacing the whole page, making Users unreachable for the org. Now mitigated in the UI, but only by dropping the entry.
- **Roles page over-reports membership.** It counts via `userIds.length` (`src/features/organization/roles/constants/tableDefinition.tsx`), so `roletwo` shows 1 member when it effectively has 0. Still wrong today; a UI workaround would mean resolving every id just to count, so the right fix is clean data.
- Any future consumer of `users` inherits the same trap, because the generated type says the elements are non-nullable.
## Suggested fix
Clean up role membership when a user is removed, so `userIds` never outlives the user — plus a migration to purge references already orphaned (at minimum `rol-db1unfn9kthuug1b` on `stage`).
Two things worth deciding alongside that:
1. **Should `users` ever contain `null`?** Omitting unresolvable entries would be less hazardous than a null hole, since the schema advertises non-nullable elements. If nulls are intentional, the OpenAPI schema should say so (`users?: (User | null)[]`) so generated types force callers to handle it.
2. **Is `userIds` vs `users` length skew ever legitimate?** If not, it's a cheap invariant to assert server-side and would have caught this at write time rather than in a browser.
## Repro
1. Sign in to `stage.studio.harperfabric.com`.
2. `GET /OrganizationRole/org-qpz5akmyrp1d0opj`.
3. Observe the `roletwo` entry: one id in `userIds`, one `null` in `users`.
Contributor guide
Research direction
Start at the GET /OrganizationRole/{organizationId} response and trace how OrganizationRole.userIds and users are produced when a user is deleted. Review src/features/organization/roles/constants/tableDefinition.tsx for the current membership count, then identify the existing orphaned role reference, including rol-db1unfn9kthuug1b on stage. Done means deleted users no longer leave role references and existing orphaned references are purged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend, database
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100