{"record":{"id":"87fa0af25598ef68","repo":"stablyai/orca","slug":"cannot-download-to-a-directory","errorCode":null,"errorMessage":"Cannot download to a directory","messagePattern":"Cannot download to a directory","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ipc/filesystem.ts","lineNumber":244,"sourceCode":"const DOWNLOAD_SESSION_TTL_MS = 30 * 60 * 1000\n\nfunction createSiblingTransferPath(destinationPath: string, suffix: string): string {\n  // Why: promotion renames must stay on the destination volume, so transfer paths remain siblings.\n  return join(dirname(destinationPath), `.${randomUUID()}.${suffix}`)\n}\n\nasync function cleanupLocalTransferPath(filePath: string | null): Promise<void> {\n  if (!filePath) {\n    return\n  }\n  await rm(filePath, { force: true }).catch(() => {})\n}\n\nasync function inspectDownloadDestination(destinationPath: string): Promise<{ existed: boolean }> {\n  try {\n    const destinationStat = await stat(destinationPath)\n    if (destinationStat.isDirectory()) {\n      throw new Error('Cannot download to a directory')\n    }\n    return { existed: true }\n  } catch (error) {\n    if (isENOENT(error)) {\n      return { existed: false }\n    }\n    throw error\n  }\n}\n\nasync function assertDestinationStillUnclaimed(destinationPath: string): Promise<void> {\n  try {\n    await stat(destinationPath)\n  } catch (error) {\n    if (isENOENT(error)) {\n      return\n    }\n    throw error","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/src/main/ipc/filesystem.ts#L226-L262","documentation":"Thrown by inspectDownloadDestination when the user-selected save path already exists and is a directory. The function stats the destination before download begins; if it is a directory, the download cannot proceed because overwriting a directory with a file would be destructive or impossible.","triggerScenarios":"In the fs:saveDownloadedFile or fs:downloadFile flow, the user picks a save path in the dialog that points to an existing directory. The stat on that path returns isDirectory() true.","commonSituations":"The user types a name in the save dialog that matches an existing folder name. The user selects an existing directory entry from the file browser. The defaultPath suggestion collides with a directory already in the target folder.","solutions":["Choose a different filename in the save dialog that does not match an existing directory.","Rename or remove the conflicting directory before saving.","In the renderer, check the save path after the dialog returns and warn the user if it is a directory."],"exampleFix":"// before: user selects /downloads/myproject (an existing folder) as save target\n// after: user picks a file path\n//   /downloads/myproject.txt or /downloads/myproject-export.json","handlingStrategy":"validation","validationCode":"const { stat } = await import('node:fs/promises')\n\nasync function assertDownloadTargetIsNotDirectory(destinationPath: string): Promise<void> {\n  try {\n    const st = await stat(destinationPath)\n    if (st.isDirectory()) {\n      throw new Error(`${destinationPath} is a directory — choose a file path`)\n    }\n  } catch (error: any) {\n    if (error.code === 'ENOENT') return // path is free\n    throw error\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await ipcRenderer.invoke('fs:saveDownloadedFile', args)\n} catch (error) {\n  if (error instanceof Error && error.message === 'Cannot download to a directory') {\n    showUserError('The chosen path is a directory. Select a file name.')\n    return\n  }\n  throw error\n}","preventionTips":["After the save dialog returns, verify the path is not an existing directory before proceeding.","Use a file extension in the defaultPath suggestion to discourage directory selection.","Validate the dialog result in the renderer before calling the download IPC."],"tags":["download","filesystem","validation","ipc"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}