{"record":{"id":"f67243a24c5edcb0","repo":"immerjs/immer","slug":"cannot-resolve-path-at-path-join","errorCode":null,"errorMessage":"Cannot resolve path at '${path.join(\"/\")}'","messagePattern":"Cannot resolve path at '(.+?)'","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"src/plugins/patches.ts","lineNumber":122,"sourceCode":"\n\t\ttry {\n\t\t\t// Validate path can be resolved from ROOT\n\t\t\tresolvePath(state.copy_, path)\n\t\t} catch (e) {\n\t\t\treturn null // Path invalid\n\t\t}\n\n\t\treturn path\n\t}\n\n\t// NEW: Add resolvePath helper function\n\tfunction resolvePath(base: any, path: PatchPath): any {\n\t\tlet current = base\n\t\tfor (let i = 0; i < path.length - 1; i++) {\n\t\t\tconst key = path[i]\n\t\t\tcurrent = get(current, key)\n\t\t\tif (!isObjectish(current) || current === null) {\n\t\t\t\tthrow new Error(`Cannot resolve path at '${path.join(\"/\")}'`)\n\t\t\t}\n\t\t}\n\t\treturn current\n\t}\n\n\tconst REPLACE = \"replace\"\n\tconst ADD = \"add\"\n\tconst REMOVE = \"remove\"\n\n\tfunction generatePatches_(\n\t\tstate: ImmerState,\n\t\tbasePath: PatchPath,\n\t\tscope: ImmerScope\n\t): void {\n\t\tif (state.scope_.processedForPatches_.has(state)) {\n\t\t\treturn\n\t\t}\n","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/immerjs/immer/blob/d2c158f5bac7081a760bbaf501ea5c360b7856e1/src/plugins/patches.ts#L104-L140","documentation":"Thrown inside the local resolvePath helper (src/plugins/patches.ts:116) during patch generation. It walks a draft's path segment by segment from the root copy_ via get(), and throws when an intermediate node is not traversable (typeof !== \"object\", or null). This is an internal control-flow sentinel: its only call site (src/plugins/patches.ts:107) sits inside try/catch and converts the throw into a null return from getPath, so it does not normally escape to a caller. It means a nested draft's location can no longer be reached from the root because the object graph was restructured.","triggerScenarios":"getPath rebuilds a nested draft's path by walking parent_ links, then validates it against state.copy_ by calling resolvePath. The throw fires when an intermediate value along that path is a primitive, string, boolean, number, undefined, function, or null - i.e. a container in the path was replaced, truncated, nulled, or spliced after the child draft was created, leaving the stored path stale.","commonSituations":"Replacing or truncating a container after grabbing a nested draft (e.g. draft.items = [] while editing draft.items[0]); reassigning draft.a.b = null while holding a draft of draft.a.b.c; splicing an array that holds drafts; reassigning a Map key's value mid-edit. Because the throw is caught (patches.ts:108), the visible effect is that the draft's path is dropped - the patch is emitted at a different level or skipped - rather than a crash.","solutions":["Mutate nested values in place instead of replacing their parent container (set draft.items[0].value directly rather than reassigning draft.items and then editing an orphaned child).","Do not capture a nested draft reference (const child = draft.a.b) and then restructure draft.a or draft before finishing the child's edit.","If you must restructure the tree, re-obtain the nested draft from the root afterward so its path reflects the new structure.","This throw is internally caught at src/plugins/patches.ts:108; if you see it escape, suspect a modified/patched Immer build or a direct call into getPath/resolvePath outside the library."],"exampleFix":"// before: replacing the container orphans the nested draft, path no longer resolves\nproduce(state, draft => {\n  const child = draft.items[0]   // nested draft captured\n  draft.items = []               // parent replaced -> child detached\n  child.value = 1                // path to child can't resolve from root\n})\n\n// after: mutate in place so the path stays valid\nproduce(state, draft => {\n  draft.items[0].value = 1\n})","handlingStrategy":"validation","validationCode":"// Before editing a nested draft captured earlier, confirm the path still\n// resolves through object/map nodes from the root state.\nimport {isDraft} from \"immer\"\n\nfunction pathStillResolves(root: any, path: Array<string | number>): boolean {\n  let cur: any = root\n  for (const seg of path) {\n    if (cur === null || typeof cur !== \"object\") return false\n    cur = cur instanceof Map ? cur.get(seg) : cur[seg as any]\n  }\n  return typeof cur === \"object\" && cur !== null\n}\n\n// usage: only edit the child if its container chain is intact\nif (pathStillResolves(state, [\"items\", 0])) {\n  produce(state, draft => { draft.items[0].value = 1 })\n}","typeGuard":"// Narrow to a value that can still be descended into along a path.\nconst isTraversable = (v: unknown): v is Record<PropertyKey, unknown> =>\n  v !== null && typeof v === \"object\"","tryCatchPattern":null,"preventionTips":["Edit nested draft values in place; do not replace or truncate a container (draft.items = [], draft.a = null) while a draft of its descendant is still in use.","Do not cache nested draft references across a restructuring operation - re-derive them from the root after the tree changes.","Remember this throw is internally caught at src/plugins/patches.ts:108 and turned into a null path; if you observe odd/missing patches rather than a crash, suspect detached nested drafts.","Keep producer functions short and local to the subtree they mutate so container lifetimes never overlap child edits."],"tags":["patches","draft-path","object-graph","internal","control-flow"],"backgroundTag":null,"analyzedSha":"d2c158f5bac7081a760bbaf501ea5c360b7856e1","analyzedAt":"2026-08-13T09:59:10.681Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}