Skip to content

fix(shell): stop the agent editing local files while the terminal is on a remote host - #242

Merged
tmac1973 merged 1 commit into
mainfrom
fix/shell-nested-session-scope
Sep 8, 2026
Merged

fix(shell): stop the agent editing local files while the terminal is on a remote host#242
tmac1973 merged 1 commit into
mainfrom
fix/shell-nested-session-scope

Conversation

@tmac1973

@tmac1973 tmac1973 commented Sep 8, 2026

Copy link
Copy Markdown
Owner

SSH into a server from the Shell tab, ask the assistant to edit a file, and it writes the file locally while its commands run remotely — then reports success on a file the user isn't looking at.

Why

The Shell tab has two tool families pointing at different machines:

  • run_command / shell_input / shell_read type into the PTY, so they act on whatever the terminal is currently in.
  • fs_read_text / fs_write_text / fs_edit_text / code_grep / code_glob go through Tauri to the local filesystem, always.

They agree only while the terminal sits at a local prompt. Two things then hide the divergence:

  1. OSC 7 comes from the local shell's prompt hook (integration.rs:251), which a remote shell never emits — so the tracked cwd freezes at the last local directory, and resolveShellPath resolves the model's bare nginx.conf against it.
  2. ssh registers as an in-flight command (133;C with no D), so runInPty refuses with "terminal is busy… use shell_input" — and the model dutifully switches to shell_input, which runs remotely, while its file tools stay local. Nothing ever told it those were different machines.

What this does

New src/lib/shell/nestedSession.ts classifies the in-flight command line into a remote/container session — ssh, mosh, docker/podman exec, kubectl exec, distrobox/toolbox enter, lxc, nsenter, chroot, vagrant ssh, virsh console, gcloud compute ssh — seeing through sudo/env-var prefixes and pipelines. Commands that hold the terminal without changing filesystem (a dev server, a REPL, su) are deliberately not flagged.

On top of that:

  • Writes are refused (fs_write_text, fs_edit_text) while the terminal is elsewhere, pointing at a cat > … <<'EOF' heredoc or sed -i through shell_input instead. Blocking rather than warning: a warning arrives after the stray local file already exists.
  • Reads and searches are labelled with the machine they actually came from (fs_read_text, fs_list_dir, code_grep, code_glob).
  • run_command's busy message now says the terminal is somewhere else, not merely busy, and explains how to run something over there.
  • Both shell prompts get a live block whenever a nested session is in flight, plus a static "TWO ENVIRONMENTS" section in Code mode. The read-only assistant gets its own wording — its suggestions are pasted into that same session, so they run remotely too, and it must tailor them to the remote host rather than the captured SESSION CONTEXT.

Testing

  • 17 new tests (nestedSession.test.ts for the classifier and message wording; agent/tools/nested-session.test.ts for the tool-level refusal, the read label, and the two non-flagged cases).
  • Full suite passes (1842 tests), npm run check clean, npm run lint 0 errors.
  • Confirmed by hand in the app against a real ssh session.

No Rust or IPC-type changes, so no export-ipc-types.sh run needed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AWxTLzKqQXJHaoiHG13wUM

…on a remote host

The Shell tab has two tool families pointing at different machines. The PTY
tools (run_command, shell_input, shell_read) act on whatever the terminal is
in; the file tools (fs_read_text, fs_write_text, fs_edit_text, code_grep,
code_glob) always act on the local filesystem. They agree only while the
terminal sits at a local prompt.

The moment the user runs `ssh`, they diverge — and two things hide it. OSC 7
comes from the local shell's prompt hook, so the tracked cwd freezes at the
last local directory and relative paths resolve against it. And `ssh` reads as
an in-flight command, so run_command refuses and the model falls back to
shell_input, which runs remotely. Asked to edit a config, the agent wrote a
local file, reported success, and left the user looking at an unchanged file
on the server.

Classify the in-flight command (ssh/mosh, docker/podman/kubectl exec,
distrobox, lxc, nsenter, chroot, vagrant, virsh, gcloud) so the split can be
named, then act on it:

  - refuse fs_write_text / fs_edit_text while the terminal is elsewhere,
    pointing at a heredoc or sed -i through shell_input instead — a warning
    would arrive after the stray local file already exists
  - label reads and searches with the machine they actually came from
  - tell run_command's busy message that the terminal is somewhere else, not
    merely busy
  - state the split in both shell prompts, live when a session is in flight;
    the read-only assistant gets its own wording, since its suggestions are
    pasted into that same session and run remotely too

Commands that hold the terminal without changing filesystem — a dev server, a
REPL, `su` — are deliberately not flagged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWxTLzKqQXJHaoiHG13wUM
@tmac1973
tmac1973 merged commit db6929f into main Sep 8, 2026
4 checks passed
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.

1 participant