{"record":{"id":"e683d710406d5bcf","repo":"eyaltoledano/claude-task-master","slug":"config-error-e683d7","errorCode":"CONFIG_ERROR","errorMessage":"'Failed to save configuration'","messagePattern":"'Failed to save configuration'","errorType":"exception","errorClass":"TaskMasterError","httpStatus":null,"severity":"error","filePath":"packages/tm-core/src/modules/config/services/config-persistence.service.ts","lineNumber":72,"sourceCode":"\t\t\t}\n\n\t\t\t// Ensure directory exists\n\t\t\tconst configDir = path.dirname(this.localConfigPath);\n\t\t\tawait fs.mkdir(configDir, { recursive: true });\n\n\t\t\tconst jsonContent = JSON.stringify(config, null, 2);\n\n\t\t\tif (atomic) {\n\t\t\t\t// Atomic write: write to temp file then rename\n\t\t\t\tconst tempPath = `${this.localConfigPath}.tmp`;\n\t\t\t\tawait fs.writeFile(tempPath, jsonContent, 'utf-8');\n\t\t\t\tawait fs.rename(tempPath, this.localConfigPath);\n\t\t\t} else {\n\t\t\t\t// Direct write\n\t\t\t\tawait fs.writeFile(this.localConfigPath, jsonContent, 'utf-8');\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tthrow new TaskMasterError(\n\t\t\t\t'Failed to save configuration',\n\t\t\t\tERROR_CODES.CONFIG_ERROR,\n\t\t\t\t{ configPath: this.localConfigPath },\n\t\t\t\terror as Error\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Create a backup of the current configuration\n\t */\n\tprivate async createBackup(): Promise<string> {\n\t\ttry {\n\t\t\tawait fs.mkdir(this.backupDir, { recursive: true });\n\n\t\t\tconst timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n\t\t\tconst backupPath = path.join(this.backupDir, `config-${timestamp}.json`);\n","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/eyaltoledano/claude-task-master/blob/c0c98d367c55296bfe69e65680625b6db437af02/packages/tm-core/src/modules/config/services/config-persistence.service.ts#L54-L90","documentation":"ConfigPersistenceService.saveConfig writes config.json atomically (temp file + rename) and throws TaskMasterError with code CONFIG_ERROR when any step — mkdir, writeFile, or rename — fails. details.configPath identifies the target file; the original error is attached as cause.","triggerScenarios":"Calling saveConfig when the config directory doesn't exist and can't be created, the disk is full, permissions deny writing (EACCES/EPERM), the target is read-only, or the rename fails because the target is locked (EBUSY/EISDIR on Windows).","commonSituations":"Read-only filesystem or container without write access to home dir; disk quota exceeded; antivirus/backup tools locking the temp file during rename; running as a user without permission to ~/.task-master; config.json replaced by a directory.","solutions":["Check free disk space and file permissions on details.configPath and its directory, then retry","Ensure the target is a regular writable file, not a directory or symlink loop","Close tools that may lock the file (editors, antivirus, sync clients) or exclude the config dir from syncing","Run the process as a user with write access to the config location, or point config at a writable path"],"exampleFix":"// before\nawait persistence.saveConfig(config); // throws on read-only dir\n// after\ntry {\n  await persistence.saveConfig(config);\n} catch (e) {\n  if (e instanceof TaskMasterError && e.code === 'CONFIG_ERROR') {\n    await fs.chmod(path.dirname(e.details.configPath), 0o755); // fix perms\n    await persistence.saveConfig(config); // retry\n  } else { throw e; }\n}","handlingStrategy":"retry","validationCode":"import { access, constants } from 'fs/promises';\nawait access(path.dirname(configPath), constants.W_OK); // throws early if dir not writable\nconst st = await stat(configPath).catch(() => null);\nif (st && !st.isFile()) throw new Error(`${configPath} is not a regular file`);","typeGuard":"function isConfigWriteError(e: unknown): e is TaskMasterError & { details: { configPath: string } } {\n  return e instanceof TaskMasterError && e.code === 'CONFIG_ERROR'\n    && typeof (e.details as any)?.configPath === 'string';\n}","tryCatchPattern":"try {\n  await persistence.saveConfig(config);\n} catch (e) {\n  if (isConfigWriteError(e)) {\n    await fixPermissionsOrFreeDisk(e.details.configPath);\n    await persistence.saveConfig(config); // retry once\n  } else { throw e; }\n}","preventionTips":["Ensure the config directory exists and is writable before first save","Exclude the config dir from antivirus/backup/sync tools that lock files during rename","Monitor disk space/quota in environments that write config frequently","Never replace config.json with a directory or symlink"],"tags":["config","filesystem","write","permissions"],"backgroundTag":"config-write-failed","analyzedSha":"c0c98d367c55296bfe69e65680625b6db437af02","analyzedAt":"2026-08-29T02:56:26.071Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}