All bugs
highSwallowed failures

Inviting a teammate throws ‘object is not extensible’

The error names property '2' on an object you never wrote. The actual cause is a push onto frozen Redux state, several layers away.

loop-qaFound 2026-06-24Full report in Replay QA

What the recording showed

projectMembers/invite rejected with TypeError: Cannot add property 2, object is not extensible. The members list stayed at 2 and the email stayed in the input.

Actual

The invite thunk throws a TypeError 'Cannot add property 2, object is not extensible' (projectMembers/invite/rejected). No member is added — the list still shows only dev@example.local (Owner) and collaborator@example.com (Member), and newmember@example.com remains in the input field.

Expected

Clicking Invite with a valid email should add the new member to the Team Members list as a 'Member' (e.g. newmember@example.com shown with a Member badge) and clear the input field.

Session replay

Chronology of events

1
The app at 0:07.00:07.0
0:07.0

On the Settings tab, in the Team Members section, the user has typed 'newmember@example.com' into the 'Enter email address to invite' field (which enabled the Invite button) and clicks the 'Invite' button — a <button type="submit" class="settings-section__save-btn" data-testid="invite-btn" data-analytics-event="project-members-invite-submit">. The user expects this to add newmember@example.com to the Team Members list as a Member. User interaction.

2 evidence probes (UserInteractions, InspectElement)
UserInteractions {"mode":"interactions","type":"click","recordingId":"1ee752f4-9be3-4845-acad-746c46ce9cfa"}
→ # User Interactions

**Filter:** click only
**Showing 1–2 of 2 interactions**

| # | Time | Type | Detail | Point |
|---|------|------|--------|-------|
| 0 | 245158ms | click | Click at (769, 138) | Point:1 |
| 1 | 265962ms | click | Click at (1155, 652) | Point:2 |

## Next Steps

- Use DescribePoint with a point reference to inspect the code at a specific interaction
- Use Evaluate with a point reference for ad-hoc value inspection at that moment
- Use `type: 'click'` or `type: 'keypress'` to filter by interaction type
InspectElement {"point":"Point:2","element":"[data-testid=\"invite-btn\"]","recordingId":"1ee752f4-9be3-4845-acad-746c46ce9cfa"}
→ Element:2 is `<button type="submit" class="settings-section__save-btn" data-testid="invite-btn" data-analytics-event="project-members-invite-submit"></button>`.
No elements are obstructing the center point of this element.

Element:2 has 92.484375px width and 36.796875px height.
Margin: top 0px right 0px bottom 0px left 0px.
Padding: top 8px right 18px bottom 8px left 18px.

Distances to its parent element:
- Top is aligned with parent boundary
- Right is aligned with parent boundary
- Bottom is aligned with parent boundary
- Left overflows parent by 799.52px

### Parent Element:3

Element:3 is `<div class="settings-section__input-row"></div>`.

Element:3 has 892px width and 36.796875px height.
Margin: top 0px right 0px bottom 0px left 0px.
Padding: top 0px right 0px bottom 0px left 0px.

Distances to its parent element:
- Top is aligned with parent boundary
- Right is aligned with parent boundary
- Bottom is aligned with parent boundary
- Left is aligned with parent boundary

### Parent Element:4

Element:4 is `<form class="members-invite" data-testid="invite-form"></form>`.

Element:4 has 892px width and 36.796875px height.
Margin: top 4px right 0px bottom 0px left 0px.
Padding: top 0px right 0px bottom 0px left 0px.

Distances to its parent element:
- Top overflows parent by 212.73px
- Right overflows parent by 21.00px
- Bottom overflows parent by 21.00px
- Left overflows parent by 21.00px

### Parent Element:5

Element:5 is `<section class="settings-section" data-testi…
2
The app at 0:07.40:07.4
0:07.4

Immediately after the click, the invite fails: the Redux thunk projectMembers/invite is rejected with TypeError 'Cannot add property 2, object is not extensible'. The Team Members list is unchanged — it still shows only dev@example.local (Owner) and collaborator@example.com (Member); newmember@example.com was NOT added and remains in the invite input field. The user is left with an error and no new member.

1 evidence probe (ReduxActions)
ReduxActions {"mode":"action-detail","actionIndex":231,"recordingId":"1ee752f4-9be3-4845-acad-746c46ce9cfa"}
→ # Action #231: `projectMembers/invite/rejected`

- **Time:** 266.03s
- **Execution point:** `486777830492395309155877143239983117`

## Action Object

```json
{
  "type": "projectMembers/invite/rejected",
  "meta": {
    "arg": {
      "projectId": "mock-proj-checkout",
      "email": "newmember@example.com"
    },
    "requestId": "nFYQl1YyQjuA8n1JtqDeu",
    "rejectedWithValue": false,
    "requestStatus": "rejected",
    "aborted": false,
    "condition": false
  },
  "error": {
    "name": "TypeError",
    "message": "Cannot add property 2, object is not extensible",
    "stack": "TypeError: Cannot add property 2, object is not extensible\n    at Array.push (<anonymous>)\n    at Module.mockInviteMember (http://localhost:5173/src/dev/mockPipeline.ts:1219:8)\n    at http://localhost:5173/src/slices/projectMembersSlice.ts:31:34\n    at http://localhost:5173/node_modules/.vite/deps/chunk-6CSVRG6I.js?v=aa8f336a:2554:79\n    at http://localhost:5173/node_modules/.vite/deps/chunk-6CSVRG6I.js?v=aa8f336a:2588:10\n    at http://localhost:5173/node_modules/.vite/deps/chunk-6CSVRG6I.js?v=aa8f336a:1631:14\n    at http://localhost:5173/node_modules/.vite/deps/chunk-6CSVRG6I.js?v=aa8f336a:1898:34\n    at http://localhost:5173/node_modules/.vite/deps/chunk-6CSVRG6I.js?v=aa8f336a:1722:12\n    at handleInvite (http://localhost:5173/src/components/ProjectMembers.tsx:39:5)\n    at executeDispatch (http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa8f336a:13622:11)"
  }
}
``…

