Skip to content

Commit a9b70e4

Browse files
committed
fix: use stable cache key for SSH remote workspaces
- Generate cache key using workspace name and relative path from home - This ensures cache persistence across SSH sessions where absolute paths may change - Add comprehensive tests for the new cache key generation logic Fixes #6066
1 parent 984d368 commit a9b70e4

File tree

2 files changed

+86
-6
lines changed

2 files changed

+86
-6
lines changed

src/services/code-index/__tests__/cache-manager.spec.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import * as vscode from "vscode"
33
import { createHash } from "crypto"
44
import debounce from "lodash.debounce"
55
import { CacheManager } from "../cache-manager"
6+
import * as path from "path"
7+
import * as os from "os"
68

79
// Mock safeWriteJson utility
810
vitest.mock("../../../utils/safeWriteJson", () => ({
@@ -38,6 +40,11 @@ vitest.mock("@roo-code/telemetry", () => ({
3840
},
3941
}))
4042

43+
// Mock os module
44+
vitest.mock("os", () => ({
45+
homedir: vitest.fn(() => "/home/user"),
46+
}))
47+
4148
describe("CacheManager", () => {
4249
let mockContext: vscode.ExtensionContext
4350
let mockWorkspacePath: string
@@ -63,8 +70,18 @@ describe("CacheManager", () => {
6370
})
6471

6572
describe("constructor", () => {
66-
it("should correctly set up cachePath using Uri.joinPath and crypto.createHash", () => {
67-
const expectedHash = createHash("sha256").update(mockWorkspacePath).digest("hex")
73+
it("should correctly set up cachePath using Uri.joinPath with stable cache key", () => {
74+
// The cache key should be based on workspace name and relative path
75+
const workspaceName = path.basename(mockWorkspacePath)
76+
const homedir = os.homedir()
77+
let relativePath = mockWorkspacePath
78+
79+
if (mockWorkspacePath.startsWith(homedir)) {
80+
relativePath = path.relative(homedir, mockWorkspacePath)
81+
}
82+
83+
const compositeKey = `${workspaceName}::${relativePath}`
84+
const expectedHash = createHash("sha256").update(compositeKey).digest("hex")
6885

6986
expect(vscode.Uri.joinPath).toHaveBeenCalledWith(
7087
mockContext.globalStorageUri,
@@ -75,6 +92,36 @@ describe("CacheManager", () => {
7592
it("should set up debounced save function", () => {
7693
expect(debounce).toHaveBeenCalledWith(expect.any(Function), 1500)
7794
})
95+
96+
it("should generate stable cache key for workspace under home directory", () => {
97+
// os.homedir is already mocked to return '/home/user'
98+
const workspaceUnderHome = "/home/user/projects/myproject"
99+
const cacheManagerHome = new CacheManager(mockContext, workspaceUnderHome)
100+
101+
// Expected key should use relative path from home
102+
const expectedKey = `myproject::projects/myproject`
103+
const expectedHash = createHash("sha256").update(expectedKey).digest("hex")
104+
105+
expect(vscode.Uri.joinPath).toHaveBeenCalledWith(
106+
mockContext.globalStorageUri,
107+
`roo-index-cache-${expectedHash}.json`,
108+
)
109+
})
110+
111+
it("should generate stable cache key for workspace outside home directory", () => {
112+
// os.homedir is already mocked to return '/home/user'
113+
const workspaceOutsideHome = "/opt/projects/myproject"
114+
const cacheManagerOutside = new CacheManager(mockContext, workspaceOutsideHome)
115+
116+
// Expected key should use full path since it's outside home
117+
const expectedKey = `myproject::/opt/projects/myproject`
118+
const expectedHash = createHash("sha256").update(expectedKey).digest("hex")
119+
120+
expect(vscode.Uri.joinPath).toHaveBeenCalledWith(
121+
mockContext.globalStorageUri,
122+
`roo-index-cache-${expectedHash}.json`,
123+
)
124+
})
78125
})
79126

80127
describe("initialize", () => {

src/services/code-index/cache-manager.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import debounce from "lodash.debounce"
55
import { safeWriteJson } from "../../utils/safeWriteJson"
66
import { TelemetryService } from "@roo-code/telemetry"
77
import { TelemetryEventName } from "@roo-code/types"
8+
import * as path from "path"
9+
import * as os from "os"
810

911
/**
1012
* Manages the cache for code indexing
@@ -23,15 +25,46 @@ export class CacheManager implements ICacheManager {
2325
private context: vscode.ExtensionContext,
2426
private workspacePath: string,
2527
) {
26-
this.cachePath = vscode.Uri.joinPath(
27-
context.globalStorageUri,
28-
`roo-index-cache-${createHash("sha256").update(workspacePath).digest("hex")}.json`,
29-
)
28+
// Generate a stable cache key that persists across SSH sessions
29+
const cacheKey = this.generateStableCacheKey(workspacePath)
30+
this.cachePath = vscode.Uri.joinPath(context.globalStorageUri, `roo-index-cache-${cacheKey}.json`)
3031
this._debouncedSaveCache = debounce(async () => {
3132
await this._performSave()
3233
}, 1500)
3334
}
3435

36+
/**
37+
* Generates a stable cache key for the workspace that persists across SSH sessions
38+
* @param workspacePath The workspace path
39+
* @returns A stable hash key
40+
*/
41+
private generateStableCacheKey(workspacePath: string): string {
42+
// Get the workspace folder name
43+
const workspaceName = path.basename(workspacePath)
44+
45+
// Try to get a relative path from home directory for additional stability
46+
const homedir = os.homedir()
47+
let relativePath = workspacePath
48+
49+
try {
50+
// If the workspace is under the home directory, use the relative path
51+
if (workspacePath.startsWith(homedir)) {
52+
relativePath = path.relative(homedir, workspacePath)
53+
}
54+
} catch (error) {
55+
// If we can't get relative path, just use the full path
56+
console.warn("Failed to get relative path from home directory:", error)
57+
}
58+
59+
// Create a composite key using workspace name and relative path
60+
// This should be more stable across SSH sessions where the absolute path might change
61+
// but the relative structure remains the same
62+
const compositeKey = `${workspaceName}::${relativePath}`
63+
64+
// Generate hash from the composite key
65+
return createHash("sha256").update(compositeKey).digest("hex")
66+
}
67+
3568
/**
3669
* Initializes the cache manager by loading the cache file
3770
*/

0 commit comments

Comments
 (0)