{"record":{"id":"6f8091e9ff8accaf","repo":"mastra-ai/mastra","slug":"eread-required","errorCode":"EREAD_REQUIRED","errorMessage":"${reason}","messagePattern":"\\$\\{reason\\}","errorType":"error_code","errorClass":"FileReadRequiredError","httpStatus":null,"severity":"error","filePath":"packages/core/src/workspace/tools/tools.ts","lineNumber":297,"sourceCode":"          enrichedContext = { ...enrichedContext, __expectedMtime: record.modifiedAtRead };\n        }\n\n        try {\n          const stat = await fs.stat(input.path);\n\n          // Policy gate: require the agent to have read the file first.\n          // Only evaluate when explicitly configured (opt-in policy).\n          // Safe default true = fail-closed if a dynamic function throws.\n          if (config.requireReadBeforeWrite !== undefined) {\n            const shouldRequireRead = await resolveDynamicValue(\n              config.requireReadBeforeWrite,\n              { args: input, requestContext: enrichedContext.requestContext ?? {}, workspace: effectiveWorkspace },\n              true,\n            );\n            if (shouldRequireRead) {\n              const check = readTracker.needsReRead(input.path, stat.modifiedAt);\n              if (check.needsReRead) {\n                throw new FileReadRequiredError(input.path, check.reason!);\n              }\n            }\n          }\n        } catch (error) {\n          if (!(error instanceof FileNotFoundError)) {\n            throw error;\n          }\n          // Missing file: if a read record exists the expectedMtime is\n          // already attached, so downstream writeFile can treat this as\n          // stale. Otherwise it's a genuinely new file.\n        }\n      }\n\n      const result = await tool.execute(input, enrichedContext);\n\n      // Post-execution: track reads / clear write records\n      if (mode === 'read' && fs) {\n        try {","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/workspace/tools/tools.ts#L279-L315","documentation":"To keep the model's context accurate, a read-tracker records the last-read time of files. If a write tool targets a file whose content changed on disk after the agent last read it (or was never read), `readTracker.needsReRead` returns a reason and the tool throws `FileReadRequiredError` with that reason. This forces a fresh read before overwriting, preventing lost-update bugs.","triggerScenarios":"Calling a write/mutate tool on `input.path` where the file's `stat.modifiedAt` is newer than the tracked read time, or where the file was never read in this session (`needsReRead` true with a reason such as 'file not read' or 'modified since last read').","commonSituations":"External processes (linter/formatter, git checkout, another agent) modify a file between the agent's read and write; agent session resumes with stale read history; user edits a file while the agent works on it.","solutions":["Call the readFile tool on the path first, then retry the write so the tracker records a fresh read","If the modification is expected/external, re-read and re-apply changes based on current content","Structure agent workflows as read-then-write per file instead of blind writes"],"exampleFix":"// before\nawait writeFile({ path: 'config.ts', content: newContent }); // EREAD_REQUIRED\n\n// after\nconst current = await readFile({ path: 'config.ts' });\nawait writeFile({ path: 'config.ts', content: merge(current, newContent) });","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await writeTool.execute({ path, content }, ctx);\n} catch (err) {\n  if (err?.code === 'EREAD_REQUIRED') {\n    const current = await readFileTool.execute({ path }, ctx);\n    return await writeTool.execute({ path, content: mergeWithCurrent(current, content) }, ctx);\n  }\n  throw err;\n}","preventionTips":["Always read a file via the readFile tool before writing it in the same session","Re-read files after any external modification (git ops, formatters, other agents)","Design agent loops so write steps follow a fresh read rather than cached content from earlier turns"],"tags":["read-tracker","stale-read","conflict","workspace-tools"],"backgroundTag":"stale-read-conflict","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}