Skip to content

fix(ssh): auto-push host-local attachments to SSH backend on read_file miss#33455

Closed
Carry00 wants to merge 3 commits into
NousResearch:mainfrom
Carry00:fix/ssh-backend-webui-attachment-push
Closed

fix(ssh): auto-push host-local attachments to SSH backend on read_file miss#33455
Carry00 wants to merge 3 commits into
NousResearch:mainfrom
Carry00:fix/ssh-backend-webui-attachment-push

Conversation

@Carry00

@Carry00 Carry00 commented May 27, 2026

Copy link
Copy Markdown
Contributor

Problem

When a user uploads a file via the WebUI in a session that uses an SSH
terminal backend (e.g. a NAS or remote server profile), the file is saved
to the host's WebUI attachment inbox
(~/.hermes/webui/attachments/<sid>/). The agent then receives the path
as [Attached files: /path/to/file], calls read_file, and the tool
routes the stat check through SSH — where that path does not exist.
Result: "File not found" on every attachment, regardless of file type.

This affects every user running an SSH-profile session who uploads any
non-image file. The original design comment in the WebUI notes
"Non-image files intentionally stay as text path attachments so the agent
can inspect them with file tools"
— a correct design, but one that assumes
the agent's terminal is local.

Fix

Two small, additive changes:

tools/environments/ssh.py — exposes a public push_file() method
that wraps the existing _scp_upload(), reusing the already-open
ControlMaster socket (no extra handshake, no new dependency).

tools/file_operations.py — adds _push_local_file_if_needed() and
calls it in both read_file() and read_file_raw() before returning the
"not found" error:

  1. Check if the path resolves to an existing file on the local host.
  2. If the active environment exposes push_file, transfer the file and
    retry the stat.
  3. If the push fails or the environment does not support it (local, docker,
    modal, …), fall through to the original suggest-similar-files response.

The fallback is entirely transparent on non-SSH backends — push_file is
absent on those environments, so getattr(..., None) short-circuits with
no overhead on the happy path.

Tests

tests/tools/test_ssh_attachment_push_fallback.py (8 new tests):

  • _push_local_file_if_needed unit cases: absent local file, missing
    push_file attr, successful push, push raises.
  • read_file integration: push called and read succeeds; push raises →
    fallback to suggest-similar; env without push_file → no crash.
  • SSHEnvironment.push_file delegates to _scp_upload.

All existing test_file_operations.py, test_file_operations_edge_cases.py,
and test_ssh_environment.py continue to pass (126 tests, 0 failures).

Impact

Backend Behaviour change
SSH Host-local files (WebUI uploads) are automatically transferred on first read; subsequent reads hit the remote copy normally
Local / Docker / Modal / … No change — push_file absent, fallback returns False immediately

@alt-glitch alt-glitch added type/bug Something isn't working backend/ssh SSH remote execution tool/file File tools (read, write, patch, search) comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists labels May 27, 2026
@Carry00 Carry00 force-pushed the fix/ssh-backend-webui-attachment-push branch 3 times, most recently from fb57d6e to 30317ef Compare May 29, 2026 14:24
Carry00 added 3 commits May 30, 2026 15:48
…x_retries

Closes NousResearch#30331.

When the configured credential pool has nothing to rotate to (single-key
deployments — the common pattern of one provider key in ~/.hermes/.env)
and an HTTP 401/403 comes back, the existing retry loop would treat the
error as transient and hit `jittered_backoff(retry_count, base_delay=5.0,
max_delay=120.0)` × `max_retries` times. Each retry hit the same dead
key and got the same 401 — up to ~8 minutes of pure latency before the
user saw an actionable error.

The fix adds a one-shot `single_key_auth_retry_attempted` flag to the
per-turn state block. After all provider-specific OAuth refresh paths
have had their chance (codex / nous / copilot / anthropic), an auth
error with no pool rotation available now:

1. On first occurrence — retries once with a fresh connection (handles
   genuine transient hiccups), logging a single visible line so the
   user can see what's happening.
