Skip to content

fix(web): repository-relative file links open in parent-folder projects - #24953

Closed
mleungdev wants to merge 1 commit into
pingdotgg:mainfrom
mleungdev:fix/nested-repo-file-links
Closed

mleungdev wants to merge 1 commit into
pingdotgg:mainfrom
mleungdev:fix/nested-repo-file-links

Conversation

@mleungdev

@mleungdev mleungdev commented Oct 25, 2026 •

Copy link
Copy Markdown

Problem

When a project is rooted at a folder that holds one or more repositories (for example ~/dev/workspace containing app/, infra/ and some worktrees), agents run their commands inside a repository and write file links relative to it: docs/STATUS.md for app/docs/STATUS.md. Clicking such a link in chat opens the file panel with "The file could not be accessed or read" and the attempted path ~/dev/workspace/docs/STATUS.md, because the path is resolved only against the project root.

To reproduce, create a project at a parent folder, ask the agent to change something inside a repository in a subfolder, and click a repository-relative path it mentions, such as docs/README.md.

ChatMarkdown already falls back to a workspace search when a link doesn't resolve, but only for bare filenames and for the repeated-prefix case (stripRepeatedWorkspacePrefix, a project rooted below the path's start). The inverse case, a project rooted above the repository, never searches.

Change

  • pickNestedWorkspaceMatch (client-runtime, next to the existing lookup helpers): among search results, take the file whose path ends in /<link path>, matching whole segments only. When several match, take the shallowest, which keeps the main checkout ahead of copies in deeper worktree folders. Ties keep search rank, as pickWorkspaceBasenameMatch does. Case folds only for Windows roots, as in pickStrippedWorkspaceMatch.
  • ChatMarkdown.findWorkspaceFileMatch: for a multi-segment relative path that is neither a basename nor a repeated prefix, first check that the literal path is missing, then search and pick the nested match. The literal-path check is shared with the stripped case (same metadata call as before). A path that exists as written opens unchanged and costs no search.
  • openFileInPanel routes every workspace-relative link through that lookup. The open-in-editor, copy-path and reveal actions already use findWorkspaceFileMatch, so they pick up the fix too.

Mobile's ThreadFeed only implements the basename lookup, not the repeated-prefix one, so this follows that precedent and changes web and desktop only.

Scope and approval

This is a small, focused fix for an obvious bug, filed without a prior issue: a link the agent wrote correctly, to a file inside the project, fails to open. The fix extends the existing "link didn't resolve, search the workspace" fallback to the one shape it misses. It adds no setting, UI or default change, and a path that exists as written still opens unchanged.

Verification

  • Focused tests in apps/web/src/workspaceBasenameLookup.test.ts, with 6 new cases: a nested match; shallowest over deeper worktree copies; search rank between equal depths; whole-segment matching only and never directories; Windows-only case folding; no match. vp test run src/workspaceBasenameLookup.test.ts passed 12/12.

  • I ran the helper on real search results. I indexed a real parent-folder workspace with the server's FileFinder (@ff-labs/fff-node, same options as WorkspaceSearchIndex). The workspace holds one repository plus about 10 linked worktrees of it, at the same depth and deeper. I passed the fileSearch results to pickNestedWorkspaceMatch. All 5 repository-relative paths I tried resolved to the main repository's file, not a worktree copy: docs/STATUS.md, docs/runbooks/deploy.md, control-plane/src/lib/centaur.ts, services/sandbox/SYSTEM_PROMPT.md, and a file that existed only in the main checkout.

  • tsc --noEmit is clean for packages/client-runtime and reports no errors for apps/web. vp lint and vp fmt --check are clean on the changed files.

  • Manual check in a dev build (vp run dev with an isolated --home-dir, macOS, web client): a project at /private/tmp/t3demo/workspace containing README.md and a Git repository app-repo/ with docs/STATUS.md. A chat message with the link [docs/STATUS.md](docs/STATUS.md), clicked once with main's ChatMarkdown.tsx and workspaceBasenameLookup.ts and once with this branch's.

    Before (main): the panel shows "The file could not be accessed or read" for /private/tmp/t3demo/workspace/docs/STATUS.md.

    Before: file panel error for workspace/docs/STATUS.md

    After (this branch): the same link opens workspace › app-repo › docs › STATUS.md.

    After: file panel opens app-repo/docs/STATUS.md
  • Not checked: the desktop (Electron) build, Windows, and remote/relay connections. Desktop uses the same ChatMarkdown component.

Model and harness: Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

When a project is rooted above a repository (e.g. a workspace folder holding
several repos), agents write links relative to the repository they work in,
such as docs/STATUS.md for repo/docs/STATUS.md. The chat link fell back to a
workspace search only for bare filenames and repeated-prefix paths, so these
opened a missing file. Search for them too when the literal path is missing,
and take the shallowest file ending in that path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 25, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 89e1bae

Macroscope's review found this PR approvable — This is a localized fix to the existing workspace file-link fallback, adding repository-relative matching for parent-folder projects while preserving literal-path behavior. The accompanying pure helper and focused tests keep the change bounded and low risk.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between c5332bf and 89e1bae.

Files selected for processing: 3
  • apps/web/src/components/ChatMarkdown.tsx
  • apps/web/src/workspaceBasenameLookup.test.ts
  • packages/client-runtime/src/workspaceBasenameLookup.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Walkthrough

The runtime adds nested workspace path matching. The web resolver uses nested or stripped-path matches only when host metadata confirms that the literal path is missing.

Changes: Nested workspace file lookup

Layer / File(s) Summary
Nested path matching and link resolution
packages/client-runtime/src/workspaceBasenameLookup.ts, apps/web/src/components/ChatMarkdown.tsx, apps/web/src/workspaceBasenameLookup.test.ts
Adds pickNestedWorkspaceMatch, which selects the shallowest matching file path and preserves entry order for ties. The web resolver checks literal-path metadata before using nested or stripped-path matches. Tests cover path matching, ordering, directories, and Windows case handling.

Priority: Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: maria-rcks

Merge Risk: Minimal · up to 89e1b

No actionable merge-blocking issue is identified for the new workspace file-link lookup. Normal checks remain appropriate before merging.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main fix: repository-relative file links now open in parent-folder projects.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description follows the template well. It has Problem, Change, Scope and approval, and Verification sections, and each is substantively filled in. The problem is clearly stated with reproduction s…
Full details: Description check

Explanation

The description follows the template well. It has Problem, Change, Scope and approval, and Verification sections, and each is substantively filled in. The problem is clearly stated with reproduction steps. The change lists the new helper, the ChatMarkdown lookup order, and the openFileInPanel routing, and it notes that mobile is intentionally excluded. The scope section explains why no prior issue is needed for a small obvious bug fix and that no setting or UI change is added. Verification gives concrete test counts, a real-results check, type and lint results, and a manual dev-build check with before and after screenshots. It also lists what was not checked (Electron, Windows, relay). The model and harness are named at the end, as the template asks. Minor gap: the template says to link a triaged issue or approval comment, and the description does not link one. The author argues an exemption applies, which the template permits, so this is a small gap rather than a failure.

Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Member

Thanks @mleungdev for the fix and the clear before/after evidence. #18358, by @cbangera2, addresses the same nested slashed file-link lookup and has broader integration and runtime coverage, so we are keeping that PR for this fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix PR opened by an issue lead; enters the PR loop size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants