stablyai/orca · error · RuntimeClientError

runtime_error

runtime_error

Error message

Could not read ${dataPath}: ${error instanceof Error ? error.message : String(error)}

What it means

Wrapped RuntimeClientError (code 'runtime_error') thrown by readPersistedState when reading/parsing the agent-hooks state file throws for any reason — a JSON SyntaxError, the not-an-object inner Error (538), or a filesystem read error. It prefixes the message with the dataPath and the underlying error message so the user knows which file failed and why. The original cause is stringified into the message (not chained via .cause).

Source

Thrown at src/cli/handlers/agent-hooks.ts:47

  return join(getDefaultUserDataPath(), 'orca-data.json')
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

function readPersistedState(dataPath: string): PersistedState {
  if (!existsSync(dataPath)) {
    return getDefaultPersistedState(homedir())
  }
  try {
    const parsed = JSON.parse(readFileSync(dataPath, 'utf-8'))
    if (!isRecord(parsed)) {
      throw new Error('file does not contain a JSON object')
    }
    return parsed as PersistedState
  } catch (error) {
    throw new RuntimeClientError(
      'runtime_error',
      `Could not read ${dataPath}: ${error instanceof Error ? error.message : String(error)}`
    )
  }
}

function writePersistedState(dataPath: string, state: PersistedState): void {
  mkdirSync(dirname(dataPath), { recursive: true })
  const tmpPath = join(dirname(dataPath), `.${Date.now()}-${randomUUID()}.tmp`)
  let renamed = false
  try {
    writeFileSync(tmpPath, `${JSON.stringify(state, null, 2)}\n`, 'utf-8')
    renameSync(tmpPath, dataPath)
    renamed = true
  } finally {
    if (!renamed && existsSync(tmpPath)) {
      try {
        unlinkSync(tmpPath)

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Read the embedded underlying message to classify: SyntaxError => corrupt JSON, EACCES => permissions, EISDIR => path is a dir.
  2. Back up then delete the corrupt file so the next run regenerates defaults.
  3. Fix permissions/ownership on the file if the cause is EACCES.
  4. Confirm writePersistedState's atomic-rename path is on the same filesystem to avoid partial writes.

Example fix

// before: corrupt ~/.config/orca/agent-hooks.json -> runtime_error

// after
mv ~/.config/orca/agent-hooks.json ~/.config/orca/agent-hooks.json.bak
# next run recreates a valid default state
Defensive patterns

Strategy: try-catch

Validate before calling

import { readFileSync, existsSync, statSync } from 'node:fs'

function safeReadState(dataPath: string): string {
  if (!existsSync(dataPath)) throw new Error('missing')
  const st = statSync(dataPath)
  if (!st.isFile()) throw new Error(`${dataPath} is not a regular file`)
  return readFileSync(dataPath, 'utf-8')
}

Type guard

function isRuntimeClientError(e: unknown): e is RuntimeClientError {
  return e instanceof RuntimeClientError
}

Try / catch

try {
  readPersistedState(dataPath)
} catch (e) {
  if (e instanceof RuntimeClientError && e.code === 'runtime_error') {
    // back up and delete corrupt file so defaults regenerate
    // renameSync(dataPath, dataPath + '.bak')
  }
  throw e
}

Prevention

When it happens

Trigger: existsSync(dataPath) is true but readFileSync throws (permissions, EISDIR), or JSON.parse throws (truncated/corrupt JSON), or the parsed value is not a record (inner throw at 538).

Common situations: Corrupt or truncated state file (crash mid-write before the atomic rename), permissions changed on the file, the path points at a directory, or a partial write from a killed process.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/0e785f399825c7d7. Report an issue: GitHub.