{"record":{"id":"1fd55d2baf12841f","repo":"laurent22/joplin","slug":"parent-id-cannot-be-the-same-as-id","errorCode":null,"errorMessage":"Parent ID cannot be the same as ID","messagePattern":"Parent ID cannot be the same as ID","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/lib/models/Folder.ts","lineNumber":1093,"sourceCode":"\t\t}\n\n\t\treturn Folder.save(modifiedFolder, { autoTimestamp: false });\n\t}\n\n\t// These \"duplicateCheck\" and \"reservedTitleCheck\" should only be done when a user is\n\t// manually creating a folder. They shouldn't be done for example when the folders\n\t// are being synced to avoid any strange side-effects. Technically it's possible to\n\t// have folders and notes with duplicate titles (or no title), or with reserved words.\n\tpublic static async save(o: FolderEntity, options: SaveOptions & { duplicateCheck?: boolean; reservedTitleCheck?: boolean; stripLeftSlashes?: boolean } = null) {\n\t\tif (!options) options = {};\n\n\t\tif (options.userSideValidation === true) {\n\t\t\tif (!('duplicateCheck' in options)) options.duplicateCheck = true;\n\t\t\tif (!('reservedTitleCheck' in options)) options.reservedTitleCheck = true;\n\t\t\tif (!('stripLeftSlashes' in options)) options.stripLeftSlashes = true;\n\n\t\t\tif (o.id && o.parent_id && o.id === o.parent_id) {\n\t\t\t\tthrow new Error('Parent ID cannot be the same as ID');\n\t\t\t}\n\t\t}\n\n\t\tif (options.stripLeftSlashes === true && o.title) {\n\t\t\twhile (o.title.length && (o.title[0] === '/' || o.title[0] === '\\\\')) {\n\t\t\t\to.title = o.title.substr(1);\n\t\t\t}\n\t\t}\n\n\t\t// We allow folders with duplicate titles so that folders with the same title can exist under different parent folder. For example:\n\t\t//\n\t\t// PHP\n\t\t//     Code samples\n\t\t//     Doc\n\t\t// Java\n\t\t//     My project\n\t\t//     Doc\n","sourceCodeStart":1075,"sourceCodeEnd":1111,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/lib/models/Folder.ts#L1075-L1111","documentation":"Thrown by Folder.save when options.userSideValidation is true and the entity being saved has a non-empty o.id and o.parent_id that are strictly equal (packages/lib/models/Folder.ts:1093). The guard prevents a notebook from becoming its own parent, which would create a self-referencing cycle in the folder tree. It only runs for user-initiated saves; sync and other internal paths skip userSideValidation by design (see the comment above the method).","triggerScenarios":"Calling Folder.save(folder, { userSideValidation: true }) (or BatchSave/saveUserIpc paths that set it) with a FolderEntity whose parent_id was set to its own id — e.g. moving/dragging a notebook into itself in the UI, or constructing a folder from a form where the parent selector defaulted to the folder's own id.","commonSituations":"Drag-and-drop UI bug that assigns the dropped folder's id as its new parent; an import/migration script copying parent_id onto the folder itself; a reducer that erroneously reuses an entity's id as parent_id on update; tests that build a folder and forget to clear/guard parent_id.","solutions":["At the call site, verify the chosen parent is not the folder itself before calling save (reject the move in the UI layer).","If this fires unexpectedly, inspect the entity: print o.id and o.parent_id to find which code path assigned the self-reference.","Ensure sync/import code does not set userSideValidation:true when it merely needs to persist a server-supplied tree (that path intentionally skips this check).","Add a UI guard disabling 'move into self' / 'set parent to current notebook' in the notebook picker."],"exampleFix":"// before\nfolder.parent_id = targetFolder.id;\nawait Folder.save(folder, { userSideValidation: true });\n\n// after\nif (folder.id && folder.id === targetFolder.id) {\n  throw new Error('A notebook cannot be moved into itself');\n}\nfolder.parent_id = targetFolder.id;\nawait Folder.save(folder, { userSideValidation: true });","handlingStrategy":"validation","validationCode":"// Reject self-parenting before it reaches the model layer.\nfunction isValidParent(folder: FolderEntity, newParentId: string | null | undefined): boolean {\n  return !folder.id || !newParentId || folder.id !== newParentId;\n}\n\nif (!isValidParent(folder, folder.parent_id)) {\n  throw new Error('A notebook cannot be its own parent');\n}","typeGuard":"function hasSelfReference(o: { id?: string; parent_id?: string | null }): boolean {\n  return Boolean(o.id) && Boolean(o.parent_id) && o.id === o.parent_id;\n}","tryCatchPattern":"// Save is part of a user action — surface a clear message, don't swallow.\ntry {\n  await Folder.save(folder, { userSideValidation: true });\n} catch (error) {\n  if (error.message === 'Parent ID cannot be the same as ID') {\n    throw new Error('You cannot move a notebook into itself.');\n  }\n  throw error;\n}","preventionTips":["In notebook picker / drag-drop UI, disable selecting the folder being moved as its own destination.","Validate parent_id !== id in form/reducer code before constructing the FolderEntity to be saved.","Keep internal sync/import code paths from setting userSideValidation: true — that flag exists specifically to gate these user-side checks.","When copying fields between folder objects, never blindly copy another folder's id into parent_id."],"tags":["model","folder","validation","tree-cycle"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}