[Bug]: SSH device probe hides missing Node in non-interactive PATH (macOS/mise), showing generic communication error#32908

opened3d agobyCclaytonfaria

Summary

When configuring an SSH device host, Test connection can report only:

text
1omarchy: Device probe host failed: Could not communicate with device support. Try refreshing devices.

The actual failure was already available in the environment server's logs:

text
1SshCommandError: Node is missing from the non-interactive SSH PATH

SSH authentication and connectivity were working. Node and npm were installed on the Mac through mise and worked in an interactive terminal, but were unavailable in the non-interactive PATH used by T3's device bootstrap.

Exposing Node and npm in a directory that T3 explicitly adds to the remote PATH resolved the prerequisite failure. The UI should preserve this actionable cause instead of suggesting a generic device refresh. Better support for version-manager installations would also help, but is a separate enhancement from fixing error reporting.

Environment and topology

  • T3 Code desktop: 0.1.7 on the Mac; Omarchy server trace paths also identify the 0.1.7 application bundle.
  • Client used to configure settings: MacBook Pro, macOS, T3 desktop.
  • Selected environment: Omarchy, Linux.
  • SSH device host: the MacBook Pro, with Xcode and iOS Simulator installed.
  • Connection direction for device support: Omarchy environment server → MacBook Pro over SSH.
  • Node installation on the Mac: mise, with executable directory at $HOME/.local/share/mise/installs/node/latest/bin.
  • Verified after the workaround: Node v26.13.0, npm 11.20.0.

This is specifically about using a Mac's simulator from a Linux environment while configuring that environment from the Mac client. The client location initially made the error look like a settings-routing problem. The logs instead showed a remote bootstrap dependency failure.

Steps to reproduce

  1. Install Node 22+ and npm on a Mac through mise, exposing them through interactive shell initialization, without exposing them through the PATH used by non-interactive SSH commands.
  2. Ensure Xcode and simulator tooling are available and passwordless SSH from a Linux environment to the Mac works.
  3. Connect the T3 desktop client on the Mac to the Linux environment.
  4. In Settings → Devices, select the Linux environment and add a Machine over SSH source targeting the Mac.
  5. Use Test connection.
  6. Compare the UI result with the Linux environment server's trace logs.

Expected behavior

The result should identify the failed prerequisite and the machine on which it failed, for example:

text
1Omarchy → MacBook Pro: SSH connected, but Node was not found in the non-interactive SSH PATH on the device host.
2Node 22+ and npm must be available to non-interactive SSH commands. Make them available on the Mac and retry Test connection.

A details section could include the original error, exit code, and bounded stderr, with appropriate credential redaction.

Actual behavior and log evidence

The UI reports a generic device-support communication error and recommends refreshing devices. Refreshing cannot repair a missing executable in the remote process environment.

The Omarchy server trace contains the following nested failure chain (paths and generated IDs omitted):

text
1ws.rpc.device.testHost
2 DeviceOperationError: Device probe host failed:
3 Could not communicate with device support. Try refreshing devices.
4 [cause]: DeviceHostError: Device host <id> failed while probe.
5 [cause]: SshCommandError:
6 Node is missing from the non-interactive SSH PATH

The failure is therefore diagnosed internally but loses its useful explanation before reaching the user.

Confirmed workaround

On the Mac, T3's SSH bootstrap explicitly prepends these directories:

sh
1export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"

My mise installation was outside those directories. I made the installed runtime accessible through $HOME/.local/bin:

sh
1mkdir -p "$HOME/.local/bin"
2ln -s "$HOME/.local/share/mise/installs/node/latest/bin/node" "$HOME/.local/bin/node"

For npm, a simple symlink to mise's npm wrapper did not work in this installation. That wrapper resolves files relative to its invocation directory, producing:

text
1Cannot find module '<home>/.local/lib/node_modules/npm/bin/npm-cli.js'

Instead, I installed a small executable wrapper at $HOME/.local/bin/npm:

sh
1#!/bin/sh
2export PATH="$HOME/.local/share/mise/installs/node/latest/bin:$PATH"
3exec "$HOME/.local/share/mise/installs/node/latest/bin/npm" "$@"

The paths above describe this particular mise installation; they are not universal paths for every installation. Existing files should not be overwritten blindly.

I then verified from Omarchy, over SSH to the Mac, using the PATH setup T3 uses:

sh
1ssh macbook-pro 'sh -c '\''set -e
2export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
3node --version
4npm --version
5xcrun --find simctl
6'\'''

Result:

text
1v26.13.0
211.20.0
3/Applications/Xcode.app/Contents/Developer/usr/bin/simctl

Validation boundary: this confirms the formerly missing bootstrap prerequisites are accessible from the correct environment and connection direction. A complete device-host connection/streaming retry has not yet been confirmed after this workaround.

Suggested T3 Code fix

1. Preserve actionable probe errors in the UI

  • Keep the underlying SshCommandError explanation when translating the failure into DeviceHostError / DeviceOperationError.
  • Distinguish a missing Node executable, unsupported Node version, missing npm, SSH authentication failure, and device-hub readiness failure.
  • Show which environment is connecting and which device host is being checked.
  • Offer bounded, redacted stderr in Show error rather than replacing it with the generic communication message.
  • Keep the generic fallback for failures that genuinely have no useful diagnostic.

2. Improve version-manager setup guidance or discovery

  • Explain that interactive terminal success does not prove availability to non-interactive SSH commands.
  • Document mise/version-manager setups and how to expose the runtime to the bootstrap PATH.
  • Consider an explicit Node executable/runtime-directory override or deliberate version-manager discovery as an enhancement. Any selected runtime should also make its corresponding npm available during both probe and tool installation.
  • Avoid relying on arbitrary interactive shell initialization just to locate Node; a predictable runtime configuration would be easier to diagnose.

3. Regression coverage

  • A connected SSH host with no Node on the bootstrap PATH should show the original missing-Node explanation in the user-visible test result.
  • Node <22 and missing npm should retain their distinct explanations.
  • A mise-style installation accessible only in an interactive shell should fail actionably; exposing its runtime through the supported PATH should pass the prerequisite checks.
  • Tests should cover a remote environment selected from a client running on the device-host machine, preserving the environment → device-host direction.
  • Include npm wrappers that depend on their invocation directory when documenting or testing runtime discovery.

The current repository source has useful entry points for investigation (reviewed on main; this is not a claim that all current-main behavior was exercised):

  • SshDeviceHost.ts: bootstrap checks command -v node, executes the remote script, and wraps errors in DeviceHostError.
  • sshDeviceScript.ts: remoteDeviceEnvironment defines the PATH additions; probe mode checks Node 22+ and npm availability.
  • DeviceHost.ts: the wrapper's message identifies host and step while the underlying cause remains nested.
  • useHostConnectionChecks.ts: dispatches the test against the selected environment.
  • Device host documentation: already documents the non-interactive Node/npm requirement.

Related: #26898 reports the same generic probe message hiding a different underlying SSH failure. That issue is closed; this report adds a confirmed macOS/mise cause, a working prerequisite workaround, and concrete suggestions for error propagation and regression coverage.