How the Remote-SSH Module Works in Reasonix for Remote Development

Reasonix's Remote-SSH module provides a fully-supervised SSH transport that mirrors VS Code's Remote-SSH extension, handling host resolution, authentication, keep-alive, reconnection, and port forwarding through a callback-driven API consumed by CLI, TUI, and desktop interfaces.

The DeepSeek-Reasonix repository includes a production-grade Remote-SSH module that enables remote development by managing SSH connections through a unified Go backend. Located in internal/remote/, this module supports host alias resolution from ~/.ssh/config, automatic reconnection with exponential backoff, and interactive authentication flows that surface to any frontend via callbacks.

Core Architecture Components

The Remote-SSH module separates concerns across distinct packages to handle transport logic, configuration parsing, and UI state synchronization.

The Remote Package (internal/remote/remote.go)

The remote package serves as the top-level supervisor for SSH transport. It defines the Client struct, status machine, and public API that manages the entire connection lifecycle. This file also houses the BackoffPolicy and KeepalivePolicy configurations that govern reconnection pacing and liveness probing.

Client Struct and Public API (internal/remote/client.go)

The Client struct in client.go acts as the central object for remote interactions. It holds dial options, the active ssh.Client connection, an SFTP handle, a forward registry, and a status hub. Key methods include New() for construction, Start() for non-blocking dial and supervision, Subscribe() for status events, SFTP() for filesystem access, and Forwards() for port tunneling management.

Frontend State Management (desktop/frontend/src/store/remote.ts)

The desktop UI consumes the Remote-SSH module through a Zustand store defined in remote.ts. This store mirrors the kernel's Remote-SSH surface, tracking hosts, connection statuses, port forwards, and pending fingerprints. The waitForRemoteConnection() helper allows React components to block until specific hosts reach a connected state.

Host Resolution and Configuration

Reasonix implements VS Code-compatible SSH configuration resolution, supporting both manual TOML entries and existing ~/.ssh/config files.

SSH Config Parsing (internal/remote/sshconfig.go)

The SSHConfigSource type parses ~/.ssh/config using an embedded fallback parser or delegates to the system's ssh -G command for accurate resolution. This ensures Reasonix interprets Host, HostName, Port, User, IdentityFile, and ProxyJump directives exactly as OpenSSH would.

Resolved Host Targets (internal/remote/host.go)

The ResolvedHost struct in host.go represents a fully-resolved target containing HostName, Port, User, identity files, and ProxyJump chains. The ResolveHost() function merges fields from reasonix.toml entries with the effective SSH configuration, while ResolveJumpHosts() builds hop chains for bastion host scenarios.

Connection Lifecycle

The Remote-SSH module follows an eight-stage supervised lifecycle that handles complex networking scenarios and user interactions.

Initialization and Dialing

The process begins when remote.New(opts) constructs a Client with populated Options, BackoffPolicy, and KeepalivePolicy. Calling Client.Start() initiates a non-blocking dial sequence that traverses ProxyJump chains if configured. The client spawns background goroutines for keep-alive probes using the configured interval (default 30 seconds) and miss tolerance (default 3).

Authentication and Host Key Verification

Authentication supports multiple methods including password, public key, and keyboard-interactive flows. When encountering unknown host keys, the client emits a StatusEvent with state="pending_hostkey" and a RemoteFingerprintView containing the SHA256 hash. The UI prompts the user via the HostKeyPrompt callback, implementing TOFU (trust-on-first-use) semantics. Similarly, secret prompts trigger state="pending_secret" events handled by the SecretPrompt callback.

Automatic Reconnection and Keep-Alive

Network resilience is handled by the classifyDialError() function, which determines whether a failure warrants aborting (e.g., ErrAuthFailed) or retrying. Transient failures trigger the BackoffPolicy (default 1 second initial delay, max 60 seconds), while the KeepalivePolicy monitors connection health. The client automatically reconnects using exponential backoff until explicitly stopped or unrecoverable authentication errors occur.

File System and Port Forwarding

Once connected, the module exposes remote resources through standardized interfaces.

SFTP Integration (internal/remote/sftpfs/)

The sftpfs sub-package wraps the sftp library to provide a read-only filesystem view. Accessed via Client.SFTP(), this powers the "Files" tab in the desktop UI, allowing recursive directory browsing and file retrieval without maintaining separate SSH sessions.

Port Forward Registry (internal/remote/forward/)

The forward sub-package manages port-forward lifecycles through a forward.Set that persists for the duration of the Client. The Client.Forwards() method returns this registry, which the UI syncs to its Zustand store for the "Ports" tab. Forwards survive reconnections and respect the same backoff policies as the main connection.

