Eugeny/tabby · error · Error
WinSCP not found
Error message
WinSCP not found
What it means
Thrown by `convertPrivateKeyFileToPuTTYFormat` when `SSHService.getWinSCPPath()` returns null - i.e. WinSCP is not registered on the system. The path is detected by reading the Windows registry key `HKCR\\WinSCP.Url\\DefaultIcon`; if the key is absent (WinSCP not installed) the conversion cannot proceed because it depends on WinSCP's key tooling.
Source
Thrown at tabby-ssh/src/services/ssh.service.ts:88
const xTunnelParams = await this.generateWinSCPXTunnelURI(jumpHostProfile)
uri += xTunnelParams.uri ?? ''
tmpFile = xTunnelParams.privateKeyFile ?? null
}
if (profile.options.host.includes(':')) {
uri += `@[${profile.options.host}]:${profile.options.port}${cwd ?? '/'}`
}else {
uri += `@${profile.options.host}:${profile.options.port}${cwd ?? '/'}`
}
return { uri, privateKeyFile: tmpFile?? null }
}
async convertPrivateKeyFileToPuTTYFormat (profile: SSHProfile): Promise<{ passphrase: string|null, privateKeyFile: tmp.FileResult|null }> {
if (profile.options.privateKeys.length === 0) {
throw new Error('No private keys in profile')
}
const path = this.getWinSCPPath()
if (!path) {
throw new Error('WinSCP not found')
}
let tmpPrivateKeyFile: tmp.FileResult|null = null
let passphrase: string|null = null
const tmpFile: tmp.FileResult = await tmp.file()
for (const pk of profile.options.privateKeys) {
let privateKeyContent: string|null = null
const buffer = await this.fileProviders.retrieveFile(pk)
privateKeyContent = buffer.toString()
await fs.writeFile(tmpFile.path, privateKeyContent)
const keyHash = crypto.createHash('sha512').update(privateKeyContent).digest('hex')
// need to pass an default passphrase, otherwise it might get stuck at the passphrase input
const curPassphrase = await this.passwordStorage.loadPrivateKeyPassword(keyHash) ?? 'tabby'
const winSCPcom = path.slice(0, -3) + 'com'
try {
await this.platform.exec(winSCPcom, ['/keygen', tmpFile.path, '-o', tmpFile.path, '--old-passphrase', curPassphrase])
} catch (error) {
console.warn('Could not convert private key ', error)
continueView on GitHub (pinned to 14e2d60b9b)
Solutions
- Install WinSCP (the installer registers `WinSCP.Url`) on the Windows host and retry.
- If using a portable WinSCP, register the `WinSCP.Url\\DefaultIcon` registry key pointing at the portable executable.
- Guard callers: check `ssh.getWinSCPPath()` before offering the PuTTY-conversion flow and disable the UI if null.
- On non-Windows platforms, do not surface the WinSCP-dependent feature at all.
Example fix
// before
const path = this.getWinSCPPath()
if (!path) throw new Error('WinSCP not found')
// caller guard
const winScp = this.ssh.getWinSCPPath()
if (!winScp) {
this.notifications.error('WinSCP is not installed; install it to use PuTTY key conversion')
return
}
await this.ssh.convertPrivateKeyFileToPuTTYFormat(profile) Defensive patterns
Strategy: validation
Validate before calling
function isWinScpAvailable (ssh: SSHService): boolean {
return ssh.getWinSCPPath() !== null
}
if (!isWinScpAvailable(this.ssh)) {
this.notifications.error('WinSCP is not installed; install it to use PuTTY key conversion')
return
}
await this.ssh.convertPrivateKeyFileToPuTTYFormat(profile) Type guard
function isWinScpPath (p: string | null): p is string { return typeof p === 'string' && p.length > 0 } Try / catch
try {
await this.ssh.convertPrivateKeyFileToPuTTYFormat(profile)
} catch (e) {
if (e instanceof Error && e.message === 'WinSCP not found') {
this.notifications.error('Install WinSCP to enable PuTTY key conversion')
return
}
throw e
} Prevention
- Install WinSCP via its official installer so the WinSCP.Url registry key is registered.
- Guard callers with getWinSCPPath() and disable the UI affordance when null.
- Do not surface WinSCP-dependent features on non-Windows hosts.
- For portable WinSCP, register the WinSCP.Url\\DefaultIcon registry key manually.
When it happens
Trigger: Calling the conversion on a machine without WinSCP installed/registered, or where the registry entry was removed. The `wnr.getRegistryKey(...)` lookup yields no `['']` default value, so `getWinSCPPath()` returns null.
Common situations: WinSCP never installed; WinSCP uninstalled but registry entries left partially; portable WinSCP that does not register the `WinSCP.Url` protocol handler; running on macOS/Linux (no registry, wnr is a no-op).
Related errors
- No private keys in profile
- Download cancelled
- ${profile.options.host}: jump host "${profile.options.jumpHo
- File handle is closed
- Remote rejected opening a shell channel: ${err}
AI-assisted analysis of Eugeny/tabby@14e2d60b9b (2026-08-12).
Data as JSON: /api/errors/17119106db29f020.
Report an issue: GitHub.