{"record":{"id":"8fe4f62390c628c6","repo":"can1357/oh-my-pi","slug":"could-not-relocate-the-session-back-to-snapshot","errorCode":null,"errorMessage":"could not relocate the session back to ${snapshot.sessionDir} (${error instanceof Error ? error.message : String(error)}); the session file remains at ${movedFile}","messagePattern":"could not relocate the session back to (.+?) \\((.+?)\\); the session file remains at (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/session/session-manager.ts","lineNumber":1376,"sourceCode":"\t/**\n\t * Undo a {@link moveTo} using a {@link captureState} snapshot: rename the\n\t * session and artifacts back into the captured bucket, then restore the\n\t * captured metadata (cwd, header, additionalDirectories). The captured\n\t * header is persisted after relocation so a fresh open of the source\n\t * session sees the pre-move metadata, including workspace roots the move\n\t * filtered out. Rollbacks must not re-enter forward-move hooks, so this\n\t * bypasses AgentSession entirely. If the rename-back itself fails, the\n\t * manager stays pointed at the actual moved file (restoring the snapshot\n\t * would split the transcript across a recreated source and the stranded\n\t * target) and the error names where the session file actually lives.\n\t */\n\tasync rollbackMove(snapshot: SessionManagerStateSnapshot): Promise<void> {\n\t\ttry {\n\t\t\tconst targetSessionDir = snapshot.sessionFile ? path.dirname(snapshot.sessionFile) : snapshot.sessionDir;\n\t\t\tawait this.moveTo(snapshot.cwd, targetSessionDir);\n\t\t} catch (error) {\n\t\t\tconst movedFile = this.getSessionFile();\n\t\t\tthrow new Error(\n\t\t\t\t`could not relocate the session back to ${snapshot.sessionDir} (${error instanceof Error ? error.message : String(error)}); the session file remains at ${movedFile}`,\n\t\t\t);\n\t\t}\n\t\tthis.restoreState(snapshot);\n\t\t// The inverse moveTo already rewrote the source file with the\n\t\t// target-filtered header. Persist the captured one so disk and memory\n\t\t// agree after a fresh open.\n\t\tif (this.#persist && this.#sessionFile) {\n\t\t\tthis.#forceFileCreation = true;\n\t\t\tthis.#rewriteRequired = true;\n\t\t\tawait this.#rewriteAtomically();\n\t\t}\n\t}\n\t/** Switch to a different session file (resume / branch). */\n\tasync setSessionFile(sessionFile: string): Promise<void> {\n\t\tawait this.#setSessionFile(sessionFile);\n\t}\n","sourceCodeStart":1358,"sourceCodeEnd":1394,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/session-manager.ts#L1358-L1394","documentation":"rollbackMove() attempts to relocate a session back to its original directory captured in a state snapshot. If that move fails, the original moveTo error is wrapped in this message that reports both the intended destination and where the session file currently sits, so the caller knows the session was NOT restored and data is at movedFile. The wrapper prevents losing the underlying cause while leaving state unrestored.","triggerScenarios":"Calling SessionManager.rollbackMove(snapshot) when moveTo(snapshot.cwd, targetSessionDir) throws — e.g. the original directory no longer exists, permissions changed, the target path is not writable, or snapshot.sessionFile points into a removed directory.","commonSituations":"Undoing a session move after the original project directory was deleted or renamed; read-only filesystem; cross-device rename restrictions; concurrent process holding a lock on the destination.","solutions":["Recreate the original session directory (snapshot.sessionDir) with write permissions, then call rollbackMove again.","Manually move the file from getSessionFile()/movedFile back to the target directory.","If the original directory is intentionally gone, adopt the current location instead of rolling back.","Read the inner parenthesized error for the precise filesystem cause (ENOENT, EACCES, EBUSY)."],"exampleFix":"// before\nawait mgr.rollbackMove(snapshot); // ENOENT if dir deleted\n// after\nif (!existsSync(snapshot.sessionDir)) {\n  mkdirSync(snapshot.sessionDir, { recursive: true });\n}\nawait mgr.rollbackMove(snapshot);","handlingStrategy":"try-catch","validationCode":"if (!existsSync(snapshot.sessionDir)) {\n  mkdirSync(snapshot.sessionDir, { recursive: true });\n}","typeGuard":"null","tryCatchPattern":"try {\n  await mgr.rollbackMove(snapshot);\n} catch (err) {\n  // session file is still at the path in err.message; move it manually\n  logger.error(\"Rollback failed; session left in place\", { err });\n}","preventionTips":["Ensure the original directory exists and is writable before moving a session away.","Avoid moving sessions onto different filesystems (rename may fail with EXDEV).","Don't delete/rename the source project directory while a session move is in flight."],"tags":["session","filesystem","rollback"],"backgroundTag":"session-move-rollback-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}