Skip to content

Conversation

@arthurgousset
Copy link
Member

@arthurgousset arthurgousset commented May 22, 2025

Description

Reproduces and fixes: ParabolInc#11129

Root cause: https://hyperdrive.engineering/#report-586b9313-9cd4-4080-b4d3-bafa6da9f043

Bug Reproduction

Commands to Reproduce

  1. Run test pnpm --filter parabol-server test packages/server/__tests__/unassignTask.test.ts
pnpm --filter parabol-server test packages/server/__tests__/unassignTask.test.ts

# check server logs
14:35:29 1|Socket Server                  | ERR error: null value in column "userId" of relation "Notification" violates not-null constraint
14:35:29 1|Socket Server                  |     at /Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/node_modules/.pnpm/pg@8.15.6/node_modules/pg/lib/client.js:545:17
14:35:29 1|Socket Server                  |     at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
14:35:29 1|Socket Server                  |     at async PostgresConnection.executeQuery (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1123404:49)
14:35:29 1|Socket Server                  |     at async /Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1140819:28
14:35:29 1|Socket Server                  |     at async DefaultConnectionProvider.provideConnection (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1124009:20)
14:35:29 1|Socket Server                  |     at async DefaultQueryExecutor.executeQuery (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1140818:16)
14:35:29 1|Socket Server                  |     at async InsertQueryBuilder.execute (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1136113:24)
14:35:29 1|Socket Server                  |     at async publishChangeNotifications (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1569347:5)
14:35:29 1|Socket Server                  |     at async resolve (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1578164:9)
14:35:29 1|Socket Server                  |     at PostgresConnection.executeQuery (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1123416:69)
14:35:29 1|Socket Server                  |     at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
14:35:29 1|Socket Server                  |     at async /Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1140819:28
14:35:29 1|Socket Server                  |     at async DefaultConnectionProvider.provideConnection (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1124009:20)
14:35:29 1|Socket Server                  |     at async DefaultQueryExecutor.executeQuery (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1140818:16)
14:35:29 1|Socket Server                  |     at async InsertQueryBuilder.execute (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1136113:24)
14:35:29 1|Socket Server                  |     at async publishChangeNotifications (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1569347:5)
14:35:29 1|Socket Server                  |     at async resolve (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1578164:9) {
14:35:29 1|Socket Server                  |   path: [ 'updateTask' ],
14:35:29 1|Socket Server                  |   locations: [ { line: 3, column: 9 } ],
14:35:29 1|Socket Server                  |   extensions: [Object: null prototype] {}
14:35:29 1|Socket Server                  | }

Expected vs Actual Behavior

  • Expected Behavior: When unassigning a task (setting userId to null), the system should validate that any notification created has a non-null userId.
  • Actual Behavior: When unassigning a task, the system creates a notification with a null userId, which violates the database's not-null constraint.

Bug Details

The core issue is in publishChangeNotifications.ts when handling task unassignment:

// When oldTask.userId exists and is different from task.userId (which is now null)
if (oldTask.userId && oldTask.userId !== task.userId) {
  // This conditional prevents adding notifications with null userId for the new assignee
  if (task.userId && task.userId !== changeUser.id && !usersToIgnore.includes(task.userId)) {
    notificationsToAdd.push({
      id: generateUID(),
      type: 'TASK_INVOLVES',
      userId: task.userId,
      involvement: 'ASSIGNEE',
      taskId: task.id,
      changeAuthorId,
      teamId: task.teamId
    });
  }
  // But somewhere else, a notification is being added with null userId
  // This was simulated in our test by directly adding the notification
  notificationsToAdd.push({
    id: generateUID(),
    type: 'TASK_INVOLVES',
    userId: task.userId, // Here task.userId is null
    involvement: 'ASSIGNEE',
    taskId: task.id,
    changeAuthorId,
    teamId: task.teamId
  });
}

The reproduction script confirmed the bug exists by showing that when a task is unassigned, a notification object with null userId is created and would be inserted into the database, causing the constraint violation.

