yikart/AiToEarn · critical · AppException

ResponseCode.ConfigEditorReadFailed

ResponseCode.ConfigEditorReadFailed

Error message

ConfigEditorReadFailed: error.message

What it means

readConfigFile failed to read the resolved config file (resolve(process.cwd(), configPath)) with fs readFile in utf-8 mode. The underlying Node error (ENOENT, EACCES, EISDIR, etc.) is wrapped into AppException(ConfigEditorReadFailed) with error.message preserved, so the message names the real filesystem cause.

Source

Thrown at project/aitoearn-backend/libs/config-editor/src/config-editor.service.ts:88

    if (!result.success) {
      throw new AppException(ResponseCode.ConfigEditorValidationFailed, z.prettifyError(result.error))
    }
    return result.data as Record<string, unknown>
  }

  private serializeConfig(config: Record<string, unknown>, format: ConfigFileFormat) {
    if (format === ConfigFileFormat.Json) {
      return `${JSON.stringify(config, null, 2)}\n`
    }
    return stringifyYaml(config)
  }

  private async readConfigFile(configPath: string) {
    try {
      return await readFile(configPath, 'utf-8')
    }
    catch (error) {
      throw new AppException(
        ResponseCode.ConfigEditorReadFailed,
        error instanceof Error ? error.message : String(error),
      )
    }
  }

  private async writeConfigFile(configPath: string, content: string) {
    try {
      await writeFile(configPath, content, 'utf-8')
    }
    catch (error) {
      throw new AppException(
        ResponseCode.ConfigEditorWriteFailed,
        error instanceof Error ? error.message : String(error),
      )
    }
  }
}

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Read error.message: fix the exact cause — create the missing file (ENOENT), fix permissions with chmod/chown (EACCES), or point configPath at a file not a directory (EISDIR)
  2. Use an absolute configPath (or verify process.cwd() at startup) so the resolve() lands where the file actually is
  3. In Docker/PM2, mount or COPY the config file and confirm with `ls` inside the container/pwd context
  4. Check the container user can read the file: run `whoami` and `stat <path>` in the same environment

Example fix

// before
ConfigEditorModule.register({ configPath: 'config/app.yaml', schema: S })
// after (cwd-independent)
ConfigEditorModule.register({ configPath: '/app/config/app.yaml', schema: S })
Defensive patterns

Strategy: try-catch

Validate before calling

import { accessSync, constants } from 'node:fs'
try { accessSync(resolvedPath, constants.R_OK) } catch (e) { /* fail fast: file missing/unreadable */ }

Try / catch

try {
  await editor.getConfig()
} catch (e) {
  if (e instanceof AppException && e.code === ResponseCode.ConfigEditorReadFailed) {
    if (e.message.includes('ENOENT')) logger.error(`Config file missing: ${e.message}`)
    if (e.message.includes('EACCES')) logger.error(`Config file unreadable: ${e.message}`)
  }
  throw e
}

Prevention

When it happens

Trigger: The file at configPath does not exist (ENOENT), the process lacks read permission (EACCES), configPath resolves to a directory (EISDIR), or the path is wrong relative to the process's cwd (containers/PM2 often change cwd).

Common situations: Config file not mounted/copied into a Docker image or volume; wrong relative path because the app starts from a different cwd under pm2/Nx; file permissions after a non-root container user change; a typo in configPath; the config file deleted between restarts because it lived only in a container layer.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/dd36dacfaefd15f7. Report an issue: GitHub.