Description
HI
When using the OpenCode TUI on Windows connected to a remote opencode serve instance on Linux, file references using @file.py or @folder/file.py are incorrectly resolved on the Windows client instead of the server.
This happens even though the TUI is able to correctly list and browse files FROM the server using @.
So:
@shows files from server → ✔ works- Referencing the SAME file in a message → ❌ fails (path is expanded locally on Windows)
Error shown on the server
ENOENT: no such file or directory, statx '/C:/Users/myuser/myproject/module/file.py'
path: "/C:/Users/myuser/myproject/module/file.py",
syscall: "statx",
errno: -2,
code: "ENOENT"
Note: The referenced file does exist on the server at /root/myproject/module/file.py.
Expected behavior
- When connected to a remote server,
@path/to/file.py should be resolved server-side, using the server’s working directory and filesystem. - TUI should NOT expand paths locally on Windows (e.g. adding
C:\Users\...).
Actual behavior
The TUI:
- Lists files from the server correctly using
@ (proves remote file access works). - But when referencing a file using
@file.py in the prompt, it expands the path locally on Windows, converts it to an absolute Windows path (e.g., C:/Users/...), and sends this invalid path to the Linux server.
Steps to reproduce
- On a Linux server:
opencode serve --hostname 0.0.0.0 --port 4700
- On a Windows machine:
opencode attach http://<vps-ip>:4700
- In TUI:
- Type
@ → browse files on server project directory → ✔ works - Select a file like
module/file.py - Submit the prompt:
@module/file.py
- Server logs:
ENOENT: no such file or directory, statx '/C:/Users/.../module/file.py'
Environment
- Server: Ubuntu 22.04, OpenCode CLI 1.0.106
- Client: Windows 11, OpenCode 1.0.97 & 1.0.106 (pre-release) via Chocolatey.
Proposed Solution / Workaround
(I developed this workaround with assistance from AI to debug the path resolution logic)
The issue lies in packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx. The submit function uses the local environment to resolve paths even when connected remotely.
I found a working fix that:
- Dynamic Root Detection: Extracts the remote server's working directory from
sync.data.session. - Path Normalization: Strips the local Windows CWD from the file path to force it to be relative.
- URL Construction: Rebuilds the
file:// URL using the remote root so the Linux server can locate it.
Important Note on the Workaround:
For this dynamic fix to work effectively, the sync.data.session object must be populated with the correct remote directory. I found that currently, this requires a specific workaround: an AGENTS.md file must exist in the project directory and it must include the filePath parameter pointing to itself, for example:
<parametername="filePath">/root/your_project/AGENTS.md</parameter>
Without this specific parameter present in AGENTS.md, the remote path auto-detection often fails to retrieve the correct server-side root.
Code Changes
In packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx:
1. Add Import
2. Replace the submit function with:
asyncfunctionsubmit(){if(props.disabled)returnif(autocomplete.visible)returnif(!store.prompt.input)returnconstsessionID=props.sessionID
? props.sessionID
: await(async()=>{constsessionID=awaitsdk.client.session.create({}).then((x)=>x.data!.id)returnsessionID})()constmessageID=Identifier.ascending("message")letinputText=store.prompt.input// -------------------------------------------------------------------------// FIX: Dynamic Remote Root Detection// Extract the server's working directory from the current or latest session.// -------------------------------------------------------------------------letREMOTE_ROOT=""if(sync.data.session&&sync.data.session.length>0){// Try to find the directory for the specific session we are in, // otherwise use the most recent session's directory as the project root.constactiveSession=sync.data.session.find((s)=>s.id===sessionID)||sync.data.session[0]if(activeSession?.directory){REMOTE_ROOT=activeSession.directory}}// -------------------------------------------------------------------------// Expand pasted text inline before submittingconstallExtmarks=input.extmarks.getAllForTypeId(promptPartTypeId)constsortedExtmarks=allExtmarks.sort((a: {start: number},b: {start: number})=>b.start-a.start)for(constextmarkofsortedExtmarks){constpartIndex=store.extmarkToPartIndex.get(extmark.id)if(partIndex!==undefined){constpart=store.prompt.parts[partIndex]if(part?.type==="text"&&part.text){constbefore=inputText.slice(0,extmark.start)constafter=inputText.slice(extmark.end)inputText=before+part.text+after}}}// FIX: Normalize file paths (Windows Client -> Linux Server)constcwd=process.cwd().replace(/\\/g,"/")constcwdLower=cwd.toLowerCase()constnonTextParts=store.prompt.parts.filter((part)=>part.type!=="text").map((part)=>{if(part.type==="file"&&part.source?.path){letp=part.source.path.replace(/\\/g,"/")constpLower=p.toLowerCase()// 1. Strip local Windows CWD to get a clean relative pathif(pLower.startsWith(cwdLower)||(pLower.startsWith("/")&&pLower.includes(cwdLower))){constidx=pLower.indexOf(cwdLower)letrel=p.slice(idx+cwdLower.length)rel=rel.replace(/^\/+/,"")// Remove leading slashes// If we found a dirty absolute path, replace it in the message textif(rel!==p){constescapeRegExp=(string: string)=>string.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')constoriginalPathRegex=newRegExp(escapeRegExp(part.source.path),'gi')constnormalizedPathRegex=newRegExp(escapeRegExp(p),'gi')inputText=inputText.replace(originalPathRegex,rel)inputText=inputText.replace(normalizedPathRegex,rel)p=rel}}elseif(path.isAbsolute(part.source.path)){// Fallback using path.relative logicconstrel=path.relative(process.cwd(),part.source.path).split(path.sep).join("/")constescapeRegExp=(string: string)=>string.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')inputText=inputText.replace(newRegExp(escapeRegExp(part.source.path),'gi'),rel)p=rel}// 2. Construct valid Linux Absolute URL using the Dynamic REMOTE_ROOTconstcleanRemoteRoot=REMOTE_ROOT.replace(/\/$/,"")constcleanRelativePath=p.replace(/^\//,"")// Combine root + relative pathletabsoluteRemotePath=cleanRelativePathif(cleanRemoteRoot){absoluteRemotePath=`${cleanRemoteRoot}/${cleanRelativePath}`}// Use 3 slashes for Linux file protocol if remote path implies rootconstnewUrl=`file://${absoluteRemotePath.startsWith('/') ? '' : '/'}${absoluteRemotePath}`return{
...part,url: newUrl,source: {
...part.source,path: p,// Keep path relative for display},}}returnpart})if(store.mode==="shell"){sdk.client.session.shell({path: {id: sessionID,},body: {agent: local.agent.current().name,model: {providerID: local.model.current().providerID,modelID: local.model.current().modelID,},command: inputText,},})setStore("mode","normal")}elseif(inputText.startsWith("/")&&iife(()=>{constcommand=inputText.split(" ")[0].slice(1)returnsync.data.command.some((x)=>x.name===command)})){let[command, ...args]=inputText.split(" ")sdk.client.session.command({path: {id: sessionID,},body: {command: command.slice(1),arguments: args.join(" "),agent: local.agent.current().name,model: `${local.model.current().providerID}/${local.model.current().modelID}`,
messageID,},})}else{sdk.client.session.prompt({path: {id: sessionID,},body: {
...local.model.current(),
messageID,agent: local.agent.current().name,model: local.model.current(),parts: [{id: Identifier.ascending("part"),type: "text",text: inputText,},
...nonTextParts.map((x)=>({id: Identifier.ascending("part"),
...x,})),],},})}history.append(store.prompt)input.extmarks.clear()setStore("prompt",{input: "",parts: [],})setStore("extmarkToPartIndex",newMap())props.onSubmit?.()// temporary hack to make sure the message is sentif(!props.sessionID)setTimeout(()=>{route.navigate({type: "session",
sessionID,})},50)input.clear()}OpenCode version
1.0.106
Steps to reproduce
- sending @file command
Screenshot and/or share link
No response
Operating System
windows as TUI and Linux as server
Terminal
No response
Description
HI
When using the OpenCode TUI on Windows connected to a remote
opencode serveinstance on Linux, file references using@file.pyor@folder/file.pyare incorrectly resolved on the Windows client instead of the server.This happens even though the TUI is able to correctly list and browse files FROM the server using
@.So:
@shows files from server → ✔ worksError shown on the server
Note: The referenced file does exist on the server at
/root/myproject/module/file.py.Expected behavior
@path/to/file.pyshould be resolved server-side, using the server’s working directory and filesystem.C:\Users\...).Actual behavior
The TUI:
@(proves remote file access works).@file.pyin the prompt, it expands the path locally on Windows, converts it to an absolute Windows path (e.g.,C:/Users/...), and sends this invalid path to the Linux server.Steps to reproduce
@→ browse files on server project directory → ✔ worksmodule/file.py@module/file.pyEnvironment
Proposed Solution / Workaround
(I developed this workaround with assistance from AI to debug the path resolution logic)
The issue lies in
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx. Thesubmitfunction uses the local environment to resolve paths even when connected remotely.I found a working fix that:
sync.data.session.file://URL using the remote root so the Linux server can locate it.Important Note on the Workaround:
For this dynamic fix to work effectively, the
sync.data.sessionobject must be populated with the correct remote directory. I found that currently, this requires a specific workaround: anAGENTS.mdfile must exist in the project directory and it must include thefilePathparameter pointing to itself, for example:Without this specific parameter present in
AGENTS.md, the remote path auto-detection often fails to retrieve the correct server-side root.Code Changes
In
packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx:1. Add Import
2. Replace the
submitfunction with:OpenCode version
1.0.106
Steps to reproduce
Screenshot and/or share link
No response
Operating System
windows as TUI and Linux as server
Terminal
No response