stablyai/orca · error · Error

Cannot download a directory

Error message

Cannot download a directory

What it means

Thrown by the fs:downloadFile IPC handler when the SSH remote path resolves to a directory. The handler calls provider.stat on the remote path; if remoteStat.type is 'directory', the download is refused because the handler downloads individual files, not directory trees.

Source

Thrown at src/main/ipc/filesystem.ts:647

        return { content: '', isBinary: true }
      }

      return { content: buffer.toString('utf-8'), isBinary: false }
    }
  )

  ipcMain.handle(
    'fs:downloadFile',
    async (
      event,
      args: { filePath?: string; connectionId?: string }
    ): Promise<DownloadFileResult> => {
      const filePath = validateRequiredString(args?.filePath, 'filePath')
      const connectionId = validateRequiredString(args?.connectionId, 'connectionId')
      const provider = requireSshFilesystemProvider(connectionId)
      const remoteStat = await provider.stat(filePath)
      if (remoteStat.type === 'directory') {
        throw new Error('Cannot download a directory')
      }
      if (!provider.downloadFile) {
        throw new Error('Remote file download is unavailable. Reconnect the SSH target and retry.')
      }

      const remoteBasename = getRuntimePathBasename(filePath)
      const defaultPath = sanitizeLocalDownloadFilename(remoteBasename)
      const parentWindow = BrowserWindow.fromWebContents(event.sender) ?? undefined
      const dialogResult = parentWindow
        ? await dialog.showSaveDialog(parentWindow, { defaultPath })
        : await dialog.showSaveDialog({ defaultPath })
      if (dialogResult.canceled || !dialogResult.filePath) {
        return { canceled: true }
      }

      const destinationPath = dialogResult.filePath
      const { existed } = await inspectDownloadDestination(destinationPath)
      const tempPath = createSiblingTransferPath(destinationPath, 'download')

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Select a file (not a directory) in the remote file browser before triggering download.
  2. If you need the entire directory, use scp/rsync from a terminal to recursively download it.
  3. In the renderer, check the entry type before enabling the download action.

Example fix

// before: download remote path /home/user/project (a directory)
// after: download a specific file
//   /home/user/project/README.md
//
// or use scp for directories:
//   scp -r user@host:/home/user/project ./local-project
Defensive patterns

Strategy: type-guard

Validate before calling

// Before calling fs:downloadFile, check the remote entry type
async function assertRemoteIsFile(provider: SshFilesystemProvider, filePath: string): Promise<void> {
  const st = await provider.stat(filePath)
  if (st.type === 'directory') {
    throw new Error(`${filePath} is a directory — select a file to download`)
  }
}

// Or in the renderer, check the entry metadata:
// if (selectedEntry.type === 'directory') {
//   showUserError('Select a file, not a directory')
//   return
// }

Type guard

function isRemoteFileEntry(entry: { type: string }): boolean {
  return entry.type === 'file'
}

// Usage in renderer:
// if (!isRemoteFileEntry(selectedEntry)) {
//   showUserError('Directories cannot be downloaded. Select a file.')
//   return
// }

Try / catch

try {
  await ipcRenderer.invoke('fs:downloadFile', { filePath, connectionId })
} catch (error) {
  if (error instanceof Error && error.message === 'Cannot download a directory') {
    showUserError('Select a file to download. Use scp -r for directories.')
    return
  }
  throw error
}

Prevention

When it happens

Trigger: Calling fs:downloadFile with a connectionId and filePath that points to a directory on the remote SSH host. The provider.stat call returns type 'directory' before the save dialog is shown.

Common situations: The user selects a directory entry in the remote file browser and triggers download. A path stored in state or clipboard is a directory rather than a file. The remote filesystem layout changed so a previously-file path is now a directory.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/3fcdd052917b7527. Report an issue: GitHub.