Skip to content

[Bug]: Repo-root-relative file paths in agent output break when the project is a subfolder of the git repo #14492

Description

@josephv123

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/web

Steps to reproduce

  1. Have a git repo whose root is a parent of the project, e.g. repo at ~/school with the T3 project registered at ~/school/physics (git rev-parse --show-toplevel from the project prints ~/school).
  2. In a thread on that project, ask the agent to create notes/outline.md and commit it.
  3. The agent refers to the file the way git printed it, relative to the repo root: `physics/notes/outline.md`.
  4. Click the file chip.

Expected behavior

The chip opens ~/school/physics/notes/outline.md.

Actual behavior

The path is joined onto the project root, so the chip resolves to ~/school/physics/physics/notes/outline.md (the tooltip shows this). The files panel opens a tab with breadcrumb physics > physics > notes > outline.md and shows:

Failed to read workspace file 'physics/notes/outline.md' in '~/school/physics'.

Opening the same file from the Files tab works. The agent's cwd was the project root, so the path was correct from git's point of view, not a hallucination.

Impact

Minor bug or occasional failure

Version or commit

main @ bd89c13

Environment

macOS, desktop/web client, Claude provider (any agent that echoes git paths is affected).

Notes

resolveMarkdownFileLinkTarget / resolveInlineCodeFileLinkMeta (apps/web/src/markdown-links.ts) join every relative path onto the thread cwd via resolvePathLinkTarget. The click-time basename rescue only covers slash-free names.

The diff panel already solves this exact mismatch: resolveDiffPathForWorkspace in apps/web/src/diffFileActions.ts uses project.repositoryIdentity.rootPath to strip the project's repo-relative prefix. Chat file links could do the same: when the project root is a strict subdirectory of the repo root (and the thread is not on a worktree), a relative path that starts with the project's repo-relative prefix (physics/) gets that prefix stripped. The only misfire would be a project with a real child folder named like its own repo-relative path, which seems acceptable, or it could be resolved with a click-time existence check.

Mobile's resolveFileChipTarget (apps/mobile/src/features/threads/fileChipMenu.ts) has the same join and should get the same treatment.

Related, not a duplicate: #10553 (agent cds into a different project's checkout; no repo information can resolve that one).

Workaround

Open the file from the Files tab, or ask the agent for absolute paths.

Activity

  1. juliusmarminge commented on Sep 30, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Confirmed on main (bd89c13020). This is a real bug, and it isn't a duplicate of #8251 or #10553 (both open issues).

    Chat file chips join every relative path onto the thread cwd. resolveMarkdownFileLinkTarget does that through resolvePathLinkTarget. The click-time lookup (needsWorkspaceBasenameLookup) only runs for names without a slash, so physics/notes/outline.md is never searched. The tooltip and the files panel both use the joined path, which is why the breadcrumb reads physics > physics > notes > outline.md and the read fails with ProjectReadFileError. "Copy relative path" and "Copy full path" use the same joined path too. Opening the file from the Files tab works because it skips the join. On mobile, resolveFileChipTarget does the same join and has no basename fallback at all.

    The diff panel already handles this kind of path. resolveDiffPathForWorkspace strips the project's repo-relative prefix using repositoryIdentity.rootPath (from git rev-parse --show-toplevel). DiffPanel and the changed-files card only pass that root when the thread isn't on a worktree.

    Two things shouldn't be copied from that helper as-is:

    • It returns null when the path doesn't start with the prefix. That's right for diffs, where paths are always repo-relative, but chat chips can be either kind. notes/outline.md from the project cwd must still open. Only strip a leading prefix when the project root is a strict subdirectory of the repo root and the thread has no worktree.
    • A real child folder with the same name (<project>/physics/...) would collide with the stripped path. Checking which candidate actually exists avoids opening the wrong file. Also, repositoryIdentity is null when the repo has no remote, so rootPath can be missing even though git rev-parse --show-toplevel would still find the parent.

    #8251 and open PR #8254 cover the opposite mismatch: the project is the parent folder, and the path is relative to a folder inside it. Matching the path's end against the workspace index can find that file, but it can't turn physics/notes/outline.md into notes/outline.md. #10553 is about the agent running cd into a different checkout, where the repo root doesn't help identify the file.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions