dmtrKovalenko/fff · error · Error

Could not read pi-fff config at

Error message

Could not read pi-fff config at ${configPath}: ${errorMessage(error)}

What it means

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.

Solutions

  1. 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).
  2. Fix permissions: `chmod u+r <path>` and ensure the process user can read it (check ownership with ls -l).
  3. If the config is broken/unneeded, move it aside and let pi-fff recreate defaults.
  4. Read the wrapped underlying error (EACCES vs EISDIR vs ELOOP) to target the exact filesystem problem.

Example fix

// before
$ mkdir -p ~/.config/pi-fff/config.json   # wrong: creates a directory
// after
rm -rf ~/.config/pi-fff/config.json
printf '{}' > ~/.config/pi-fff/config.json && chmod 600 ~/.config/pi-fff/config.json
Defensive patterns

Strategy: try-catch

Validate before calling

import { statSync, accessSync, constants } from 'fs';
const st = statSync(configPath, { throwIfNoEntry: false });
if (st?.isDirectory()) throw new Error(`${configPath} is a directory, expected a JSON file`);
if (st) accessSync(configPath, constants.R_OK);

Try / catch

try {
  const cfg = loadConfig();
} catch (e) {
  if (String(e.message).startsWith('Could not read pi-fff config')) {
    // move broken config aside and continue with defaults
    renameSync(configPath, configPath + '.broken');
    return {};
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of dmtrKovalenko/fff@7f8537e70f (2026-09-10). Data as JSON: /api/errors/e7b1551739add1c4. Report an issue: GitHub.

Appendix: source

Thrown at packages/pi-fff/src/config.ts:40

  "$schema",
  "mode",
  "frecencyDbPath",
  "historyDbPath",
  "enableFsRootScanning",
  "enableHomeDirScanning",
  "warnOnHomeDirScan",
  "followSymlinks",
]);

export function loadConfig(agentDir = piDataDir()): FffConfig {
  const configPath = join(agentDir, CONFIG_FILE_NAME);
  let contents: string;

  try {
    contents = readFileSync(configPath, "utf8");
  } catch (error: unknown) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
    throw new Error(
      `Could not read pi-fff config at ${configPath}: ${errorMessage(error)}`,
    );
  }

  let parsed: unknown;
  try {
    parsed = JSON.parse(contents);
  } catch (error: unknown) {
    throw invalidConfig(configPath, `not valid JSON (${errorMessage(error)})`);
  }

  if (!isRecord(parsed)) {
    throw invalidConfig(configPath, "expected a JSON object");
  }

  for (const key of Object.keys(parsed)) {
    if (!CONFIG_KEYS.has(key as keyof FffConfig)) {
      throw invalidConfig(configPath, `unknown option "${key}"`);

View on GitHub (pinned to 7f8537e70f)