HarperFast / HarperFast/studio

Deleting a user leaves dangling references in OrganizationRole.userIds

Open
#1,586 0 comments 0 reactions 0 assignees View on GitHub
admin & data blocked
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.