{"record":{"id":"5ad6453b9d15a31f","repo":"CherryHQ/cherry-studio","slug":"required-agent-prompt-file-is-not-a-regular-file","errorCode":null,"errorMessage":"Required agent prompt file is not a regular file: ${exact}","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":22,"sourceCode":"\nimport { loggerService } from '@logger'\nimport type { AgentConfiguration } from '@shared/data/types/agent'\n\nimport { buildBootstrapInstructions, SOUL_CONTENT_THRESHOLD } from './bootstrap'\n\nconst logger = loggerService.withContext('PromptBuilder')\n\n/**\n * Resolve a filename within a directory using case-insensitive matching.\n * Returns the full path if found (preferring exact match), or undefined.\n */\nasync function resolveFile(dir: string, name: string, failOnError = false): Promise<string | undefined> {\n  const exact = path.join(dir, name)\n  try {\n    const fileStat = await lstat(exact)\n    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) {","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/agents/prompt.ts#L4-L40","documentation":"Thrown by resolveFile (with failOnError=true) when an exact-match prompt file path exists but lstat reports it is not a regular file or is a symbolic link. Symlinks are explicitly ignored (logged as a warning) and, in failOnError mode, escalate to a throw. This keeps agent prompt assembly from following symlinks, which could read files outside the prompt data directory.","triggerScenarios":"resolveFile(dir, name, failOnError=true) is called for a required prompt resource whose exact path exists but is a symlink, directory, FIFO, or special file.","commonSituations":"A required prompt file (e.g. a system-prompt fragment) was replaced by a symlink; the prompt data dir lives on synced storage that materializes placeholders; tampering; a directory created where a file was expected.","solutions":["Inspect the exact path with `ls -la <exact>` and `readlink <exact>`.","Replace the symlink/non-regular entry with the real prompt file content.","Ensure prompt data files are committed as real files, not symlinks, in the repo.","If the file is optional, call resolveFile without failOnError so it returns undefined instead of throwing."],"exampleFix":"// before: required prompt file is a symlink\nprompts/system.md -> /etc/something\n\n// after: real file\nrm prompts/system.md\necho \"...\" > prompts/system.md   # a real regular file","handlingStrategy":"validation","validationCode":"import { lstat } from 'node:fs/promises'\nimport path from 'node:path'\nasync function isRegularPromptFile(dir: string, name: string): Promise<boolean> {\n  try {\n    const s = await lstat(path.join(dir, name))\n    return s.isFile() && !s.isSymbolicLink()\n  } catch {\n    return false\n  }\n}\nif (!(await isRegularPromptFile(dir, name))) {\n  // skip failOnError; mark prompt resource as missing/unsafe\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    // required prompt resource is unsafe; abort prompt assembly with a clear error\n  } else throw e\n}","preventionTips":["Store prompt resources as real files, not symlinks.","Keep prompt data directories off synced storage.","Only set failOnError for truly required resources."],"tags":["filesystem","security","symlink","agents","prompts"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}