Steps to reproduce

1

Open the app at localhost:5173.

2

Open a project (e.g. 'Checkout demo' at /projects/mock-proj-checkout/overview).

3

Click the 'Settings' tab.

4

Scroll to the 'Team Members' section (current members: dev@example.local Owner, collaborator@example.com Member).

5

Type a valid email (e.g. newmember@example.com) into the 'Enter email address to invite' field — the Invite button becomes enabled.

6

Click the 'Invite' button.

7

Observe: an error 'Cannot add property 2, object is not extensible' appears and the member is not added to the list.

Root cause

In src/dev/mockPipeline.ts, mockInviteMember reads the project's member array with const list = store.membersByProject[projectId] ?? [] and then calls list.push(m) (mockPipeline.ts:1365-1366). That array is the exact same object reference that was previously stored into Redux state at src/slices/projectMembersSlice.ts:106 (state.items = action.payload.items), because mockFetchProjectMembers returns store.membersByProject[projectId] directly (mockPipeline.ts:1349). Redux Toolkit (Immer) freezes state in development, so once that array was placed in the store it became non-extensible. Calling Array.push on the now-frozen array throws 'TypeError: Cannot add property 2, object is not extensible' (the existing array already holds 2 members at indices 0 and 1, so adding index 2 fails). The rejected projectMembers/invite action's error has name 'TypeError', message 'Cannot add property 2, object is not extensible', and a stack of 'at Array.push → at Module.mockInviteMember (mockPipeline.ts) → at handleInvite (ProjectMembers.tsx:39)'. The thunk rejects, no member is appended, and the member is never added.

2 evidence probes (ReduxActions, SearchSources)
ReduxActions {"mode":"action-detail","actionIndex":231,"recordingId":"1ee752f4-9be3-4845-acad-746c46ce9cfa"}
→ # Action #231: `projectMembers/invite/rejected`

- **Time:** 266.03s
- **Execution point:** `486777830492395309155877143239983117`

## Action Object

