From bdde58c0ac017c2e7daf3b073ab05d42f14e9a47 Mon Sep 17 00:00:00 2001 From: Jermiah Joseph Date: Sat, 25 Apr 2026 11:26:18 -0400 Subject: [PATCH] fix(editor): reject lock files with no workspace match for cwd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without a minimum score threshold, opencode would connect to any VSCode lock file in ~/.claude/ide/ even when the workspace folders had no relation to the current working directory. This caused cross-directory IDE context injection — e.g. a WezTerm session in an unrelated dir receiving file-open events from a VSCode window. Filter lock files to only those whose workspaceFolders contain cwd before sorting. Replace the binary pathContains check with pathContainsLength so the sort can prefer the most specific (deepest) workspace match, falling back to mtime for ties. Removes scoreEditorLock which is no longer needed. --- .../src/cli/cmd/tui/context/editor.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/opencode/src/cli/cmd/tui/context/editor.ts b/packages/opencode/src/cli/cmd/tui/context/editor.ts index 75c5440f5d94..72b0785d6165 100644 --- a/packages/opencode/src/cli/cmd/tui/context/editor.ts +++ b/packages/opencode/src/cli/cmd/tui/context/editor.ts @@ -278,12 +278,16 @@ function resolveEditorLockFile() { } const cwd = process.cwd() + // longest workspace folder that contains cwd; 0 if none match + const bestMatchLength = (lock: EditorLockFile) => + Math.max(0, ...lock.workspaceFolders.map((folder) => pathContainsLength(folder, cwd))) const locks = entries .filter((entry) => entry.endsWith(".lock")) .map((entry) => readEditorLockFile(path.join(directory, entry))) .filter((entry): entry is EditorLockFile => Boolean(entry)) - .sort((left, right) => scoreEditorLock(right, cwd) - scoreEditorLock(left, cwd)) - + .filter((entry) => bestMatchLength(entry) > 0) + // prefer locks with longer matching workspace folders, then more recent ones + .sort((left, right) => bestMatchLength(right) - bestMatchLength(left) || right.mtimeMs - left.mtimeMs) return locks[0] } @@ -310,11 +314,6 @@ function readEditorLockFile(filePath: string): EditorLockFile | undefined { } } -function scoreEditorLock(lock: EditorLockFile, cwd: string) { - const workspaceMatch = lock.workspaceFolders.some((folder) => pathContains(folder, cwd)) ? 1 : 0 - return workspaceMatch * 1_000_000_000_000 + lock.mtimeMs -} - function editorSelectionKey(selection: EditorSelection | undefined) { if (!selection) return "" return [ @@ -327,9 +326,10 @@ function editorSelectionKey(selection: EditorSelection | undefined) { ].join("\0") } -function pathContains(parent: string, child: string) { - const relative = path.relative(path.resolve(parent), path.resolve(child)) - return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)) +function pathContainsLength(parent: string, child: string) { + const resolved = path.resolve(parent) + const relative = path.relative(resolved, path.resolve(child)) + return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)) ? resolved.length : 0 } function openEditorSocket(connection: EditorConnection) {