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

0:07.0On 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 typeInspectElement {"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…
0:07.4Immediately 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
Open the app at localhost:5173.
Open a project (e.g. 'Checkout demo' at /projects/mock-proj-checkout/overview).
Click the 'Settings' tab.
Scroll to the 'Team Members' section (current members: dev@example.local Owner, collaborator@example.com Member).
Type a valid email (e.g. newmember@example.com) into the 'Enter email address to invite' field — the Invite button becomes enabled.
Click the 'Invite' button.
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
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 typeInspectElement {"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…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.