{"record":{"id":"272a8dc867491b64","repo":"CherryHQ/cherry-studio","slug":"required-agent-prompt-file-is-not-a-regular-file-272a8d","errorCode":null,"errorMessage":"Required agent prompt file is not a regular file: ${matchedPath}","messagePattern":"Required agent prompt file is not a regular file: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/agents/prompt.ts","lineNumber":38,"sourceCode":"    if (fileStat.isFile() && !fileStat.isSymbolicLink()) return exact\n    if (fileStat.isSymbolicLink()) logger.warn('Ignoring symbolic link in agent prompt data', { path: exact })\n    if (failOnError) throw new Error(`Required agent prompt file is not a regular file: ${exact}`)\n    return undefined\n  } catch (error) {\n    if (failOnError && (error as NodeJS.ErrnoException).code !== 'ENOENT') throw error\n    // exact match not found, try case-insensitive\n  }\n\n  try {\n    const entries = await readdir(dir)\n    const target = name.toLowerCase()\n    const match = entries.find((e) => e.toLowerCase() === target)\n    if (!match) return undefined\n    const matchedPath = path.join(dir, match)\n    const fileStat = await lstat(matchedPath)\n    if (fileStat.isFile() && !fileStat.isSymbolicLink()) return matchedPath\n    if (fileStat.isSymbolicLink()) logger.warn('Ignoring symbolic link in agent prompt data', { path: matchedPath })\n    if (failOnError) throw new Error(`Required agent prompt file is not a regular file: ${matchedPath}`)\n    return undefined\n  } catch (error) {\n    if (failOnError) throw error\n    return undefined\n  }\n}\n\nasync function isRealDirectory(dir: string): Promise<boolean> {\n  try {\n    const directoryStat = await lstat(dir)\n    if (directoryStat.isSymbolicLink()) {\n      logger.warn('Ignoring symbolic-link directory in agent prompt data', { path: dir })\n      return false\n    }\n    return directoryStat.isDirectory()\n  } catch {\n    return false\n  }","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/agents/prompt.ts#L20-L56","documentation":"Sibling of error 89 but for the case-insensitive fallback: resolveFile found a directory entry whose lowercased name matches the requested name, yet lstat of that matched path shows it is not a regular file or is a symlink. In failOnError mode this escalates to a throw; otherwise the function returns undefined.","triggerScenarios":"resolveFile(dir, name, failOnError=true) finds no exact match, enumerates the directory case-insensitively, finds a candidate, but that candidate is a symlink/dir/special file rather than a regular file.","commonSituations":"Case-insensitive filesystem collisions (macOS/Windows) where a symlink or directory name matches the requested file; symlinked prompt data on synced storage; tampering.","solutions":["Inspect the matched path from the error: `ls -la <matchedPath>` and `readlink <matchedPath>`.","Remove or replace the offending entry with a real regular file of the intended name.","Use exact-case filenames for prompt resources so the case-insensitive fallback is not exercised.","Avoid storing prompt data on filesystems that confuse symlinked and real entries."],"exampleFix":"// before: case-insensitive match lands on a symlink\nprompts/System.MD -> /elsewhere   (requested 'system.md')\n\n// after: real file with correct case\nrm prompts/System.MD\necho \"...\" > prompts/system.md","handlingStrategy":"validation","validationCode":"import { lstat, readdir } from 'node:fs/promises'\nimport path from 'node:path'\nasync function findRealPromptFile(dir: string, name: string): Promise<string | undefined> {\n  const exact = path.join(dir, name)\n  try {\n    const s = await lstat(exact)\n    if (s.isFile() && !s.isSymbolicLink()) return exact\n  } catch { /* fall through */ }\n  const target = name.toLowerCase()\n  const match = (await readdir(dir)).find((e) => e.toLowerCase() === target)\n  if (!match) return undefined\n  const s = await lstat(path.join(dir, match))\n  return s.isFile() && !s.isSymbolicLink() ? path.join(dir, match) : undefined\n}","typeGuard":"function isRegularFile(stat: import('node:fs').Stats): boolean {\n  return stat.isFile() && !stat.isSymbolicLink()\n}","tryCatchPattern":"try {\n  const resolved = await resolveFile(dir, name, true)\n} catch (e) {\n  if (e instanceof Error && /not a regular file/.test(e.message)) {\n    // case-insensitive match was a symlink/special file; abort\n  } else throw e\n}","preventionTips":["Use exact-case prompt filenames to avoid the case-insensitive fallback.","Never symlink prompt resources.","Avoid case-insensitive filesystems for prompt data when possible."],"tags":["filesystem","security","symlink","agents","prompts","case-insensitive"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}