Files
twenty/packages/twenty-server
Charles Bochet 003ad62f66 fix(server): surface nested QueryFailedError detail in upgrade error formatting (#21948)
## Context

While upgrading, a workspace migration failed with:

```
[Runner] [install-perf] migration failed after 20 action(s): Migration action 'create' for 'pageLayoutWidget' (universalIdentifier: f473b435-...) failed
```

The action names the failure but not *why* — unique violation? FK? which
key/row? The real cause is captured but was getting flattened away
before it reached anyone reading it.

## Root cause of the bad diagnostics

When a migration action fails, the action handler captures the real
error (typically a TypeORM `QueryFailedError` from `repository.insert`)
into
`WorkspaceMigrationRunnerException.errors.{metadata,workspaceSchema,actionTranspilation}`
and re-throws it intact. Caller-side formatters surface it — but
`formatUpgradeErrorForStorage` (read by the `upgrade-status` command)
flattened a nested `QueryFailedError` to just its `.message`, dropping
the PostgreSQL `code`, `detail` (the exact failing key/value) and
`query`.

## What this PR does

`formatUpgradeErrorForStorage` now **recurses** into nested causes, so a
wrapped `QueryFailedError` keeps its full driver detail.
Surfacing/logging stays a caller concern (the runner already produces
and re-throws the structured exception) — this PR only fixes the
formatter that was dropping detail. Added a unit test for the `create
pageLayoutWidget` unique-violation case.

### Stored upgrade error — before
```
Metadata error: duplicate key value violates unique constraint "IDX_..."
```

### After
```
Metadata error:
  [QueryFailedError] duplicate key value violates unique constraint "IDX_..."
  PostgreSQL code: 23505
  Detail: Key (universalIdentifier)=(f473b435-...) already exists.
  Query: INSERT INTO "core"."pageLayoutWidget" VALUES ($1)
```

## Scope

Diagnostics only — it surfaces the cause, it does not change migration
behavior. The underlying `create pageLayoutWidget` failure (likely a
unique/FK violation when upgrading existing workspaces, downstream of
#21673) is a separate follow-up once the exact cause is captured.

## Note / possible follow-up

`workspaceMigrationRunnerExceptionFormatter` (the GraphQL/app-install
surfacing path) has the same flattening issue — it reads
`error.errors.metadata.code`, but for a `QueryFailedError` the pg code
lives on `driverError.code`, so it falls back to `INTERNAL_SERVER_ERROR`
and loses `detail`. Left out of scope here; happy to fix in a follow-up
if wanted.

## Tests

- New unit test for a `QueryFailedError` nested in an `EXECUTION_FAILED`
exception; snapshots updated.
- `oxlint`, `oxfmt --check`, `nx typecheck twenty-server`, and the
affected jest suites pass.
2026-06-22 13:32:21 +02:00
..