{"record":{"id":"f802f3d748c236ac","repo":"mastra-ai/mastra","slug":"estale","errorCode":"ESTALE","errorMessage":"File was modified externally: ${path} (expected mtime ${expectedMtime.toISOString()}, actual ${actualMtime.toISOString()})","messagePattern":"File was modified externally: (.+?) \\(expected mtime (.+?), actual (.+?)\\)","errorType":"exception","errorClass":"StaleFileError","httpStatus":null,"severity":"warning","filePath":"packages/core/src/workspace/filesystem/local-filesystem.ts","lineNumber":448,"sourceCode":"          throw new DirectoryNotFoundError(parentPath);\n        }\n        throw error;\n      }\n    }\n\n    if (options?.recursive !== false) {\n      const dir = nodePath.dirname(absolutePath);\n      await fs.mkdir(dir, { recursive: true });\n    }\n\n    // Optimistic concurrency: reject if file was modified since caller last read it\n    if (options?.expectedMtime) {\n      try {\n        const currentStat = await fs.stat(absolutePath);\n        // Compare via Date objects — Node's stats.mtime applies internal\n        // rounding that can diverge from Math.floor(stats.mtimeMs).\n        if (currentStat.mtime.getTime() !== options.expectedMtime.getTime()) {\n          throw new StaleFileError(inputPath, options.expectedMtime, currentStat.mtime);\n        }\n      } catch (error: unknown) {\n        if (error instanceof StaleFileError) throw error;\n        // File doesn't exist yet — no conflict possible, proceed with write\n        if (!isEnoentError(error)) throw error;\n      }\n    }\n\n    // Use 'wx' flag for atomic overwrite check (avoids TOCTOU race)\n    const writeFlag = options?.overwrite === false ? 'wx' : 'w';\n    try {\n      await fs.writeFile(absolutePath, this.toBuffer(content), { flag: writeFlag });\n    } catch (error: unknown) {\n      if (options?.overwrite === false && isEexistError(error)) {\n        throw new FileExistsError(inputPath);\n      }\n      throw error;\n    }","sourceCodeStart":430,"sourceCodeEnd":466,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/workspace/filesystem/local-filesystem.ts#L430-L466","documentation":"writeFile throws StaleFileError (code ESTALE) when options.expectedMtime is provided and the file's current modification time differs from the expected value. This is optimistic concurrency control: it detects that the file was modified by someone else between your read and your write, preventing silent lost updates. If the file does not exist, no conflict is possible and the write proceeds.","triggerScenarios":"Two agents/processes edit the same file concurrently; writeFile carries an expectedMtime captured from an earlier stat/read but another writer committed first; clock/filesystem rounding causes mtime mismatch on filesystems with coarse timestamps; caller reuses a stale mtime from a cached FileStat.","commonSituations":"Multi-agent workflows where several agents write shared state files; a human editor saved the file while an agent run was in flight; long-running job re-tries a write with an old expectedMtime after the file legitimately changed.","solutions":["Re-read the file (getting fresh content and mtime via stat), merge/re-apply your change, and retry the write with the new expectedMtime.","If last-writer-wins is acceptable, omit expectedMtime entirely.","Use the file-write-lock utilities (file-write-lock.ts) or an external lock to serialize writers.","Catch StaleFileError and surface a conflict so the calling agent can re-base instead of overwriting."],"exampleFix":"// before\nawait fs.writeFile('state.json', next, { expectedMtime: oldMtime }); // StaleFileError if changed\n// after\nlet success = false;\nwhile (!success) {\n  const { content, mtime } = await readWithMtime('state.json');\n  try {\n    await fs.writeFile('state.json', merge(content, next), { expectedMtime: mtime });\n    success = true;\n  } catch (e) {\n    if (!(e instanceof StaleFileError)) throw e;\n  }\n}","handlingStrategy":"retry","validationCode":"const s = await ws.stat(p);\n// use s.modifiedAt (fresh) as expectedMtime immediately before writing;\n// if the file changed since your read, re-read and merge first","typeGuard":"import { StaleFileError } from '@mastra/core/workspace/errors';\nfunction isStaleFileError(e: unknown): e is StaleFileError {\n  return e instanceof StaleFileError ||\n    (e instanceof Error && 'code' in e && (e as { code?: string }).code === 'ESTALE');\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  const s = await ws.stat(p);\n  const fresh = await ws.readFile(p, { encoding: 'utf8' });\n  try {\n    await ws.writeFile(p, merge(fresh, myChange), { expectedMtime: s.modifiedAt });\n    break;\n  } catch (e) {\n    if (!isStaleFileError(e) || attempt === 2) throw e;\n  }\n}","preventionTips":["Capture expectedMtime as late as possible — immediately before the write.","Keep the read-modify-write window short; re-read inside the same step that writes.","Serialize writers with the file-write-lock utilities or an external lock.","Only use expectedMtime when lost updates matter; otherwise omit it."],"tags":["filesystem","concurrency","optimistic-locking","estale"],"backgroundTag":"stale-write-conflict","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}