Implementing Remote Development

Developers can interact with the Remote-SSH module programmatically from both Go backends and TypeScript frontends.

Starting a remote client from Go:

import (
    "reasonix/internal/remote"
    "reasonix/internal/config"
)

func startRemote(hostName string) error {
    cfg, _ := config.Load()                     // load reasonix.toml
    sshCfg, _ := remote.LoadUserSSHConfig()    // read ~/.ssh/config
    target, err := remote.ResolveHost(cfg, hostName, sshCfg)
    if err != nil { return err }

    opts := remote.Options{
        Host:        target,
        Auth:        remote.AuthOptions{SecretPrompt: promptSecret},
        HostKeys:    &remote.HostKeyPolicy{},
        Keepalive:   remote.KeepalivePolicy{}, // defaults (30s interval, 3 misses)
        Backoff:     remote.BackoffPolicy{},   // defaults (1s initial, max 60s)
    }

    client, err := remote.New(opts)
    if err != nil { return err }
    go client.Start() // non‑blocking; client will reconnect automatically
    return nil
}

Waiting for connections in the frontend:

import { waitForRemoteConnection } from "./store/remote";

async function openExplorer(hostId: string) {
  try {
    await waitForRemoteConnection(hostId);
    // now the UI can safely show the file explorer or ports list
  } catch (e) {
    console.error("Failed to connect:", e);
  }
}

Handling host-key verification in React:

import { useRemoteStore } from "./store/remote";

export function FingerprintPrompt() {
  const { pendingFingerprint, clearPendingFingerprint } = useRemoteStore(
    (s) => ({
      pendingFingerprint: s.pendingFingerprint,
      clearPendingFingerprint: s.clearPendingFingerprint,
    })
  );

  if (!pendingFingerprint) return null;

  return (
    <div className="fingerprint-prompt">
      <p>Verify host key for {pendingFingerprint.hostId}:</p>
      <pre>{pendingFingerprint.sha256}</pre>
      <button onClick={() => clearPendingFingerprint(pendingFingerprint)}>
        Accept
      </button>
    </div>
  );
}

Summary

  • The Remote-SSH module in internal/remote/ provides a VS Code-compatible SSH transport with automatic reconnection and keep-alive management.
  • Host resolution merges TOML configuration with ~/.ssh/config parsing via SSHConfigSource and produces ResolvedHost targets including ProxyJump chains.
  • The Client struct manages the connection lifecycle through New(), Start(), and callback-driven authentication flows for host keys and secrets.
  • Resilience policies including BackoffPolicy and KeepalivePolicy ensure connections survive network interruptions without manual intervention.
  • SFTP and port forwarding registries provide filesystem access and tunneling capabilities that persist across reconnections.
  • The Zustand store in desktop/frontend/src/store/remote.ts exposes connection state to React components, enabling interactive prompts and status monitoring.

Frequently Asked Questions

How does Reasonix handle SSH configuration compared to standard OpenSSH clients?

Reasonix uses the SSHConfigSource type in internal/remote/sshconfig.go to parse ~/.ssh/config, falling back to the system's ssh -G command for accurate value resolution. This ensures that HostName, Port, User, and ProxyJump directives behave identically to VS Code's Remote-SSH extension, supporting complex bastion host configurations without duplicating settings.

What happens when a network connection drops during a remote session?

The Client automatically enters a reconnection loop governed by BackoffPolicy (exponential backoff with 1-second initial delay, max 60 seconds) and KeepalivePolicy (30-second probes, 3-miss tolerance). The classifyDialError() function distinguishes between authentication failures (which abort the connection) and transient network issues (which trigger retry). Existing port forwards and SFTP sessions reconnect transparently once the network recovers.

How does the Remote-SSH module support multiple frontend interfaces?

All interactivity flows through callbacks defined in remote.Options. When the engine encounters an unknown host key or requires a password, it emits StatusEvent objects (e.g., state="pending_hostkey") rather than writing to stdin/stdout. The CLI, TUI, and Wails desktop UI each implement these callbacks differently—prompting via terminal or React components—while sharing the same Client backend logic.

Where does the module store connection state for the desktop UI?

The desktop frontend maintains a synchronized state through the Zustand store defined in desktop/frontend/src/store/remote.ts. This store tracks RemoteHostEntry configurations, connection statuses, pending fingerprints, and active port forwards. Components like FingerprintPrompt subscribe to this store to render interactive prompts, while the waitForRemoteConnection() utility provides Promise-based waiting for specific connection states.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →