{"record":{"id":"da5a82533730a6bd","repo":"eyaltoledano/claude-task-master","slug":"failed-to-read-filepath-for-modification-err","errorCode":null,"errorMessage":"Failed to read ${filePath} for modification: ${err.message}","messagePattern":"Failed to read (.+?) for modification: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/tm-core/src/modules/storage/adapters/file-storage/file-operations.ts","lineNumber":151,"sourceCode":"\t\t\t} catch (err: any) {\n\t\t\t\t// Distinguish between expected empty/new files and actual corruption\n\t\t\t\tif (err.code === 'ENOENT') {\n\t\t\t\t\t// File doesn't exist yet - start fresh\n\t\t\t\t\tcurrentData = {} as T;\n\t\t\t\t} else if (err instanceof SyntaxError) {\n\t\t\t\t\t// Check if it's just an empty file (our ensureFileExists writes '{}')\n\t\t\t\t\tconst content = await fs.readFile(filePath, 'utf-8').catch(() => '');\n\t\t\t\t\tif (content.trim() === '' || content.trim() === '{}') {\n\t\t\t\t\t\tcurrentData = {} as T;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Actual JSON corruption - this is a serious error\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t`Corrupted JSON in ${filePath}: ${err.message}. File contains: ${content.substring(0, 100)}...`\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Other errors (permission, I/O) should be surfaced\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Failed to read ${filePath} for modification: ${err.message}`\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply modification\n\t\t\tconst newData = await modifier(currentData);\n\n\t\t\t// Write atomically using steno (same pattern as workflow-state-manager)\n\t\t\tconst content = JSON.stringify(newData, null, 2);\n\t\t\tconst writer = this.getWriter(filePath);\n\t\t\tawait writer.write(content);\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\ttry {\n\t\t\t\t\tawait release();\n\t\t\t\t} catch (err: any) {\n\t\t\t\t\t// Log but don't throw - lock may have been released already","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/eyaltoledano/claude-task-master/blob/c0c98d367c55296bfe69e65680625b6db437af02/packages/tm-core/src/modules/storage/adapters/file-storage/file-operations.ts#L133-L169","documentation":"modifyJson() re-reads the target file inside the cross-process lock; if the read fails with anything other than ENOENT or a SyntaxError (i.e. a permission or I/O error), it throws 'Failed to read <path> for modification: <reason>'. The modification is intentionally aborted before applying the modifier so a broken read can never cause a lost update.","triggerScenarios":"saveTasks, createTag, deleteTag, renameTag, or any modifyJson call where fs.readFile fails with EACCES, EISDIR, EIO, EBUSY, or a similar OS-level error on the lock target file.","commonSituations":"Permissions changed on .taskmaster files between reads and writes (e.g. chown by another user); the file was replaced by a directory; filesystem errors in Docker/NFS mounts; antivirus or backup tools holding the file on Windows.","solutions":["Read the OS error in the message (EACCES/EISDIR/etc.) and correct it — grant read permission or remove a directory that replaced the file.","Verify the path passed to the storage layer is the intended file, not a directory or symlink to an unreadable target.","If in a container, ensure the volume mount is healthy and owned by the process user (match UID/GID).","Retry the operation after resolving the transient I/O condition; the cross-process lock was already released."],"exampleFix":"// before\nls -ld .taskmaster/tasks.json  # drwxr-xr-x (a directory!)\n// after\nrm -rf .taskmaster/tasks.json && echo '{}' > .taskmaster/tasks.json","handlingStrategy":"try-catch","validationCode":"import { access, constants, stat } from 'fs/promises';\nexport async function isModifiableFile(filePath: string): Promise<boolean> {\n  try {\n    const s = await stat(filePath);\n    if (!s.isFile()) return false;\n    await access(filePath, constants.R_OK | constants.W_OK);\n    return true;\n  } catch { return false; }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await fileOps.modifyJson(filePath, (data) => mutate(data));\n} catch (err: any) {\n  if (err.message.includes('for modification')) {\n    console.error(`Fix filesystem issue on ${filePath}: ${err.message}`);\n    throw err; // do not proceed — modification was aborted to avoid lost updates\n  }\n  throw err;\n}","preventionTips":["Keep permissions stable on .taskmaster files (don't chown to root in shared environments).","Mount volumes with correct UID/GID ownership in containers.","Preflight stat+access checks before batch tag operations.","Replace stale lock targets that were swapped for directories or symlinks."],"tags":["filesystem","io","file-storage","permissions"],"backgroundTag":"file-read-failed","analyzedSha":"c0c98d367c55296bfe69e65680625b6db437af02","analyzedAt":"2026-08-29T02:56:26.071Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}