Repository navigation
[Bug]: File chips for files created outside the project root fail to open: "Failed to read workspace file 'docs/X.md' in '<root>'" #8251
Description
Activity
Another case with the same error but a different setup. It suggests the cause is wider than the agent's cwd. T3 Code 0.0.43-nightly.20260926.2282, Linux desktop, Claude Code provider.
- The thread's workspace is
~/Agent, and the agent's cwd is the same. The agent inspects a second repository withcd ~/Development/other-repo && …in Bash. - Its reply mentions
src/lib/content/company.ts, a path relative to that other repository. - T3 renders the inline code as a file chip resolved against the thread's workspace. Hovering shows
~/Agent/src/lib/content/company.ts, and clicking fails withFailed to read workspace file 'src/lib/content/company.ts' in '~/Agent'.
The same text breaks in any thread. When I quoted the path in another thread (workspace
~/Documents/notes), it became a chip for~/Documents/notes/src/lib/content/company.tsand failed the same way. The full path~/Development/other-repo/src/lib/content/company.tsopens correctly.~/Development/other-repo/...also became a chip, labelled....Resolving against the agent's cwd, as this issue expects, would not fix this case, because here the agent's cwd equals the workspace. Two changes would:
- Check that the target exists before rendering a chip, and render plain inline code otherwise.
resolveInlineCodeFileLinkMetainChatMarkdown.tsxdecides from the text shape alone (inlineCodeFilePathCandidate). The only lookup is the basename search, andneedsWorkspaceBasenameLookupskips it for any path that contains/. - Tell the agent the rule.
buildRuntimeInstructions(apps/server/src/provider/RuntimeInstructions.ts) mentions absolute paths only for images and videos. Nothing says that paths in inline code become links resolved against the workspace root, so the agent has no reason to write~/…or absolute paths for files outside it.
- The thread's workspace is
Note
Drafted by Claude Opus 5.5 and GPT-6 Astra in T3 Code; reviewed and posted by @jieyuexing.
I traced the subdirectory case on upstream main b707eeb and reproduced the path selection with the real resolver plus isolated files. This example uses ASCII and includes a directory separator, so neither Unicode classification nor bare-basename lookup is needed to reproduce it.
Thread workspace: /workspace/repo
Tool working directory: /workspace/repo/project-a
File created by the tool: /workspace/repo/project-a/docs/report.md
Assistant output: reportChatView passes the current thread/project worktree path as markdownCwd; MessagesTimeline passes it to ChatMarkdown. resolveMarkdownFileLinkTarget joins docs/report.md to that base, producing /workspace/repo/docs/report.md.
If that root-level file is absent, the fixture gets ENOENT. If a different file exists there, it reads that file instead. The absolute intended path and the full workspace-relative project-a/docs/report.md both select the intended file.
Sources:
t3code/apps/web/src/components/ChatView.tsx
Lines 4120 to 4125 in b707eeb
const gitCwd = activeProject ? projectScriptCwd({ project: { cwd: activeProject.workspaceRoot }, worktreePath: activeThread?.worktreePath ?? null, }) : null; t3code/apps/web/src/components/ChatView.tsx
Lines 11451 to 11455 in b707eeb
markdownCwd={ paintOnlyDisplayedTimeline ? (heldPaintContext?.markdownCwd ?? undefined) : (gitCwd ?? undefined) } t3code/packages/shared/src/markdownLinks.ts
Lines 342 to 354 in b707eeb
export function resolveMarkdownFileLinkTarget( href: string | undefined, cwd?: string, baseDir: string | undefined = cwd, ): string | null { if (!href) return null; const target = parseMarkdownFileLink(href); if (!target) return null; const pathWithPosition = formatFilePathPosition(target); if (!isRelativeFilePath(pathWithPosition)) return pathWithPosition; if (!baseDir) return null; return resolvePathLinkTarget(pathWithPosition, baseDir); t3code/packages/shared/src/fileLinks.ts
Lines 91 to 107 in b707eeb
export function resolvePathLinkTarget(rawPath: string, cwd: string): string { const position = splitFilePathPosition(rawPath); const { path } = position; let resolvedPath = path; if (path.startsWith("~/")) { const home = inferHomeFromCwd(cwd); if (home) { const separator: "/" | "\\" = isWindowsPathStyle(home) ? "\\" : "/"; resolvedPath = joinPath(home, path.slice(2), separator); } } else if (!isAbsolutePath(path)) { const separator: "/" | "\\" = isWindowsPathStyle(cwd) ? "\\" : "/"; resolvedPath = joinPath(cwd, path, separator); } return formatFilePathPosition({ ...position, path: resolvedPath });
This is resolver/file fixture evidence; I have not captured a new UI/network reproduction. The target environment is not lost in this example: the base directory is the mismatch. A tool's transient cwd is not the thread binding, and using the last command cwd as a universal link base would be ambiguous when a response references multiple directories.
I found the existing #8254 work and am not proposing a duplicate implementation. Cross-project paths belong with #10553; project-as-Git-subfolder behavior has a separate report in #14492.
Area: apps/web
Steps to reproduce:
~/Documents, agent working inside~/Documents/<subdir>).docs/GROUPS_PLAN.md.docs/GROUPS_PLAN.md:46).Expected behavior:
Clicking the chip opens the file the agent actually created (
~/Documents/<subdir>/docs/GROUPS_PLAN.md) in the file preview panel.Actual behavior:
An error toast and a broken preview panel:
Impact: Minor bug or occasional failure
Version or commit: 0.0.34-nightly.20260825.1180 (9996038)
Environment: macOS 26.6.2, T3 Code desktop app 0.0.34-nightly.20260825.1180, OpenCode provider
Logs or stack traces:
[projects.readFile] ProjectReadFileError { cwd: "/Users/<user>/Documents", relativePath: "docs/GROUPS_PLAN.md", failure: "operation_failed", operation: "realpath-target", operationPath: "/Users/<user>/Documents/docs/GROUPS_PLAN.md", }Workaround: Open the file via the Files explorer instead of the chip.