CherryHQ/cherry-studio · error · Error

Agent memory file must be a real file: ${matchedPath}

Error message

Agent memory file must be a real file: ${matchedPath}

What it means

The second branch of resolveFileCI(): when no exact-case match exists, it scans the directory case-insensitively. If a case-variant match (e.g. 'fact.md' when looking for 'FACT.md') is found but that entry is a symlink or non-regular file, this throws. Same security invariant as the exact-match branch, applied to the fallback resolution.

Source

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

    const fileStat = await lstat(exact)
    if (!fileStat.isFile() || fileStat.isSymbolicLink()) {
      throw new Error(`Agent memory file must be a real file: ${exact}`)
    }
    return exact
  } catch (err) {
    if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err
    // exact match not found, try case-insensitive
  }

  try {
    const entries = await readdir(dir)
    const target = name.toLowerCase()
    const match = entries.find((e) => e.toLowerCase() === target)
    if (!match) return exact
    const matchedPath = path.join(dir, match)
    const fileStat = await lstat(matchedPath)
    if (!fileStat.isFile() || fileStat.isSymbolicLink()) {
      throw new Error(`Agent memory file must be a real file: ${matchedPath}`)
    }
    return matchedPath
  } catch (err) {
    if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
      logger.warn('Unexpected error reading directory', { dir, error: (err as Error).message })
    }
    return exact
  }
}

type JournalEntry = {
  ts: string
  tags: string[]
  text: string
}

const MEMORY_TOOL: Tool = {
  name: 'memory',

View on GitHub (pinned to 726446b54c)

Solutions

  1. List the memory directory and identify the case-variant entry: ls -la <agentDataPath>/memory/.
  2. Remove the offending case-variant symlink/non-file and create FACT.md or JOURNAL.jsonl as a regular file with the correct case.
  3. On case-insensitive filesystems, move the file to a temp name first to force the correct case.

Example fix

# before: fact.md (lowercase) is a symlink
ls memory/  # fact.md -> /elsewhere

# after
rm memory/fact.md
printf '# Facts
' > memory/FACT.md
Defensive patterns

Strategy: validation

Validate before calling

import { lstat, readdir } from 'node:fs/promises'
import path from 'node:path'

async function safeResolveCi(dir: string, name: string): Promise<string | null> {
  const target = name.toLowerCase()
  const entries = await readdir(dir).catch(() => [])
  const match = entries.find((e) => e.toLowerCase() === target)
  if (!match) return null
  const p = path.join(dir, match)
  const s = await lstat(p)
  return s.isFile() && !s.isSymbolicLink() ? p : null
}

Prevention

When it happens

Trigger: The exact filename is absent, but a case-different variant exists and is a symlink/directory/special file. Happens on case-insensitive filesystems (macOS APFS, Windows NTFS) where the OS may report a differently-cased entry, or when a user manually created 'fact.md' as a symlink.

Common situations: macOS users whose filesystem folds case; a symlink left from a migration; a directory accidentally named with wrong case; cross-platform sync creating case variants.

Related errors


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