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.

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.

Appendix: 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)