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
This commit is contained in:
Roo Code 2025-07-22 16:36:43 +00:00
parent 984d368f7a
commit a9b70e4e76
2 changed files with 86 additions and 6 deletions

View file

@ -3,6 +3,8 @@ import * as vscode from "vscode"
import { createHash } from "crypto"
import debounce from "lodash.debounce"
import { CacheManager } from "../cache-manager"
import * as path from "path"
import * as os from "os"
// Mock safeWriteJson utility
vitest.mock("../../../utils/safeWriteJson", () => ({
@ -38,6 +40,11 @@ vitest.mock("@roo-code/telemetry", () => ({
},
}))
// Mock os module
vitest.mock("os", () => ({
homedir: vitest.fn(() => "/home/user"),
}))
describe("CacheManager", () => {
let mockContext: vscode.ExtensionContext
let mockWorkspacePath: string
@ -63,8 +70,18 @@ describe("CacheManager", () => {
})
describe("constructor", () => {
it("should correctly set up cachePath using Uri.joinPath and crypto.createHash", () => {
const expectedHash = createHash("sha256").update(mockWorkspacePath).digest("hex")
it("should correctly set up cachePath using Uri.joinPath with stable cache key", () => {
// The cache key should be based on workspace name and relative path
const workspaceName = path.basename(mockWorkspacePath)
const homedir = os.homedir()
let relativePath = mockWorkspacePath
if (mockWorkspacePath.startsWith(homedir)) {
relativePath = path.relative(homedir, mockWorkspacePath)
}
const compositeKey = `${workspaceName}::${relativePath}`
const expectedHash = createHash("sha256").update(compositeKey).digest("hex")
expect(vscode.Uri.joinPath).toHaveBeenCalledWith(
mockContext.globalStorageUri,
@ -75,6 +92,36 @@ describe("CacheManager", () => {
it("should set up debounced save function", () => {
expect(debounce).toHaveBeenCalledWith(expect.any(Function), 1500)
})
it("should generate stable cache key for workspace under home directory", () => {
// os.homedir is already mocked to return '/home/user'
const workspaceUnderHome = "/home/user/projects/myproject"
const cacheManagerHome = new CacheManager(mockContext, workspaceUnderHome)
// Expected key should use relative path from home
const expectedKey = `myproject::projects/myproject`
const expectedHash = createHash("sha256").update(expectedKey).digest("hex")
expect(vscode.Uri.joinPath).toHaveBeenCalledWith(
mockContext.globalStorageUri,
`roo-index-cache-${expectedHash}.json`,
)
})
it("should generate stable cache key for workspace outside home directory", () => {
// os.homedir is already mocked to return '/home/user'
const workspaceOutsideHome = "/opt/projects/myproject"
const cacheManagerOutside = new CacheManager(mockContext, workspaceOutsideHome)
// Expected key should use full path since it's outside home
const expectedKey = `myproject::/opt/projects/myproject`
const expectedHash = createHash("sha256").update(expectedKey).digest("hex")
expect(vscode.Uri.joinPath).toHaveBeenCalledWith(
mockContext.globalStorageUri,
`roo-index-cache-${expectedHash}.json`,
)
})
})
describe("initialize", () => {

View file

@ -5,6 +5,8 @@ import debounce from "lodash.debounce"
import { safeWriteJson } from "../../utils/safeWriteJson"
import { TelemetryService } from "@roo-code/telemetry"
import { TelemetryEventName } from "@roo-code/types"
import * as path from "path"
import * as os from "os"
/**
* Manages the cache for code indexing
@ -23,15 +25,46 @@ export class CacheManager implements ICacheManager {
private context: vscode.ExtensionContext,
private workspacePath: string,
) {
this.cachePath = vscode.Uri.joinPath(
context.globalStorageUri,
`roo-index-cache-${createHash("sha256").update(workspacePath).digest("hex")}.json`,
)
// Generate a stable cache key that persists across SSH sessions
const cacheKey = this.generateStableCacheKey(workspacePath)
this.cachePath = vscode.Uri.joinPath(context.globalStorageUri, `roo-index-cache-${cacheKey}.json`)
this._debouncedSaveCache = debounce(async () => {
await this._performSave()
}, 1500)
}
/**
* Generates a stable cache key for the workspace that persists across SSH sessions
* @param workspacePath The workspace path
* @returns A stable hash key
*/
private generateStableCacheKey(workspacePath: string): string {
// Get the workspace folder name
const workspaceName = path.basename(workspacePath)
// Try to get a relative path from home directory for additional stability
const homedir = os.homedir()
let relativePath = workspacePath
try {
// If the workspace is under the home directory, use the relative path
if (workspacePath.startsWith(homedir)) {
relativePath = path.relative(homedir, workspacePath)
}
} catch (error) {
// If we can't get relative path, just use the full path
console.warn("Failed to get relative path from home directory:", error)
}
// Create a composite key using workspace name and relative path
// This should be more stable across SSH sessions where the absolute path might change
// but the relative structure remains the same
const compositeKey = `${workspaceName}::${relativePath}`
// Generate hash from the composite key
return createHash("sha256").update(compositeKey).digest("hex")
}
/**
* Initializes the cache manager by loading the cache file
*/