From 61d8b1020f60dd67305cdbe725f2aedd5266d225 Mon Sep 17 00:00:00 2001 From: M Waleed Kadous Date: Mon, 23 Feb 2026 15:25:34 -0800 Subject: [PATCH] [Bugfix #535] Fix: Use CWD-based workspace detection for cross-workspace file opens Root cause: open.ts derived workspace from the file's location (findWorkspaceRoot(dirname(filePath))). When running af open from workspace A targeting a file in workspace B, the file opened in B's viewer instead of A's. Fix: Prefer CWD-based workspace detection so files open in the user's active workspace. Fall back to file-based detection when CWD isn't in a recognizable workspace (e.g., running from /tmp). --- .../bugfix-500-af-open-any-dir.test.ts | 12 +++-- ...bugfix-535-af-open-cross-workspace.test.ts | 50 +++++++++++++++++++ .../codev/src/agent-farm/commands/open.ts | 16 ++++-- 3 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 packages/codev/src/agent-farm/__tests__/bugfix-535-af-open-cross-workspace.test.ts diff --git a/packages/codev/src/agent-farm/__tests__/bugfix-500-af-open-any-dir.test.ts b/packages/codev/src/agent-farm/__tests__/bugfix-500-af-open-any-dir.test.ts index 9da9d34c..10a43597 100644 --- a/packages/codev/src/agent-farm/__tests__/bugfix-500-af-open-any-dir.test.ts +++ b/packages/codev/src/agent-farm/__tests__/bugfix-500-af-open-any-dir.test.ts @@ -6,8 +6,9 @@ * (or in a different subdirectory), the wrong workspace path is sent to * the Tower API, causing the open to fail. * - * Fix: Derive workspace root from the FILE's directory (via findWorkspaceRoot) - * instead of CWD. The file itself determines which workspace it belongs to. + * Fix (updated by bugfix #535): Prefer CWD-based workspace detection (for + * cross-workspace opens), but fall back to file-based detection when CWD + * isn't in a recognizable workspace. */ import { describe, it, expect } from 'vitest'; import { resolve } from 'node:path'; @@ -19,10 +20,11 @@ describe('Bugfix #500: af open works from any directory', () => { 'utf-8', ); - it('should use findWorkspaceRoot from file directory, not getConfig', () => { - // The fix: workspace root is derived from the file's location + it('should fall back to findWorkspaceRoot from file directory when CWD is not in a workspace', () => { + // Bugfix #535 changed to CWD-first with file-based fallback. + // The file-based fallback must still exist for when CWD is outside any workspace. expect(openSrc).toContain('findWorkspaceRoot(dirname(filePath))'); - // Should NOT use getConfig() — that uses CWD which is the wrong approach + // Should NOT use getConfig() — that doesn't support file-based fallback expect(openSrc).not.toContain('getConfig()'); }); diff --git a/packages/codev/src/agent-farm/__tests__/bugfix-535-af-open-cross-workspace.test.ts b/packages/codev/src/agent-farm/__tests__/bugfix-535-af-open-cross-workspace.test.ts new file mode 100644 index 00000000..7b228465 --- /dev/null +++ b/packages/codev/src/agent-farm/__tests__/bugfix-535-af-open-cross-workspace.test.ts @@ -0,0 +1,50 @@ +/** + * Regression test for bugfix #535: af open does not reliably work on files + * outside the current directory + * + * Root cause: `open.ts` derived the workspace from the FILE's location + * (via findWorkspaceRoot(dirname(filePath))). When running `af open` from + * workspace A but targeting a file in workspace B, the file opened in B's + * annotation viewer instead of A's. + * + * Fix: Prefer CWD-based workspace detection (findWorkspaceRoot(process.cwd())) + * so the file opens in the user's current workspace. Fall back to file-based + * detection only when CWD isn't in a recognizable workspace. + */ +import { describe, it, expect } from 'vitest'; +import { resolve } from 'node:path'; +import { readFileSync } from 'node:fs'; + +describe('Bugfix #535: af open cross-workspace file resolution', () => { + const openSrc = readFileSync( + resolve(import.meta.dirname, '../commands/open.ts'), + 'utf-8', + ); + + it('should prefer CWD-based workspace detection over file-based', () => { + // The fix: determine workspace from CWD first + expect(openSrc).toContain('findWorkspaceRoot(process.cwd())'); + }); + + it('should check whether CWD resolves to a real workspace', () => { + // Must verify CWD is in a workspace (has codev/ or .git) before using it + expect(openSrc).toContain('cwdIsWorkspace'); + expect(openSrc).toMatch(/existsSync.*codev/); + expect(openSrc).toMatch(/existsSync.*\.git/); + }); + + it('should fall back to file-based detection when CWD is not a workspace', () => { + // When CWD is outside any workspace (e.g., /tmp), fall back to file location + expect(openSrc).toContain('findWorkspaceRoot(dirname(filePath))'); + }); + + it('should still support worktree fallback to main repo', () => { + // Bugfix #427's worktree fallback must be preserved + expect(openSrc).toContain('getMainRepoFromWorktree(workspacePath)'); + }); + + it('should not use getConfig for workspace detection', () => { + // getConfig() uses CWD internally but doesn't support the fallback pattern + expect(openSrc).not.toContain('getConfig()'); + }); +}); diff --git a/packages/codev/src/agent-farm/commands/open.ts b/packages/codev/src/agent-farm/commands/open.ts index 73f31023..c9b99571 100644 --- a/packages/codev/src/agent-farm/commands/open.ts +++ b/packages/codev/src/agent-farm/commands/open.ts @@ -67,10 +67,18 @@ export async function open(options: OpenOptions): Promise { fatal(`File not found: ${filePath}`); } - // Find workspace root from the FILE's location, not CWD. - // This allows `af open` to work from any directory — the file itself - // determines which workspace it belongs to. - let workspacePath = findWorkspaceRoot(dirname(filePath)); + // Determine workspace from CWD first (the user's current workspace context). + // This ensures cross-workspace file opens show in the user's active workspace, + // not in the workspace where the file physically resides. + // Fall back to file-based detection when CWD isn't in a recognizable workspace + // (e.g., running from /tmp). + const cwdWorkspace = findWorkspaceRoot(process.cwd()); + const cwdIsWorkspace = existsSync(resolve(cwdWorkspace, 'codev')) || + existsSync(resolve(cwdWorkspace, '.git')); + + let workspacePath = cwdIsWorkspace + ? cwdWorkspace + : findWorkspaceRoot(dirname(filePath)); // When running from a worktree, Tower only knows the main repo workspace. // Fall back to main repo path so the API call targets a registered workspace.