NousResearch/hermes-agent · error

Saving is not available

Error message

Saving is not available

What it means

Thrown by writeDesktopFileText() in apps/desktop/src/lib/desktop-fs.ts:91 when running in LOCAL mode (not remote FS mode) and the bridge object lacks a `writeTextFile` method. The hardened Electron IPC save path only exists in shells that expose it; an older desktop build, a stripped preload, or a non-desktop environment with a partial hermesDesktop stub reaches this guard and saving is refused rather than silently doing nothing.

Source

Thrown at apps/desktop/src/lib/desktop-fs.ts:91

export async function readDesktopFileText(path: string): Promise<HermesReadFileTextResult> {
  if (!isDesktopFsRemoteMode()) {
    return bridge().readFileText(path)
  }

  return remoteFsApi<HermesReadFileTextResult>(fsPath('read-text', path))
}

// Save UTF-8 text back to a file. Local writes go through the hardened Electron
// IPC; remote writes hit the dashboard's POST /api/fs/write-text (same path
// hardening, parent-must-exist, size cap) so the editor behaves identically in
// both modes. Stale-on-disk detection is the caller's job (re-read before save).
export async function writeDesktopFileText(path: string, content: string): Promise<{ path: string }> {
  const desktop = bridge()

  if (!isDesktopFsRemoteMode()) {
    if (!desktop.writeTextFile) {
      throw new Error('Saving is not available')
    }

    return desktop.writeTextFile(path, content)
  }

  const result = await remoteFsApi<{ ok?: boolean; path?: string }>('/api/fs/write-text', { content, path })

  return { path: result.path || path }
}

export async function readDesktopFileDataUrl(path: string): Promise<string> {
  if (!isDesktopFsRemoteMode()) {
    return bridge().readFileDataUrl(path)
  }

  const result = await remoteFsApi<string | { dataUrl?: string }>(fsPath('read-data-url', path))

  return typeof result === 'string' ? result : result.dataUrl || ''

View on GitHub (pinned to c896c09c42)

Solutions

  1. Update the desktop shell so the preload exposes writeTextFile (restart the app after update — the hook's own copy recommends a restart for similar IPC gaps).
  2. Guard the save UI: disable the Save action when `!window.hermesDesktop?.writeTextFile && !isDesktopFsRemoteMode()`.
  3. In remote mode this path isn't taken — connect to the remote gateway so writes go through POST /api/fs/write-text.
  4. For tests, stub writeTextFile on the fake bridge.

Example fix

// before
await writeDesktopFileText(path, content) // throws on old shell

// after
const canSave = isDesktopFsRemoteMode() || Boolean(window.hermesDesktop?.writeTextFile)
if (!canSave) { setSaveUnavailable('Update the desktop app to enable saving'); return }
await writeDesktopFileText(path, content)
Defensive patterns

Strategy: type-guard

Validate before calling

function canWriteFiles(): boolean {
  return isDesktopFsRemoteMode() || typeof window.hermesDesktop?.writeTextFile === 'function'
}

Type guard

function hasWriteTextFile(w: Window): w is Window & { hermesDesktop: { writeTextFile: (p: string, c: string) => Promise<unknown> } } {
  return typeof (w as any).hermesDesktop?.writeTextFile === 'function'
}

Try / catch

if (!canWriteFiles()) { setSaveDisabled('Update the desktop app to enable saving'); return }
try { await writeDesktopFileText(path, content) } catch (e) { notifyError(e, 'Save failed') }

Prevention

When it happens

Trigger: Calling writeDesktopFileText in local mode on an older Electron shell that predates the writeTextFile IPC; a test/mocked bridge exposing only `api`; a preview environment where the bridge exists but file-write capabilities were not injected.

Common situations: Desktop app and runtime on separate clocks after an update (renderer newer than shell); running the renderer in a browser with a partial hermesDesktop shim; sandboxed contexts where the write IPC was deliberately omitted.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/4e01d6c7e4402044. Report an issue: GitHub.