{"record":{"id":"b007c54d8d0054b9","repo":"CherryHQ/cherry-studio","slug":"session-workspace-is-unavailable-workspaceroot","errorCode":null,"errorMessage":"Session workspace is unavailable: ${workspaceRoot}","messagePattern":"Session workspace is unavailable: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/channels/security/WorkspaceFileGuard.ts","lineNumber":33,"sourceCode":" * inside it. `realpath` defeats `../` and symlink escape; reading happens on a single\n * fd over the canonical path so the stat/size check and the read see the same inode.\n *\n * This is defense-in-depth against traversal mistakes and prompt injection picking a\n * wrong path — not a sandbox against an agent with code execution (which can already\n * read arbitrary files and exfiltrate them as message text). See #16566.\n */\nexport async function resolveWorkspaceFile(workspaceRoot: string, userPath: string): Promise<FileAttachment> {\n  const requested = path.resolve(workspaceRoot, userPath)\n\n  let realRoot: string\n  try {\n    realRoot = await realpath(workspaceRoot)\n  } catch (error) {\n    // The root is a caller invariant, but if the session workspace is gone a bare ENOENT\n    // naming the root reads like \"your file_path is wrong\" — wrap it so the agent doesn't\n    // waste retries on other paths.\n    if (isErrnoException(error) && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) {\n      throw new Error(`Session workspace is unavailable: ${workspaceRoot}`)\n    }\n    throw error\n  }\n\n  let realTarget: string\n  try {\n    realTarget = await realpath(requested)\n  } catch (error) {\n    if (isErrnoException(error) && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) {\n      throw new Error(`File not found in workspace: ${userPath}`)\n    }\n    throw error\n  }\n\n  if (realTarget !== realRoot && !realTarget.startsWith(realRoot + path.sep)) {\n    throw new Error(`Path is outside the workspace: ${userPath}`)\n  }\n","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/channels/security/WorkspaceFileGuard.ts#L15-L51","documentation":"Thrown by resolveWorkspaceFile() when realpath(workspaceRoot) fails with ENOENT or ENOTDIR. Unlike the per-file 'File not found' error, this specifically indicates the workspace root directory itself is gone. The comment in the source explains: a bare ENOENT naming the root would read like 'your file_path is wrong', so this is wrapped to tell the agent the session workspace itself is unavailable, preventing wasted retries on alternative file paths.","triggerScenarios":"Called from agent document tools (cherryDocumentTools.ts:84, cherryAutonomyTools.ts:436) when an agent attempts to read a workspace file but the workspace directory was deleted, unmounted, or the session's workspacePath was never created. This is a caller-invariant violation: the workspace root is expected to exist for the entire session lifetime.","commonSituations":"The session workspace temp directory was cleaned by an OS temp-reaper or manual cleanup while the agent was still running; a crash-restart scenario where the workspace path was persisted but the directory was not recreated; the workspace was on a removable drive that was ejected; a workspace creation race where the agent started before the directory was fully set up.","solutions":["Verify the session workspace directory still exists at the path given by workspaceRoot — if it was cleaned, recreate it or start a new session.","If this is a crash-restart path, ensure the session lifecycle recreates the workspace directory before rehydrating agent state.","If running on a temp directory, increase the OS temp-cleanup interval or move workspaces to a stable application-managed path via application.getPath()."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import { exists } from '@main/utils/file'\n\n// Check workspace exists before resolving files\nconst workspaceExists = await exists(workspaceRoot)\nif (!workspaceExists) {\n  throw new Error(`Workspace not found, recreating or starting new session: ${workspaceRoot}`)\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await resolveWorkspaceFile(workspaceRoot, userPath)\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith('Session workspace is unavailable:')) {\n    // The workspace itself is gone — no retry on alternative paths will help\n    logger.error('Session workspace missing', { workspaceRoot })\n    throw new Error('Session workspace was deleted. Start a new session.')\n  }\n  throw error\n}","preventionTips":["Use application.getPath() for workspace roots to ensure they live in a stable, app-managed directory.","Implement workspace lifecycle hooks that verify directory existence on session resume.","Avoid OS temp directories for long-lived sessions — temp reapers can clean them unexpectedly."],"tags":["filesystem","workspace","session","enoent","channels"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}