diff --git a/apps/desktop/src/lib/desktop-fs.test.ts b/apps/desktop/src/lib/desktop-fs.test.ts index c45ffb6745a..664937cb806 100644 --- a/apps/desktop/src/lib/desktop-fs.test.ts +++ b/apps/desktop/src/lib/desktop-fs.test.ts @@ -5,6 +5,7 @@ import { $connection } from '@/store/session' import { desktopDefaultCwd, desktopGitRoot, + desktopFsCacheKey, readDesktopDir, readDesktopFileDataUrl, readDesktopFileText, @@ -113,4 +114,25 @@ describe('desktop filesystem facade', () => { expect(remoteSelect).not.toHaveBeenCalled() expect(selectPaths).not.toHaveBeenCalled() }) + + it('cache key distinguishes two SSH hosts that share the same local forwarded port', () => { + // Both remotes resolve to the same loopback tunnel baseUrl (the local + // forwarded port is reusable across remotes). Without the remoteHost in the + // identity these collide and one host's cached fs reads serve the other. + $connection.set({ mode: 'remote', baseUrl: 'http://127.0.0.1:50001', remoteHost: 'jonny@mac-mini' } as never) + const keyA = desktopFsCacheKey() + $connection.set({ mode: 'remote', baseUrl: 'http://127.0.0.1:50001', remoteHost: 'jonny@ubuntu-box' } as never) + const keyB = desktopFsCacheKey() + + expect(keyA).not.toBe(keyB) + expect(keyA).toContain('mac-mini') + expect(keyB).toContain('ubuntu-box') + }) + + it('cache key falls back to baseUrl when no remoteHost is present', () => { + $connection.set({ mode: 'remote', baseUrl: 'https://box.tail1234.ts.net' } as never) + expect(desktopFsCacheKey()).toContain('box.tail1234.ts.net') + $connection.set(null) + expect(desktopFsCacheKey()).toBe('local:') + }) }) diff --git a/apps/desktop/src/lib/desktop-fs.ts b/apps/desktop/src/lib/desktop-fs.ts index b57701013e6..0ed21855716 100644 --- a/apps/desktop/src/lib/desktop-fs.ts +++ b/apps/desktop/src/lib/desktop-fs.ts @@ -21,7 +21,14 @@ function connectionCacheKey(connection: HermesConnection | null) { if (!connection) { return 'local:' } - return `${connection.mode || 'local'}:${connection.profile || ''}:${connection.baseUrl || ''}` + // The remote host is part of the cache identity, NOT just the baseUrl. Local + // forwarded ports are reusable across different remotes, so two SSH hosts + // that happen to map to the same 127.0.0.1: would otherwise + // collide — serving one host's cached fs reads for the other. remoteHost is + // the user@host (SSH) or the real backend host (token/oauth); fall back to + // baseUrl for safety. + const host = connection.remoteHost || connection.baseUrl || '' + return `${connection.mode || 'local'}:${connection.profile || ''}:${host}:${connection.baseUrl || ''}` } export function desktopFsCacheKey() {