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
- Check permissions on the journal file and parent directory: ls -la <agentDataPath>/memory/.
- Ensure the Electron process has read access (same user that wrote it).
- 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
- Ensure the Electron process owns its userData files and has read access.
- Do not move userData to read-only media without updating permissions.
- Surface the wrapped message to the user so they can fix permissions.
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
- Agent memory file must be a real file: ${exact}
- Agent memory file must be a real file: ${matchedPath}
- Agent memory directory must be a real directory: ${memoryDir
- Agent memory file must be a real file: ${filePath}
- Agent journal must be a regular file: ${journalPath}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/0934cde20095ac3f.
Report an issue: GitHub.