Files
twenty/packages
Félix Malfait 705caab2b0 fix(onboarding): refresh stale workspace in currentWorkspace field resolver (#21839)
## What & why

After sign-up, users are redirected to `/sync/emails` and the page loads
forever; a full refresh fixes it. This blocks production deploy.

**Root cause** — a GraphQL field resolver returns an unrefreshed (stale)
workspace:

- `@AuthWorkspace()` (`request.workspace`) is read from the per-instance
core entity cache and can still be `PENDING_CREATION` /
`ONGOING_CREATION` right after `activateWorkspace`.
- The `currentUser` query resolver and the `onboardingStatus` field
already guard against this by calling
`refreshWorkspaceIfPendingOrOngoingCreation(...)`.
- But the `currentWorkspace` `@ResolveField` returned the raw
`@AuthWorkspace()` workspace. Because a field resolver takes precedence
over any value the query resolver attaches to its returned object, the
client receives that stale workspace.

So right after activation the client got an inconsistent payload:
- `onboardingStatus: SYNC_EMAIL` (fresh — computed from a direct DB
read)
- `currentWorkspace.activationStatus: ONGOING/PENDING_CREATION` (stale)

On the frontend, metadata loading is gated on
`isWorkspaceActiveOrSuspended(currentWorkspace)`, and
`MinimalMetadataGater` does **not** exclude `/sync/emails`. So the
workspace looked inactive → metadata never loaded (no metadata GraphQL
request was even issued) → the gater's loader showed indefinitely. A
full refresh worked because the cache had since refreshed to `ACTIVE`.

## Why it surfaces on staging but isn't caught by tests

The stale window only opens on a real fresh sign-up followed by
immediate activation, against a workspace cache that hasn't refreshed
yet (multi-instance / cache TTL). Single-instance local dev and the
existing `successful-user-and-workspace-creation` integration test
exercise `activateWorkspace` + `getCurrentUser` against one consistent
cache, so `currentWorkspace` already looks `ACTIVE` and they pass —
which is why this reproduces on staging/production but not locally, and
why a manual refresh recovers.

## How

Refresh the workspace in the `currentWorkspace` field resolver too, so
it is consistent with `onboardingStatus`. For active workspaces this is
a no-op (no extra DB read).

```ts
async currentWorkspace(@AuthWorkspace({ allowUndefined: true }) workspace) {
  if (!isDefined(workspace)) return workspace;
  return this.userService.refreshWorkspaceIfPendingOrOngoingCreation(workspace);
}
```

This is preferred over the frontend alternative (excluding
`/sync/emails` from `MinimalMetadataGater`), which would only hide the
symptom while every other consumer still received a wrong
`activationStatus`.

## Verification
- `nx typecheck twenty-server` and `nx lint:diff-with-main
twenty-server` (oxlint + oxfmt) are green.

https://claude.ai/code/session_018c1X6CwDgttMXA5tB797yS

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-19 14:45:12 +02:00
..