{"record":{"id":"41e44f8cf196eff6","repo":"laurent22/joplin","slug":"multiple-invitations-found-with-the-same-id","errorCode":null,"errorMessage":"Multiple invitations found with the same ID","messagePattern":"Multiple invitations found with the same ID","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/app-cli/app/command-share.ts","lineNumber":225,"sourceCode":"\t\t\t\t\t} else {\n\t\t\t\t\t\tthis.stdout(`\\t${_('None')}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tconst commandShareAcceptOrReject = async (folderId: string, accept: boolean) => {\n\t\t\tawait ShareService.instance().maintenance();\n\n\t\t\tconst shareState = getShareState();\n\t\t\tconst invitations = shareState.shareInvitations.filter(invitation => {\n\t\t\t\treturn invitation.share.folder_id === folderId && invitation.status === ShareUserStatus.Waiting;\n\t\t\t});\n\t\t\tif (invitations.length === 0) throw new Error('No such invitation found');\n\n\t\t\t// If there are multiple invitations for the same folder, stop early to avoid\n\t\t\t// accepting the wrong invitation.\n\t\t\tif (invitations.length > 1) throw new Error('Multiple invitations found with the same ID');\n\n\t\t\tconst invitation = invitations[0];\n\n\t\t\tthis.stdout(accept ? _('Accepting share...') : _('Rejecting share...'));\n\t\t\tawait invitationRespond(invitation.id, invitation.share.folder_id, invitation.master_key, accept);\n\t\t};\n\n\t\tconst commandShareAccept = (folderId: string) => (\n\t\t\tcommandShareAcceptOrReject(folderId, true)\n\t\t);\n\n\t\tconst commandShareReject = (folderId: string) => (\n\t\t\tcommandShareAcceptOrReject(folderId, false)\n\t\t);\n\n\t\tconst commandShareDelete = async (folder: FolderEntity) => {\n\t\t\tconst force = args.options.force;\n\t\t\tconst ok = force ? true : await this.prompt(","sourceCodeStart":207,"sourceCodeEnd":243,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/app-cli/app/command-share.ts#L207-L243","documentation":"Thrown by `commandShareAcceptOrReject` when more than one Waiting invitation exists for the same folderId. This is a defensive guard: accepting/rejecting uses `invitations[0]`, so an ambiguous set could act on the wrong invitation. Rather than guess, the command refuses to proceed. The comment in source explicitly documents this as preventing wrong-invitation acceptance.","triggerScenarios":"The redux `shareInvitations` array contains two or more entries with the same `share.folder_id` and `status === Waiting`. This is an unexpected server state — normally a folder has at most one outstanding invitation per recipient — and usually results from a server-side bug, a duplicate sync, or re-invitation after a rejected one that did not clear the old entry.","commonSituations":"Server bug producing duplicate invitations; sync replay that duplicated invitation records; re-inviting a user after a reject without cleaning the prior record.","solutions":["Run `:sync` again — a follow-up server reconciliation may collapse the duplicates.","Run `share list` and inspect invitations; if a duplicate is visible, report it as a server/client bug with the folder id.","As a workaround, reject first to clear Waiting entries, then ask the sender to re-invite once.","Do not attempt to patch redux state manually unless you understand the share reducer."],"exampleFix":"// before\nif (invitations.length > 1) throw new Error('Multiple invitations found with the same ID');\n\n// after - keep the guard but expose the duplicate ids for diagnostics\nif (invitations.length > 1) throw new Error(`Multiple waiting invitations (${invitations.length}) for folder ${folderId}: ${invitations.map(i => i.id).join(', ')}`);","handlingStrategy":"try-catch","validationCode":"if (invitations.length > 1) {\n\t// Surface as a data-integrity issue rather than silently picking [0].\n\tthrow new Error(`Duplicate waiting invitations for folder ${folderId}: ${invitations.map(i => i.id).join(', ')}`);\n}","typeGuard":"const hasUniqueWaitingInvitation = (invitations: ShareInvitation[], folderId: string): boolean =>\n\tinvitations.filter(i => i.share.folder_id === folderId && i.status === ShareUserStatus.Waiting).length === 1;","tryCatchPattern":"try {\n\tawait commandShareAccept(folderId);\n} catch (e) {\n\tif (/Multiple invitations/.test(e.message)) { /* sync, report server bug, or reject-then-reinvite */ }\n\telse throw e;\n}","preventionTips":["Treat duplicate invitations as a server/state bug and report it with the ids.","Sync again — server reconciliation may collapse the duplicates.","Avoid manually mutating the shareInvitations array in redux."],"tags":["share","invitation","duplicate","data-integrity","cli","accept","reject"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}