Files
Codeman/.changeset/remote-file-access.md
T
Randalix 013a5d9cc8 fix(files): read remote-case file previews and downloads over ssh
A remote case's workingDir is an absolute path on the remote host, but the
file read routes resolved it with local `fs`: `validateSessionFilePath`'s
realpathSync fails for a path that does not exist on the Codeman host, so
every preview of an agent-written file answered "File not found" (#415).

Add src/remote-files.ts as the single remote-read layer, built on the same
buildSshConnectionArgs() the launch uses:

- remoteProbePaths(): ONE round trip returning realpath + stat for the
  requested path AND the workspace root, so containment is checked against a
  remotely canonicalized root (a symlinked remotePath is ordinary).
- remoteCreateReadStream(): streams the body (cat, or tail -c +N | head -c L
  for a Range) with nothing buffered in memory, and reaps the ssh child when
  the response ends so an aborted download cannot orphan it.
- remoteReadFile(): bounded read for file-content.

file-raw, file-content, file-preview and file-thumbnail now share one local/
remote target resolution. Guards keep their local strength: lexical pre-check,
remote realpath, workspace containment, sensitive-path blocklist, and the size
cap applied to the remote size before any bytes are read. An unreachable host
answers 502 with the remote reason instead of a misleading 404. Nothing is ever
copied to the Codeman host and there is NO local fallback (an sshfs mount of
the same tree must not shadow the remote bytes).

Deliberately unchanged: writes (edit=1 / PUT now answer 400 explicitly while
the viewer hides its Edit affordance), office previews, thumbnails, file tree,
picker, external attachment registration and tail-file stay local-only.
2026-09-14 14:54:41 +02:00

1.6 KiB

aicodeman
aicodeman
patch

File previews, downloads and text reads now work in a remote (SSH) case.

A remote case's working directory is an absolute path on the remote host, but the file routes resolved it with local fs — so a clicked path (or the File Viewer) always failed as "File not found" even though the file existed and the session was clearly working in that directory. GET /api/sessions/:id/file-raw, file-content, file-preview and file-thumbnail now resolve and read through the same buildSshConnectionArgs() connection the launch uses (src/remote-files.ts, one realpath+stat probe per request returning both the file and the workspace root).

The guards are unchanged in strength: the workspace boundary is still enforced (now resolved on the host that can actually resolve it), the sensitive-path blocklist and the size cap (CODEMAN_MAX_DOWNLOAD_BYTES) still apply before any bytes are read, and Range requests keep working, so remote <video>/<audio> seeking behaves like a local file. An unreachable host is reported as 502 with the remote reason instead of a misleading 404. Nothing is ever copied to the Codeman host.

Still not available for remote cases, and now said explicitly instead of 404-ing: editing a file (edit=1 / PUT answer 400, the viewer hides its Edit affordance), office-document previews and generated thumbnails (both need the bytes on the server's disk), the file tree / path picker, attachment registration for paths outside the workspace, and tail-file. Docker cases are unaffected (their workspace is bind-mounted at the same absolute path).