CherryHQ/cherry-studio · error · Error
Agent journal must be a regular file: ${journalPath}
Error message
Agent journal must be a regular file: ${journalPath} What it means
In memoryAppend(), after opening JOURNAL.jsonl with O_APPEND|O_CREAT|O_WRONLY (and O_NOFOLLOW on non-Windows), handle.stat() is checked. If the opened handle is somehow not a regular file (stat().isFile() false), it throws. This catches a swap where a FIFO, device, or non-regular file occupies the journal path between the pre-write assertRegularFileOrMissing check and the open.
Source
Thrown at src/main/ai/mcp/servers/agentMemory.ts:264
const memoryDir = await this.assertMemoryDirectory()
const journalPath = await resolveFileCI(memoryDir, 'JOURNAL.jsonl')
await this.assertRegularFileOrMissing(journalPath)
const entry: JournalEntry = {
ts: new Date().toISOString(),
tags,
text
}
const handle = await open(
journalPath,
withNoFollow(constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY),
0o600
)
try {
const fileStat = await handle.stat()
if (!fileStat.isFile()) throw new Error(`Agent journal must be a regular file: ${journalPath}`)
await handle.appendFile(JSON.stringify(entry) + '\n', 'utf-8')
} finally {
await handle.close()
}
logger.info('Journal entry appended via tool', { agentId: this.agentId, tags })
return {
content: [{ type: 'text' as const, text: `Journal entry added at ${entry.ts}.` }]
}
}
private async memorySearch(args: Record<string, string | undefined>) {
const query = args.query ?? ''
const tagFilter = args.tag ?? ''
const limit = Math.max(1, parseInt(args.limit ?? '20', 10) || 20)
const memoryDir = await this.assertMemoryDirectory()
const journalPath = await resolveFileCI(memoryDir, 'JOURNAL.jsonl')View on GitHub (pinned to 726446b54c)
Solutions
- Identify the special file: ls -la <agentDataPath>/memory/JOURNAL.jsonl.
- Remove it: rm memory/JOURNAL.jsonl (the append will recreate it via O_CREAT).
- Audit for the process that created the non-regular file.
Example fix
# before: JOURNAL.jsonl is a FIFO ls -la memory/JOURNAL.jsonl # prw-r--r-- # after rm memory/JOURNAL.jsonl # re-run append; O_CREAT recreates a regular file
Defensive patterns
Strategy: try-catch
Validate before calling
import { lstat } from 'node:fs/promises'
async function isRegularFile(p: string): Promise<boolean> {
try {
const s = await lstat(p)
return s.isFile() && !s.isSymbolicLink()
} catch {
return false
}
}
// Minimize the TOCTOU window: assert immediately before open.
if (!(await isRegularFile(journalPath)) && !(await fileExists(journalPath))) {
throw new Error('Journal path is occupied by a non-regular file')
} Try / catch
try {
await memoryAppend(args)
} catch (err) {
if (err instanceof Error && err.message.startsWith('Agent journal must be a regular file')) {
// Cleanup the offending entry and retry once
await unlink(journalPath).catch(() => {})
return memoryAppend(args)
}
throw err
} Prevention
- Do not create FIFOs/devices inside agent data directories.
- If you see this repeatedly, audit for a local process tampering with the journal path.
- The O_NOFOLLOW guard already blocks symlinks on non-Windows; this check covers the rest.
When it happens
Trigger: A FIFO, device node, or socket exists at JOURNAL.jsonl. O_NOFOLLOW already blocks symlinks (on non-Windows), so this catches the remaining non-regular file types. The TOCTOU window is between assertRegularFileOrMissing and open.
Common situations: An adversarial local process creates a named pipe at the journal path to hang or exploit the append; filesystem corruption placed a special file there; a misbehaving backup tool.
Related errors
- Agent memory file must be a real file: ${filePath}
- Agent storage directory must be a real directory: ${targetPa
- Agent data file must be a real file: ${filePath}
- Agent memory file must be a real file: ${exact}
- Agent memory file must be a real file: ${matchedPath}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/6ddeca882f0fa6cf.
Report an issue: GitHub.