CherryHQ/cherry-studio · error · Error

Failed to read journal at ${journalPath}: ${(err as Error).m

Error message

Failed to read journal at ${journalPath}: ${(err as Error).message}

What it means

In memorySearch(), any error from opening or reading the journal that is NOT ENOENT is re-thrown wrapped with context. ENOENT is treated as 'no journal yet' and returns a friendly message; all other failures (EACCES, EIO, ENOSPC, etc.) bubble up as this wrapped Error, which the outer CallToolRequest catch converts into an isError response.

Source

Thrown at src/main/ai/mcp/servers/agentMemory.ts:298

    const memoryDir = await this.assertMemoryDirectory()
    const journalPath = await resolveFileCI(memoryDir, 'JOURNAL.jsonl')

    let fileContent: string
    try {
      const handle = await open(journalPath, withNoFollow(constants.O_RDONLY))
      try {
        const fileStat = await handle.stat()
        if (!fileStat.isFile()) throw new Error(`Agent journal must be a regular file: ${journalPath}`)
        fileContent = await handle.readFile('utf-8')
      } finally {
        await handle.close()
      }
    } catch (err) {
      if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
        return { content: [{ type: 'text' as const, text: 'No journal entries found.' }] }
      }
      throw new Error(`Failed to read journal at ${journalPath}: ${(err as Error).message}`)
    }

    const queryLower = query.toLowerCase()
    const tagLower = tagFilter.toLowerCase()
    const matches: JournalEntry[] = []

    for (const line of fileContent.split('\n')) {
      if (!line.trim()) continue
      let entry: JournalEntry
      try {
        entry = JSON.parse(line)
      } catch {
        logger.warn('Skipping corrupted journal line', { journalPath, line: line.substring(0, 100) })
        continue
      }
      if (tagFilter && !entry.tags?.some((t) => t.toLowerCase() === tagLower)) continue
      if (query && !entry.text.toLowerCase().includes(queryLower)) continue
      matches.push(entry)

View on GitHub (pinned to 726446b54c)

Solutions

  1. Check permissions on the journal file and parent directory: ls -la <agentDataPath>/memory/.
  2. Ensure the Electron process has read access (same user that wrote it).
  3. If the disk is failing, run filesystem checks and restore from backup.

Example fix

# before: file owned by root after a bad migration
ls -la memory/JOURNAL.jsonl  # -rw------- root root

# after: fix ownership to the app user
chown $USER:$USER memory/JOURNAL.jsonl
Defensive patterns

Strategy: try-catch

Validate before calling

import { access } from 'node:fs/promises'
import { constants } from 'node:fs'

async function isReadable(p: string): Promise<boolean> {
  try {
    await access(p, constants.R_OK)
    return true
  } catch {
    return false
  }
}

Try / catch

try {
  return await memorySearch(args)
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Failed to read journal')) {
    logger.warn('Journal read failed', { error: err.message })
    return { content: [{ type: 'text', text: 'Journal temporarily unavailable.' }] }
  }
  throw err
}

Prevention

When it happens

Trigger: The journal exists but cannot be read: permission denied (EACCES), disk I/O error (EIO), path too long, or the file is locked by another process in a way that blocks the read.

Common situations: File permissions were changed after creation (chmod); the disk has bad sectors; antivirus or another process holds an exclusive lock; the userData directory was moved to read-only media.

Related errors


AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12). Data as JSON: /api/errors/0934cde20095ac3f. Report an issue: GitHub.