```json
{
  "type": "projectMembers/invite/rejected",
  "meta": {
    "arg": {
      "projectId": "mock-proj-checkout",
      "email": "newmember@example.com"
    },
    "requestId": "nFYQl1YyQjuA8n1JtqDeu",
    "rejectedWithValue": false,
    "requestStatus": "rejected",
    "aborted": false,
    "condition": false
  },
  "error": {
    "name": "TypeError",
    "message": "Cannot add property 2, object is not extensible",
    "stack": "TypeError: Cannot add property 2, object is not extensible\n    at Array.push (<anonymous>)\n    at Module.mockInviteMember (http://localhost:5173/src/dev/mockPipeline.ts:1219:8)\n    at http://localhost:5173/src/slices/projectMembersSlice.ts:31:34\n    at http://localhost:5173/node_modules/.vite/deps/chunk-6CSVRG6I.js?v=aa8f336a:2554:79\n    at http://localhost:5173/node_modules/.vite/deps/chunk-6CSVRG6I.js?v=aa8f336a:2588:10\n    at http://localhost:5173/node_modules/.vite/deps/chunk-6CSVRG6I.js?v=aa8f336a:1631:14\n    at http://localhost:5173/node_modules/.vite/deps/chunk-6CSVRG6I.js?v=aa8f336a:1898:34\n    at http://localhost:5173/node_modules/.vite/deps/chunk-6CSVRG6I.js?v=aa8f336a:1722:12\n    at handleInvite (http://localhost:5173/src/components/ProjectMembers.tsx:39:5)\n    at executeDispatch (http://localhost:5173/node_modules/.vite/deps/react-dom_client.js?v=aa8f336a:13622:11)"
  }
}
``…
SearchSources {"pattern":"list.push|membersByProject","recordingId":"1ee752f4-9be3-4845-acad-746c46ce9cfa"}
→ ### http://localhost:5173/src/components/PolishTab.tsx
 hits | L | code 
------|------|------
   2 |    1 | import { Fragment, memo, useEffect, useState, useMemo } from 'react' 

   0 |   32 |     const list = map.get(key) ?? [] 
   0 |  *33 |     list.push(item) 
   0 |   34 |     map.set(key, list)
### http://localhost:5173/src/dev/mockPipeline.ts
 hits | L | code 
------|------|------
     |    1 | import type { Project } from '../slices/projectsSlice' 

     |   80 |   polishTasks: PolishTask[] 
     |  *81 |   membersByProject: Record<string, ProjectMember[]> 
     |   82 | } 

     |  720 |     polishTasks, 
     | *721 |     membersByProject: { 
     |  722 |       [PJ1]: [ 

   0 |  796 |   store.projects.unshift(p) 
   0 | *797 |   store.membersByProject[id] = [ 
     |  798 |     { 

   0 |  816 |   store.polishTasks = store.polishTasks.filter((t) => t.project_id !== id) 
   0 | *817 |   delete store.membersByProject[id] 
     |  818 | } 

     | 1348 | export function mockFetchProjectMembers(projectId: string): { items: ProjectMember[]; role: string } { 
   2 | *1349 |   const items = store.membersByProject[projectId] ?? [] 
   2 | 1350 |   const mine = items.find((m) => m.email === MOCK_AUTH_USER.email) 

     | 1364 |   } 
   1 | *1365 |   const list = store.membersByProject[projectId] ?? [] 
   1 | *1366 |   list.push(m) 
   0 | *1367 |   store.membersByProject[projectId] = list 
   0 | 1368 |   return m 

     | 1371 | export function mockRemoveMember(memberId:…

Root cause → actual behavior

1

The user clicks the Invite button (Element:2, <button data-testid="invite-btn" data-analytics-event="project-members-invite-submit">), which submits the invite form and calls handleInvite, dispatching the inviteProjectMember thunk.

2 evidence probes (UserInteractions, InspectElement)
UserInteractions {"mode":"interactions","type":"click","recordingId":"1ee752f4-9be3-4845-acad-746c46ce9cfa"}
→ # User Interactions

**Filter:** click only
**Showing 1–2 of 2 interactions**

| # | Time | Type | Detail | Point |
|---|------|------|--------|-------|
| 0 | 245158ms | click | Click at (769, 138) | Point:1 |
| 1 | 265962ms | click | Click at (1155, 652) | Point:2 |

## Next Steps

- Use DescribePoint with a point reference to inspect the code at a specific interaction
- Use Evaluate with a point reference for ad-hoc value inspection at that moment
- Use `type: 'click'` or `type: 'keypress'` to filter by interaction type
InspectElement {"point":"Point:2","element":"[data-testid=\"invite-btn\"]","recordingId":"1ee752f4-9be3-4845-acad-746c46ce9cfa"}
→ Element:2 is `<button type="submit" class="settings-section__save-btn" data-testid="invite-btn" data-analytics-event="project-members-invite-submit"></button>`.
No elements are obstructing the center point of this element.

Element:2 has 92.484375px width and 36.796875px height.
Margin: top 0px right 0px bottom 0px left 0px.
Padding: top 8px right 18px bottom 8px left 18px.

Distances to its parent element:
- Top is aligned with parent boundary
- Right is aligned with parent boundary
- Bottom is aligned with parent boundary
- Left overflows parent by 799.52px

### Parent Element:3

Element:3 is `<div class="settings-section__input-row"></div>`.

Element:3 has 892px width and 36.796875px height.
Margin: top 0px right 0px bottom 0px left 0px.
Padding: top 0px right 0px bottom 0px left 0px.

Distances to its parent element:
- Top is aligned with parent boundary
- Right is aligned with parent boundary
- Bottom is aligned with parent boundary
- Left is aligned with parent boundary

### Parent Element:4

Element:4 is `<form class="members-invite" data-testid="invite-form"></form>`.

Element:4 has 892px width and 36.796875px height.
Margin: top 4px right 0px bottom 0px left 0px.
Padding: top 0px right 0px bottom 0px left 0px.

Distances to its parent element:
- Top overflows parent by 212.73px
- Right overflows parent by 21.00px
- Bottom overflows parent by 21.00px
- Left overflows parent by 21.00px

### Parent Element:5

Element:5 is `<section class="settings-section" data-testi…
2

The thunk calls mockInviteMember, which runs list.push(m) on store.membersByProject[projectId] — the same array reference Immer froze when it became Redux state.items. Array.push on the frozen array throws 'TypeError: Cannot add property 2, object is not extensible', so the thunk dispatches projectMembers/invite/rejected and the member is never appended to the list.

1 evidence probe (ReduxActions)
ReduxActions {"mode":"actions","actionType":"projectMembers/invite","recordingId":"1ee752f4-9be3-4845-acad-746c46ce9cfa"}
→ # Redux Actions

**Filters:** type contains "projectMembers/invite"
**Showing 1–2 of 2 actions**

| # | Action Type | Time | Origin | Duration | Affected | Wasted |
|---|-------------|------|--------|----------|----------|--------|
| 230 | `projectMembers/invite/pending` | 265.97s | — | — | — | — |
| 231 | `projectMembers/invite/rejected` | 266.03s | — | — | — | — |

Is your app hiding bugs like this?

Replay QA explores every build, records what it finds, and writes the root cause.