2. On the second occurrence — upgrades the ClassifiedError reason from
   `auth` to `auth_permanent`. The downstream `is_client_error` branch
   sees `retryable=False` and takes the existing non-retryable abort
   path, which already has actionable "your API key was rejected" hints
   for Codex/xAI OAuth, OpenRouter, and generic providers.

status_code / provider / model / error_context are preserved through
the upgrade so the abort path's diagnostic output stays accurate.
When a WebUI file upload targets a session using an SSH terminal backend,
the file lands in the host attachment inbox but is absent on the remote
machine.  read_file/read_file_raw would return "File not found" with no
recovery path.

Fix:
- Add SSHEnvironment.push_file() as a public wrapper around _scp_upload(),
  reusing the existing ControlMaster socket (no extra handshake).
- Add ShellFileOperations._push_local_file_if_needed(): when a remote stat
  fails, checks whether the same path exists on the local host and, if the
  active environment exposes push_file(), transfers the file and retries.
- Update both read_file() and read_file_raw() to call the fallback before
  returning the suggest-similar-files error.

The change is transparent on local/docker backends (no push_file attribute)
and adds no overhead on the happy path (fallback only triggers after a
failed stat).
…s-profile SSH leakage

_resolve_container_task_id always returned "default", so _active_environments
shared a single SSHEnvironment across all WebUI sessions. When a user switched
from profile A (ssh_host=10.0.0.1) to profile B (ssh_host=10.0.0.2), the new
session found _active_environments["default"] already set to A's SSHEnvironment
and reused it — silently running every command on the wrong remote host.

Fix: when HERMES_SESSION_KEY is present (set per-session by the WebUI streaming
layer and per-message by the gateway via contextvars), return "session:<key>"
as the cache key instead of "default". Each session now owns its own slot in
_active_environments and always creates an environment from its own profile's
TERMINAL_SSH_HOST / TERMINAL_ENV config.

Behaviour unchanged in CLI mode (no HERMES_SESSION_KEY → still "default").
RL/benchmark task overrides (register_task_env_overrides) are unaffected.
Subagent task_ids inside a WebUI session collapse to "session:<key>" so they
continue to share the parent session's container.

Five new regression tests added to test_shared_container_task_id.py.
@Carry00 Carry00 force-pushed the fix/ssh-backend-webui-attachment-push branch from 30317ef to b3a0eef Compare May 30, 2026 07:49
teknium1 added a commit that referenced this pull request Jun 9, 2026
@file: attachments now work when the desktop is connected to a remote
gateway. Previously a referenced file resolved to a client-disk path the
gateway couldn't see, so context_references rejected it with "path is
outside the allowed workspace" and the agent never saw the file.

Adds a file.attach RPC (sibling to the existing image.attach_bytes /
pdf.attach byte-upload pipeline): the desktop uploads the file bytes, the
gateway stages them into <workspace>/.hermes/desktop-attachments/ and
returns a workspace-relative @file: ref that resolves cleanly. Local mode
passes the path directly; a gateway-visible file outside the workspace is
copied in; an in-workspace file is referenced as-is with no copy.

Consolidates the file-sync design from #38615 (LeonSGP43) and the
host-file-staging idea from #33455 (Carry00), rebased onto the
image/PDF remote-media helpers already on main.

Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
@teknium1

teknium1 commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Superseded by #42634 (commit dbbd1d4 on main), which solves the remote-desktop file-attach gap from the gateway/desktop side. Your host-file-staging idea — detect a file the backend can see but that lives outside the session workspace, and stage it in so the agent can read it — is captured there as the "gateway-visible file outside the workspace → copy into .hermes/desktop-attachments/" case.

Your PR specifically targets the SSH-backend WebUI-upload path (push_file() on read_file miss), which #42634 does not cover — that's a distinct surface. If you'd like to pursue the SSH push-on-read-miss fix on its own, rebased onto current main, we'd happily review it as a focused PR. Closing this one as overlapping with the merged work; thanks for the contribution.