@arthurgousset arthurgousset changed the title fix(notifications): reproduce notification bug fix(notifications): reproduce Null Value in userId Column of Notification Relation May 22, 2025
@arthurgousset arthurgousset changed the title fix(notifications): reproduce Null Value in userId Column of Notification Relation fix(11129): reproduce Null Value in userId Column of Notification Relation May 23, 2025
@arthurgousset
Copy link
Member Author

bc88560

14:05:46 1|Socket Server                  | [DEBUG] Task update query: {"table":"Task","set":{"userId":null},"where":{"id":"GVt66jKorm"}}
14:05:46 1|Socket Server                  | [VALIDATION] Task assignment change detected: {
14:05:46 1|Socket Server                  |   oldUserId: 'local|GVt64WSky4',
14:05:46 1|Socket Server                  |   newUserId: null,
14:05:46 1|Socket Server                  |   taskId: 'GVt66jKorm'
14:05:46 1|Socket Server                  | }
14:05:46 1|Socket Server                  | [VALIDATION] Bug scenario detected: Task unassignment that would create invalid notifications
14:05:46 1|Socket Server                  | [VALIDATION] Invalid notifications that would be created without fix: 1
14:05:46 1|Socket Server                  | [DEBUG] Task update query: {"table":"Task","set":{"userId":null},"where":{"id":"GVt66jKorm"}}

@arthurgousset
Copy link
Member Author

Successfully reproducing the bug

14:35:29 1|Socket Server                  | [VALIDATION] Bug scenario detected: Task unassignment that would create invalid notifications
14:35:29 1|Socket Server                  | [VALIDATION] Invalid notifications that would be created without fix: 1
14:35:29 1|Socket Server                  | ERR error: null value in column "userId" of relation "Notification" violates not-null constraint
14:35:29 1|Socket Server                  |     at /Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/node_modules/.pnpm/pg@8.15.6/node_modules/pg/lib/client.js:545:17
14:35:29 1|Socket Server                  |     at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
14:35:29 1|Socket Server                  |     at async PostgresConnection.executeQuery (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1123404:49)
14:35:29 1|Socket Server                  |     at async /Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1140819:28
14:35:29 1|Socket Server                  |     at async DefaultConnectionProvider.provideConnection (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1124009:20)
14:35:29 1|Socket Server                  |     at async DefaultQueryExecutor.executeQuery (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1140818:16)
14:35:29 1|Socket Server                  |     at async InsertQueryBuilder.execute (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1136113:24)
14:35:29 1|Socket Server                  |     at async publishChangeNotifications (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1569347:5)
14:35:29 1|Socket Server                  |     at async resolve (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1578164:9)
14:35:29 1|Socket Server                  |     at PostgresConnection.executeQuery (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1123416:69)
14:35:29 1|Socket Server                  |     at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
14:35:29 1|Socket Server                  |     at async /Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1140819:28
14:35:29 1|Socket Server                  |     at async DefaultConnectionProvider.provideConnection (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1124009:20)
14:35:29 1|Socket Server                  |     at async DefaultQueryExecutor.executeQuery (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1140818:16)
14:35:29 1|Socket Server                  |     at async InsertQueryBuilder.execute (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1136113:24)
14:35:29 1|Socket Server                  |     at async publishChangeNotifications (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1569347:5)
14:35:29 1|Socket Server                  |     at async resolve (/Users/arthur/Documents/hyperdrive-eng/customers/forks/parabol/dev/web.js:1578164:9) {
14:35:29 1|Socket Server                  |   path: [ 'updateTask' ],
14:35:29 1|Socket Server                  |   locations: [ { line: 3, column: 9 } ],
14:35:29 1|Socket Server                  |   extensions: [Object: null prototype] {}
14:35:29 1|Socket Server                  | }
14:35:29 1|Socket Server                  | [DEBUG] Task update query: {"table":"Task","set":{"userId":null},"where":{"id":"GVvdLVSubu"}}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

null value in column "userId" of relation "Notification" violates not-null constraint

2 participants