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
- 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)
- Use an absolute configPath (or verify process.cwd() at startup) so the resolve() lands where the file actually is
- In Docker/PM2, mount or COPY the config file and confirm with `ls` inside the container/pwd context
- 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
- Prefer absolute configPath so it is independent of process.cwd()
- Mount/COPY the config file into Docker images and verify with ls at startup
- Check file readability for the runtime user, especially non-root containers
- Keep config files on persistent volumes, not ephemeral container layers
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.