@teknium1 teknium1 closed this Jun 9, 2026
a249169329-cpu pushed a commit to a249169329-cpu/hermes-agent that referenced this pull request Jun 9, 2026
@file: attachments now work when the desktop is connected to a remote
gateway. Previously a referenced file resolved to a client-disk path the
gateway couldn't see, so context_references rejected it with "path is
outside the allowed workspace" and the agent never saw the file.

Adds a file.attach RPC (sibling to the existing image.attach_bytes /
pdf.attach byte-upload pipeline): the desktop uploads the file bytes, the
gateway stages them into <workspace>/.hermes/desktop-attachments/ and
returns a workspace-relative @file: ref that resolves cleanly. Local mode
passes the path directly; a gateway-visible file outside the workspace is
copied in; an in-workspace file is referenced as-is with no copy.

Consolidates the file-sync design from NousResearch#38615 (LeonSGP43) and the
host-file-staging idea from NousResearch#33455 (Carry00), rebased onto the
image/PDF remote-media helpers already on main.

Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
wachoo pushed a commit to wachoo/hermes-agent that referenced this pull request Jun 10, 2026
@file: attachments now work when the desktop is connected to a remote
gateway. Previously a referenced file resolved to a client-disk path the
gateway couldn't see, so context_references rejected it with "path is
outside the allowed workspace" and the agent never saw the file.

Adds a file.attach RPC (sibling to the existing image.attach_bytes /
pdf.attach byte-upload pipeline): the desktop uploads the file bytes, the
gateway stages them into <workspace>/.hermes/desktop-attachments/ and
returns a workspace-relative @file: ref that resolves cleanly. Local mode
passes the path directly; a gateway-visible file outside the workspace is
copied in; an in-workspace file is referenced as-is with no copy.

Consolidates the file-sync design from NousResearch#38615 (LeonSGP43) and the
host-file-staging idea from NousResearch#33455 (Carry00), rebased onto the
image/PDF remote-media helpers already on main.

Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
changman pushed a commit to changman/hermes-agent that referenced this pull request Jun 10, 2026
@file: attachments now work when the desktop is connected to a remote
gateway. Previously a referenced file resolved to a client-disk path the
gateway couldn't see, so context_references rejected it with "path is
outside the allowed workspace" and the agent never saw the file.

Adds a file.attach RPC (sibling to the existing image.attach_bytes /
pdf.attach byte-upload pipeline): the desktop uploads the file bytes, the
gateway stages them into <workspace>/.hermes/desktop-attachments/ and
returns a workspace-relative @file: ref that resolves cleanly. Local mode
passes the path directly; a gateway-visible file outside the workspace is
copied in; an in-workspace file is referenced as-is with no copy.

Consolidates the file-sync design from NousResearch#38615 (LeonSGP43) and the
host-file-staging idea from NousResearch#33455 (Carry00), rebased onto the
image/PDF remote-media helpers already on main.

Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
alt-glitch pushed a commit that referenced this pull request Jun 14, 2026
@file: attachments now work when the desktop is connected to a remote
gateway. Previously a referenced file resolved to a client-disk path the
gateway couldn't see, so context_references rejected it with "path is
outside the allowed workspace" and the agent never saw the file.

Adds a file.attach RPC (sibling to the existing image.attach_bytes /
pdf.attach byte-upload pipeline): the desktop uploads the file bytes, the
gateway stages them into <workspace>/.hermes/desktop-attachments/ and
returns a workspace-relative @file: ref that resolves cleanly. Local mode
passes the path directly; a gateway-visible file outside the workspace is
copied in; an in-workspace file is referenced as-is with no copy.

Consolidates the file-sync design from #38615 (LeonSGP43) and the
host-file-staging idea from #33455 (Carry00), rebased onto the
image/PDF remote-media helpers already on main.

Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend/ssh SSH remote execution comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists tool/file File tools (read, write, patch, search) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants