{"record":{"id":"a4b101eaac52364f","repo":"BabylonJS/Babylon.js","slug":"persistent-anchor-restoration-was-interrupted-befo","errorCode":null,"errorMessage":"Persistent anchor restoration was interrupted before tracking began","messagePattern":"Persistent anchor restoration was interrupted before tracking began","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/dev/core/src/XR/features/WebXRAnchorSystem.pure.ts","lineNumber":343,"sourceCode":"     * @param handle The persistent anchor handle to restore\r\n     * @returns A promise that resolves after the restored anchor is tracked by an XR frame\r\n     * @throws If restoring persistent anchors is not supported by the current session\r\n     */\r\n    public async restorePersistentAnchorAsync(handle: string): Promise<IWebXRAnchor> {\r\n        if (!this.attached) {\r\n            throw new Error(\"Restoring persistent anchors requires the anchor system to be attached\");\r\n        }\r\n\r\n        const session = this._xrSessionManager.session;\r\n        const restorePersistentAnchor = session?.restorePersistentAnchor;\r\n        if (!restorePersistentAnchor) {\r\n            throw new Error(\"Restoring persistent anchors is not supported in this environment/browser\");\r\n        }\r\n\r\n        const nativeAnchor = await restorePersistentAnchor.call(session, handle);\r\n        if (!this.attached || this._xrSessionManager.session !== session) {\r\n            nativeAnchor.delete();\r\n            throw new Error(\"Persistent anchor restoration was interrupted before tracking began\");\r\n        }\r\n        const existingAnchorIndex = this._findIndexInAnchorArray(nativeAnchor);\r\n        if (existingAnchorIndex !== -1) {\r\n            const existingAnchor = this._trackedAnchors[existingAnchorIndex];\r\n            existingAnchor.persistentHandle = handle;\r\n            return existingAnchor;\r\n        }\r\n\r\n        return await new Promise<IWebXRAnchor>((resolve, reject) => {\r\n            this._futureAnchors.push({\r\n                nativeAnchor,\r\n                resolved: false,\r\n                submitted: true,\r\n                persistentHandle: handle,\r\n                resolve,\r\n                reject,\r\n            });\r\n        });\r","sourceCodeStart":325,"sourceCodeEnd":361,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/core/src/XR/features/WebXRAnchorSystem.pure.ts#L325-L361","documentation":"During restorePersistentAnchorAsync, after awaiting the native restorePersistentAnchor call, the code re-checks that the feature is still attached and that the session has not changed (e.g. session ended and a new one started). If that invariant broke while awaiting, the freshly restored native anchor is deleted and this error is thrown — the restore was valid on the native side but can no longer be surfaced to the caller.","triggerScenarios":"The XR session ends or the anchor system detaches/reattaches (or _xrSessionManager.session is swapped) between the restorePersistentAnchor call and its completion — typically user exits VR mid-restore or app tears down XR during startup restoration.","commonSituations":"Race conditions when restoring many persistent anchors at app entry while the user exits the headset, timeout/disconnect of the session, calling restorePersistentAnchorsAsync while simultaneously disposing the scene/XR helpers.","solutions":["Catch this error and treat the handle as un-restored; optionally retry after the next session starts","Stop XR teardown until pending anchor restorations settle (await Promise.all before dispose)","Listen to session end events and cancel/ignore in-flight restoration bookkeeping","Re-run restorePersistentAnchorsAsync on the new session once attached"],"exampleFix":"// before\nawait anchorSystem.restorePersistentAnchorsAsync();\ndisposeXr();\n// after\nawait anchorSystem.restorePersistentAnchorsAsync();\n// then, guarded by no pending work:\nawait Promise.allSettled(pendingRestores);\ndisposeXr();","handlingStrategy":"try-catch","validationCode":"// wait for all in-flight restores before ending the session or disposing XR\nawait Promise.allSettled(pendingRestorePromises);\nif (anchorSystem.attached && xrSessionManager.session) {\n  // safe to proceed\n}","typeGuard":"function restoreStillValid(anchorSystem: WebXRAnchorSystem, session: XRSession): boolean {\n  return anchorSystem.attached && anchorSystem['_xrSessionManager'].session === session;\n}","tryCatchPattern":"try {\n  const anchor = await anchorSystem.restorePersistentAnchorAsync(handle);\n} catch (e) {\n  if (/interrupted/.test((e as Error).message)) {\n    console.warn('session changed during restore; retry on next session');\n  }\n}","preventionTips":["Await all pending restorations before disposing XR","Re-run restorePersistentAnchorsAsync after any new session starts","Listen to session end events to cancel restore bookkeeping","Avoid tearing down XR concurrently with startup restore flows"],"tags":["webxr","anchors","race-condition","lifecycle"],"backgroundTag":"xr-session-interrupted","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}