Skip to content

fix: read a remote session's @fix_plan.md on its host, refuse remote Ralph writes - #728

Merged
Ark0N merged 1 commit into
Ark0N:masterfrom
timkjr:fix/fix-plan-remote
Oct 28, 2026
Merged

Ark0N merged 1 commit into
Ark0N:masterfrom
timkjr:fix/fix-plan-remote

Conversation

@timkjr

@timkjr timkjr commented Oct 27, 2026

Copy link
Copy Markdown

What

Follow-up to #722, same root cause. The Ralph file routes in ralph-routes.ts use local fs on a remote-SSH session's workingDir, which is a path on the remote host:

  • POST .../fix-plan/read imports a same-named local @fix_plan.md, or answers 404 when there is none;
  • POST .../fix-plan/write and POST .../ralph-prompt/write write @fix_plan.md and @ralph_prompt.md into a local folder with the remote path, or fail when there is none.

Fix

  • fix-plan/read reads the file on the remote host through src/remote-files.ts, the same ssh path the File Viewer uses. The steps are:
    • Probe: one probe covers both @fix_plan.md and the workspace root.
    • Confinement: the File Viewer's check applies, so a file that resolves outside the workspace is treated as missing.
    • Read: the file is read from its real path, capped at 1 MB.
    • Errors: a missing remote file returns the same NOT_FOUND as a missing local one. A non-file, an oversized file or an unreachable host is a failed read (OPERATION_FAILED, with the RemoteFileAccessError message). There is never a local fallback.
  • fix-plan/write and ralph-prompt/write refuse a remote session with INVALID_INPUT. Remote writes are out of scope by design: remote-files.ts is read-only, and the File Viewer refuses a remote edit for the same reason. The Ralph panel already shows the error text. ralph-prompt/write has no caller in the UI, but it has the same issue, so it gets the same refusal.

Depends on #722

#722 stops the @fix_plan.md watcher from watching the local copy of a remote session's folder. Without it, the watcher can still auto-import the local file after this route has read the remote one correctly.

Tests

test/routes/ralph-routes-remote.test.ts mocks the ssh layer and keeps the pure builders and RemoteFileAccessError real. The workspace path also exists locally, holding a different @fix_plan.md, so any fallback to local fs would import or overwrite the wrong file. It covers:

  • Remote read: the read imports the remote content, a missing remote file is NOT_FOUND, and a file resolving outside the workspace is treated as missing. A directory, a special file, an oversized file and an unreachable host are each a failed read with nothing imported, and the error text omits the internal error-class name.
  • Remote writes: both writes are refused and leave the local folder untouched.
  • Local sessions: reads and writes behave as before, with no ssh calls.

On master the six remote cases fail and the two local ones pass.

🤖 Generated with Claude Code

…Ralph writes

A remote-SSH session's workingDir is a path on the remote host, but the
Ralph file routes used local fs on it: fix-plan/read imported a same-named
LOCAL @fix_plan.md (or 404'd), and fix-plan/write and ralph-prompt/write
wrote their files into the local twin of the remote folder. Same root
cause as the Ralph @fix_plan.md watcher fix.

- fix-plan/read reads the file on the remote host through
  src/remote-files.ts (the File Viewer's ssh path): one probe of the file
  and the workspace, the File Viewer's confinement (a file resolving
  outside the workspace is treated as missing), then a capped read (1 MB).
  A missing remote file is the same NOT_FOUND as a missing local one; a
  non-file, an oversized file or an unreachable host is a failed read,
  never a local fallback.
- fix-plan/write and ralph-prompt/write refuse a remote session
  (INVALID_INPUT): remote writes are out of scope by design, as for the
  File Viewer's remote edit. The Ralph panel already shows the error.
@Ark0N

Ark0N commented Oct 27, 2026

Copy link
Copy Markdown
Owner

Thank you, @timkjr, for following up #722 with this. It makes the Ralph file routes treat a remote (SSH) session's working directory as a path on the remote host: fix-plan/read reads @fix_plan.md there through src/remote-files.ts, and fix-plan/write and ralph-prompt/write refuse a remote session before anything touches the local disk.

The change is correct and lines up with the existing remote-file rules: the host comes from the session, the probe resolves both the file and the workspace remotely and fails closed, the cap applies to the remote size, and there is no local fallback. The local twin in the test is a good way to prove that, and I confirmed the remote cases fail against master and pass here. Typecheck, lint, format and the route tests are all green.

A few small things I will take care of at merge time, so there is nothing you need to push:

  1. Docs (docs/remote-sessions.md:339, docs/architecture-invariants.md:90): the "File access over SSH" table and the remote-cases paragraph list what each route does for a remote case. I will add rows for fix-plan/read (remote read, NOT_FOUND / OPERATION_FAILED, no local fallback) and the two write refusals.
  2. Shared containment check (src/web/routes/ralph-routes.ts:555): the inline check is a second copy of isPathWithinRoot in file-routes.ts. I will export that helper and call it from both routes, so the two confinement checks cannot drift.
  3. Comment (src/web/routes/ralph-routes.ts:546): I will rewrap the JSDoc line to the 120-column width and say NOT_FOUND where it says 404, since the route answers a NOT_FOUND envelope.

The watcher half of this bug is #722, which is still open; this PR stands on its own and does not need it to merge. This merges as it stands, with the three items above applied on top. Thanks again for tracking both halves down.

@Ark0N
Ark0N merged commit 4612224 into Ark0N:master Oct 28, 2026
2 checks passed
Ark0N pushed a commit that referenced this pull request Oct 28, 2026
Export isPathWithinRoot from file-routes.ts and use it for the remote
@fix_plan.md confinement, so the two checks cannot drift; rewrap the
readRemoteFixPlan JSDoc and say NOT_FOUND; list fix-plan/read and the two
write refusals in docs/remote-sessions.md and architecture-invariants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ark0N pushed a commit that referenced this pull request Oct 28, 2026
@Ark0N

Ark0N commented Oct 28, 2026

Copy link
Copy Markdown
Owner

Merged in 1.47.0 (https://github.com/Ark0N/Codeman/releases/tag/codeman%401.47.0). Thank you, this closes the remote half of the Ralph file routes. At merge I applied the three items from the review (79a7f49): isPathWithinRoot is exported from file-routes.ts and used for the remote @fix_plan.md confinement, the JSDoc is rewrapped and says NOT_FOUND, and docs/remote-sessions.md and docs/architecture-invariants.md list fix-plan/read and the two write refusals. Two related gaps came up in the release review, if you want another one: autoConfigureRalph still reads .claude/ralph-loop.local.md and CLAUDE.md locally for a remote session from POST /api/sessions/:id/interactive and respawn start (quick-start already skips it), and Ralph loop start treats a remote case as a local folder.

@timkjr
timkjr deleted the fix/fix-plan-remote branch October 28, 2026 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants