Use this page when a node is visible in status but node tools fail.

Node goes offline after SSH logout (Linux)

On Linux, apus node install creates a user-level systemd service. The systemd --user instance is torn down when your last login session ends, so the node service stops the moment you log out — even though it looked healthy (enabled + running) while you were connected.

Check lingering:

loginctl show-user "$USER" -p Linger

If it reads Linger=no, enable it (may require sudo):

sudo loginctl enable-linger "$USER"

Then restart the node service and verify it survives logout:

apus node restart
# log out, then from another machine:
apus nodes status

apus node install prints a warning with this recovery command when it detects lingering is disabled. Don't mix a user-level service with a system-level one for the same node. The duplicate-scope guard that prevents two managers from running the same unit name is enforced for gateway units (two supervisors on the same port SIGTERM each other in a restart loop); for node services the installer does not raise this guard, so a leftover unit in the other scope can leave the node in an ambiguous state. Fully remove one before switching.

Command ladder

apus status
apus gateway status
apus logs --follow
apus doctor
apus channels status --probe

Then run node-specific checks:

apus nodes pending
apus nodes status
apus nodes describe --node <idOrNameOrIp>
apus approvals get --node <idOrNameOrIp>

Healthy signals:

If startup preparation disables container session hosting that you enabled, check the node host's local stderr for node host worker hosting disabled: ... and follow the reported engine or context recovery guidance. The macOS app forwards worker stderr to its logger under subsystem ai.apus, category node-host-worker; see macOS logging for capture options. After fixing the cause, restart the node host. Explicitly disabled hosting produces no such diagnostic.

After a node-host restart, session capacity remains occupied until worker cleanup is verified. On Linux and macOS, released direct workers are recovered through their original process group even if its leader has already exited. Newer workers retain a cleanup anchor that records descendant completion before exiting; recovery also waits for its process group to disappear. If the anchor dies without that record, the node logs lost its cleanup anchor without recorded lineage completion and keeps the slot reserved. Inspect remaining worker descendants and the node-host logs; another restart alone cannot establish that cleanup finished. Other free slots remain available. See the database compatibility contract before downgrading a node with active workers.

If a Windows node host exits before confirming worker cleanup, its unfinished claims and slots stay reserved across restarts. Losing or reusing a worker's PID does not prove that its descendants stopped. Cleanup confirmed by the original host still releases capacity normally; container-isolated workers on supported hosts use their container engine's removal confirmation.

Node runtime version differs from the CLI

A packaged headless node can run a newer private runtime than the globally installed CLI. Use apus nodes status --json to check the connected node's version; apus --version reports the CLI version. For delayed updates, opt-outs, fallback, and migration or repair deferrals, see Headless node updates.

If an apparently idle node keeps deferring an update, check its installed plugins. A plugin without an idle-work callback cannot confirm that its background work has finished, so the node keeps running. Update the plugin, or finish its work before updating and restarting the node manually.

Foreground requirements

camera.* and screen.* are foreground-only on iOS/Android nodes.

Quick check and fix:

apus nodes describe --node <idOrNameOrIp>
apus logs --follow

If you see NODE_BACKGROUND_UNAVAILABLE, bring the node app to the foreground and retry.

Permissions matrix

Capability iOS Android macOS node app Typical failure code
camera.snap, camera.clip Camera (+ mic for clip audio) Camera (+ mic for clip audio) Camera (+ mic for clip audio) *_PERMISSION_REQUIRED
screen.record Screen Recording (+ mic optional) Screen capture prompt (+ mic optional) Screen Recording *_PERMISSION_REQUIRED
computer.act n/a n/a Accessibility + Screen Recording COMPUTER_DISABLED, ACCESSIBILITY_REQUIRED
location.get While Using or Always (depends on mode) Foreground/Background location based on mode Location permission LOCATION_PERMISSION_REQUIRED
system.run n/a (node host path) n/a (node host path) Exec approvals required SYSTEM_RUN_DENIED

Pairing versus approvals

Four separate approval and policy gates control whether a node command succeeds:

  1. Device pairing: can this node connect to the gateway?
  2. Node command surface approval: has the declared command been approved through apus nodes pending and apus nodes approve <nodeRequestId>?
  3. Gateway node command policy: is the RPC command ID allowed by gateway.nodes.commands.allow / gateway.nodes.commands.deny and platform defaults?
  4. Exec approvals: can this node run a specific shell command locally?

Device pairing admits the identity; surface approval limits the commands on its paired-device record. The two request IDs are distinct. An initial unapproved surface exposes no effective commands. A pending expansion retains only commands that were already approved, remain declared, and pass Gateway policy. For system.run, shell allowlist and ask policy live in the node's exec approvals (apus approvals get --node ...), not the pairing record. Platform permissions and foreground requirements still apply.

Quick checks:

apus devices list
apus nodes pending
apus nodes status
apus approvals get --node <idOrNameOrIp>
apus approvals allowlist add --node <idOrNameOrIp> "/usr/bin/uname"

SSH-verified and bootstrap enrollment can approve the first surface automatically. Trusted-network device approval does not. Later command, capability, or permission expansion still needs surface approval.

For approval-backed host=node runs, the gateway also binds execution to the prepared canonical systemRunPlan. If a later caller mutates the command, cwd, or session metadata before the approved run is forwarded, the gateway rejects the run as an approval mismatch instead of trusting the edited payload.

Common node error codes

Code Meaning
NODE_BACKGROUND_UNAVAILABLE App is backgrounded; bring it to the foreground.
CAMERA_DISABLED Camera toggle disabled in node settings.
*_PERMISSION_REQUIRED OS permission missing/denied.
LOCATION_DISABLED Location mode is off.
LOCATION_PERMISSION_REQUIRED Requested location mode not granted.
LOCATION_BACKGROUND_UNAVAILABLE App is backgrounded but only While Using permission exists.
COMPUTER_DISABLED Enable Allow Computer Control in the macOS app, then approve the pairing update.
ACCESSIBILITY_REQUIRED Grant Accessibility to the current Apus app bundle in macOS System Settings.
SYSTEM_RUN_DENIED: approval required Exec request needs explicit approval.
SYSTEM_RUN_DENIED: allowlist miss Command blocked by allowlist mode. On Windows node hosts, shell-wrapper forms like cmd.exe /c ... are treated as allowlist misses in allowlist mode unless approved via the ask flow.

Fast recovery loop

apus nodes status
apus nodes describe --node <idOrNameOrIp>
apus approvals get --node <idOrNameOrIp>
apus logs --follow

If still stuck:

For computer control, also verify that the node-local Computer Control toggle is enabled, its pairing update is approved, a vision-capable agent exposes the computer tool, and screen.snapshot succeeds with Screen Recording permission. A gateway.nodes.commands.deny entry always overrides a platform default or gateway.nodes.commands.allow.

Related