{"record":{"id":"17119106db29f020","repo":"Eugeny/tabby","slug":"winscp-not-found","errorCode":null,"errorMessage":"WinSCP not found","messagePattern":"WinSCP not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tabby-ssh/src/services/ssh.service.ts","lineNumber":88,"sourceCode":"            const xTunnelParams = await this.generateWinSCPXTunnelURI(jumpHostProfile)\n            uri += xTunnelParams.uri ?? ''\n            tmpFile = xTunnelParams.privateKeyFile ?? null\n        }\n        if (profile.options.host.includes(':')) {\n            uri += `@[${profile.options.host}]:${profile.options.port}${cwd ?? '/'}`\n        }else {\n            uri += `@${profile.options.host}:${profile.options.port}${cwd ?? '/'}`\n        }\n        return { uri, privateKeyFile: tmpFile?? null }\n    }\n\n    async convertPrivateKeyFileToPuTTYFormat (profile: SSHProfile): Promise<{ passphrase: string|null, privateKeyFile: tmp.FileResult|null }> {\n        if (profile.options.privateKeys.length === 0) {\n            throw new Error('No private keys in profile')\n        }\n        const path = this.getWinSCPPath()\n        if (!path) {\n            throw new Error('WinSCP not found')\n        }\n        let tmpPrivateKeyFile: tmp.FileResult|null = null\n        let passphrase: string|null = null\n        const tmpFile: tmp.FileResult = await tmp.file()\n        for (const pk of profile.options.privateKeys) {\n            let privateKeyContent: string|null = null\n            const buffer = await this.fileProviders.retrieveFile(pk)\n            privateKeyContent = buffer.toString()\n            await fs.writeFile(tmpFile.path, privateKeyContent)\n            const keyHash = crypto.createHash('sha512').update(privateKeyContent).digest('hex')\n            // need to pass an default passphrase, otherwise it might get stuck at the passphrase input\n            const curPassphrase = await this.passwordStorage.loadPrivateKeyPassword(keyHash) ?? 'tabby'\n            const winSCPcom = path.slice(0, -3) + 'com'\n            try {\n                await this.platform.exec(winSCPcom, ['/keygen', tmpFile.path, '-o', tmpFile.path, '--old-passphrase', curPassphrase])\n            } catch (error) {\n                console.warn('Could not convert private key ', error)\n                continue","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/Eugeny/tabby/blob/14e2d60b9b6dee84a53c37f05eefeb803787de04/tabby-ssh/src/services/ssh.service.ts#L70-L106","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before\nconst path = this.getWinSCPPath()\nif (!path) throw new Error('WinSCP not found')\n\n// caller guard\nconst winScp = this.ssh.getWinSCPPath()\nif (!winScp) {\n    this.notifications.error('WinSCP is not installed; install it to use PuTTY key conversion')\n    return\n}\nawait this.ssh.convertPrivateKeyFileToPuTTYFormat(profile)","handlingStrategy":"validation","validationCode":"function isWinScpAvailable (ssh: SSHService): boolean {\n    return ssh.getWinSCPPath() !== null\n}\n\nif (!isWinScpAvailable(this.ssh)) {\n    this.notifications.error('WinSCP is not installed; install it to use PuTTY key conversion')\n    return\n}\nawait this.ssh.convertPrivateKeyFileToPuTTYFormat(profile)","typeGuard":"function isWinScpPath (p: string | null): p is string { return typeof p === 'string' && p.length > 0 }","tryCatchPattern":"try {\n    await this.ssh.convertPrivateKeyFileToPuTTYFormat(profile)\n} catch (e) {\n    if (e instanceof Error && e.message === 'WinSCP not found') {\n        this.notifications.error('Install WinSCP to enable PuTTY key conversion')\n        return\n    }\n    throw e\n}","preventionTips":["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."],"tags":["ssh","winscp","windows","registry","dependencies"],"backgroundTag":null,"analyzedSha":"14e2d60b9b6dee84a53c37f05eefeb803787de04","analyzedAt":"2026-08-12T11:46:48.773Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}