{"record":{"id":"e7b1551739add1c4","repo":"dmtrKovalenko/fff","slug":"could-not-read-pi-fff-config-at-configpath-e","errorCode":null,"errorMessage":"Could not read pi-fff config at ${configPath}: ${errorMessage(error)}","messagePattern":"Could not read pi-fff config at (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/pi-fff/src/config.ts","lineNumber":40,"sourceCode":"  \"$schema\",\n  \"mode\",\n  \"frecencyDbPath\",\n  \"historyDbPath\",\n  \"enableFsRootScanning\",\n  \"enableHomeDirScanning\",\n  \"warnOnHomeDirScan\",\n  \"followSymlinks\",\n]);\n\nexport function loadConfig(agentDir = piDataDir()): FffConfig {\n  const configPath = join(agentDir, CONFIG_FILE_NAME);\n  let contents: string;\n\n  try {\n    contents = readFileSync(configPath, \"utf8\");\n  } catch (error: unknown) {\n    if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n    throw new Error(\n      `Could not read pi-fff config at ${configPath}: ${errorMessage(error)}`,\n    );\n  }\n\n  let parsed: unknown;\n  try {\n    parsed = JSON.parse(contents);\n  } catch (error: unknown) {\n    throw invalidConfig(configPath, `not valid JSON (${errorMessage(error)})`);\n  }\n\n  if (!isRecord(parsed)) {\n    throw invalidConfig(configPath, \"expected a JSON object\");\n  }\n\n  for (const key of Object.keys(parsed)) {\n    if (!CONFIG_KEYS.has(key as keyof FffConfig)) {\n      throw invalidConfig(configPath, `unknown option \"${key}\"`);","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/dmtrKovalenko/fff/blob/7f8537e70f0ea1210f9acbbfc4640141105cdc78/packages/pi-fff/src/config.ts#L22-L58","documentation":"pi-fff's loadConfig() reads the user's JSON config file synchronously and treats only ENOENT as 'no config'. Any other read failure (permissions, EISDIR, I/O errors) is rethrown wrapped in this message with the path and underlying error text.","triggerScenarios":"Calling config()/loadConfig() when the config file at configPath exists but cannot be read: it is a directory instead of a file, the process lacks read permission, a symlink is broken in an unexpected way (ELOOP), or the filesystem errors during read.","commonSituations":"Config path pointing at a directory (someone created ~/.config/pi-fff/config.json/ as a folder), files owned by another user after switching run users (root vs user), NFS/permissions issues in containers, or SELinux/apparmor denials.","solutions":["Check the file at the printed path: ensure it is a regular file, not a directory (`file <path>`; if a dir, remove it and create the JSON file).","Fix permissions: `chmod u+r <path>` and ensure the process user can read it (check ownership with ls -l).","If the config is broken/unneeded, move it aside and let pi-fff recreate defaults.","Read the wrapped underlying error (EACCES vs EISDIR vs ELOOP) to target the exact filesystem problem."],"exampleFix":"// before\n$ mkdir -p ~/.config/pi-fff/config.json   # wrong: creates a directory\n// after\nrm -rf ~/.config/pi-fff/config.json\nprintf '{}' > ~/.config/pi-fff/config.json && chmod 600 ~/.config/pi-fff/config.json","handlingStrategy":"try-catch","validationCode":"import { statSync, accessSync, constants } from 'fs';\nconst st = statSync(configPath, { throwIfNoEntry: false });\nif (st?.isDirectory()) throw new Error(`${configPath} is a directory, expected a JSON file`);\nif (st) accessSync(configPath, constants.R_OK);","typeGuard":null,"tryCatchPattern":"try {\n  const cfg = loadConfig();\n} catch (e) {\n  if (String(e.message).startsWith('Could not read pi-fff config')) {\n    // move broken config aside and continue with defaults\n    renameSync(configPath, configPath + '.broken');\n    return {};\n  }\n  throw e;\n}","preventionTips":["Never create a directory where the config file is expected.","Ensure the process user owns/can read the config file (chmod 600).","Validate config JSON after edits to catch malformed setups early.","Check the wrapped errno (EACCES/EISDIR/ELOOP) before deeper debugging."],"tags":["config","file-read","permissions","filesystem"],"backgroundTag":"file-read-failed","analyzedSha":"7f8537e70f0ea1210f9acbbfc4640141105cdc78","analyzedAt":"2026-09-10T07:26:53.407Z","contentChangedAt":"2026-09-10T07:26:53.407Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}