NousResearch/hermes-agent · error · Error
SSH host is required.
Error message
SSH host is required.
What it means
Thrown after normalizeSshConfig returns null when merging SSH connection settings. normalizeSshConfig yields null when the required host field is empty — here the merge of input.sshHost over the existing block's host (undefined input inherits the saved value) still produced no host. Note the merge semantics: only a truly absent (undefined) input field inherits, so an empty-string input overrides a saved host with ''.
Source
Thrown at apps/desktop/electron/main.ts:7144
// Build an SSH connection block from a save payload, preserving an
// already-adopted dashboard token from the existing block (the token is minted
// + reconciled at bootstrap, never user-entered). `mode: 'ssh'` is stamped so
// normalizeSshConfig/profileSshOverride recognize it.
function buildSshBlock(input: any, existingBlock: any = {}) {
// `??` (not `||`) so an explicit '' (user CLEARED the field) wins over the
// saved value; only a truly absent (undefined) field inherits.
const merged = normalizeSshConfig({
mode: 'ssh',
host: input.sshHost ?? existingBlock.host,
user: input.sshUser ?? existingBlock.user,
port: input.sshPort ?? existingBlock.port,
keyPath: input.sshKeyPath ?? existingBlock.keyPath,
remoteHermesPath: input.sshRemoteHermesPath ?? existingBlock.remoteHermesPath,
remoteProfile: input.sshRemoteProfile ?? existingBlock.remoteProfile
})
if (!merged) {
throw new Error('SSH host is required.')
}
// Carry forward an already-adopted dashboard token unless the host changed
// (a different host invalidates the old dashboard's token).
if (existingBlock.token && existingBlock.host === merged.host) {
merged.token = existingBlock.token
}
return merged
}
// Build a remote backend connection descriptor from an already-resolved remote
// config. Handles both auth models (OAuth ws-ticket vs static session token)
// and is shared by the per-profile, env, and global resolution paths. `token`
// is the DECRYPTED static token (or null in OAuth mode). `source` is a label
// for diagnostics ('profile' | 'env' | 'settings').
async function buildRemoteConnection(
rawUrl,View on GitHub (pinned to c896c09c42)
Solutions
- Provide a non-empty sshHost.
- In calling code, omit fields the user didn't fill (send undefined, not '') so the existing saved values inherit.
- Strip empty strings before merging: input.sshHost || undefined.
Example fix
// before — '' overrides the saved host and normalizeSshConfig returns null
const merged = normalizeSshConfig({ mode: 'ssh', host: input.sshHost ?? existingBlock.host, ... })
// after — coerce empty strings to undefined so saved values inherit
const orUndef = (v) => (v === '' ? undefined : v)
const merged = normalizeSshConfig({
mode: 'ssh',
host: orUndef(input.sshHost) ?? existingBlock.host,
user: orUndef(input.sshUser) ?? existingBlock.user,
port: orUndef(input.sshPort) ?? existingBlock.port
}) Defensive patterns
Strategy: validation
Validate before calling
const orUndef = (v) => (v === '' || v == null ? undefined : v)
function sshMergeReady(input, existing) {
const host = orUndef(input.sshHost) ?? existing?.host
return typeof host === 'string' && host.trim() !== ''
} Type guard
function isNonEmptyHost(host) {
return typeof host === 'string' && host.trim() !== ''
} Prevention
- Send undefined (omit), never '', for unfilled SSH form fields
- Remember the merge semantics: defined-but-empty overrides the saved value
- Validate host presence client-side before save
When it happens
Trigger: Saving an SSH remote with no host in either the new input or the existing block; passing sshHost: '' (which, being defined, does NOT inherit the saved host) — the classic footgun called out in the comment.
Common situations: Form serialization sending '' for untouched fields instead of omitting them; clearing the host field and saving; first-time SSH config with all fields blank.
Related errors
- Invalid profile name: ${value}
- Remote gateway session token is required.
- HERMES_DESKTOP_REMOTE_URL is set but HERMES_DESKTOP_REMOTE_T
- SSH remote mode is selected but no host is configured.
- Remote path must not be empty.
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/f15c1b159f769b6e.
Report an issue: GitHub.