{"record":{"id":"03a49e10637393c1","repo":"yikart/AiToEarn","slug":"responsecode-configeditorwritefailed","errorCode":"ResponseCode.ConfigEditorWriteFailed","errorMessage":"ConfigEditorWriteFailed: error.message","messagePattern":"ConfigEditorWriteFailed: error\\.message","errorType":"error_code","errorClass":"AppException","httpStatus":null,"severity":"critical","filePath":"project/aitoearn-backend/libs/config-editor/src/config-editor.service.ts","lineNumber":100,"sourceCode":"\n  private async readConfigFile(configPath: string) {\n    try {\n      return await readFile(configPath, 'utf-8')\n    }\n    catch (error) {\n      throw new AppException(\n        ResponseCode.ConfigEditorReadFailed,\n        error instanceof Error ? error.message : String(error),\n      )\n    }\n  }\n\n  private async writeConfigFile(configPath: string, content: string) {\n    try {\n      await writeFile(configPath, content, 'utf-8')\n    }\n    catch (error) {\n      throw new AppException(\n        ResponseCode.ConfigEditorWriteFailed,\n        error instanceof Error ? error.message : String(error),\n      )\n    }\n  }\n}\n","sourceCodeStart":82,"sourceCodeEnd":107,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/libs/config-editor/src/config-editor.service.ts#L82-L107","documentation":"writeConfigFile failed when saveConfig tried to persist the serialized config with fs writeFile (utf-8). The Node error is rethrown as AppException(ConfigEditorWriteFailed) with error.message, typically ENOSPC (disk full), EACCES (no write permission), EROFS (read-only filesystem), or ENOENT (parent directory missing).","triggerScenarios":"saveConfig() is called and the resolved configPath is in a read-only container filesystem or read-only volume mount, the process user lacks write permission, the disk is full, or the config file was deleted along with its directory after startup.","commonSituations":"Kubernetes/Docker mounts with the config baked as read-only (common for ConfigMap mounts); running as a non-root user in a container that owns no writable path; tmpfs full; disk quota exceeded; PM2 restart with a cwd whose config dir no longer exists.","solutions":["Read error.message: ENOSPC → free disk space; EACCES → chown/chmod the file or run as a user with write access; EROFS/ENOENT → make the mount writable or recreate the directory","Ensure the config file's volume is mounted rw, not :ro, in docker-compose/Kubernetes","Give the container's runtime user ownership of the config directory (chown -R node:node /app/config)","If the config is intentionally immutable (ConfigMap), stop exposing saveConfig / make the editor read-only instead of writing"],"exampleFix":"// before (docker-compose.yml)\nvolumes:\n  - ./config/app.yaml:/app/config/app.yaml:ro\n// after\nvolumes:\n  - ./config/app.yaml:/app/config/app.yaml:rw","handlingStrategy":"try-catch","validationCode":"import { accessSync, constants } from 'node:fs'\ntry { accessSync(resolvedPath, constants.W_OK) } catch { /* not writable — surface before editing */ }","typeGuard":null,"tryCatchPattern":"try {\n  await editor.saveConfig(newConfig)\n} catch (e) {\n  if (e instanceof AppException && e.code === ResponseCode.ConfigEditorWriteFailed) {\n    if (e.message.includes('EROFS')) logger.error('Config filesystem is read-only')\n    if (e.message.includes('ENOSPC')) logger.error('Disk full')\n    if (e.message.includes('EACCES')) logger.error('No write permission')\n  }\n  throw e\n}","preventionTips":["Mount config volumes as rw if the editor must save","Grant the runtime user write access (chown/chmod) to the config directory","Do not expose saveConfig when configs are intentionally immutable (ConfigMap)","Monitor disk space on hosts where config edits are allowed"],"tags":["filesystem","config","io","permissions"],"backgroundTag":"file-write-permission-denied","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}