{"record":{"id":"5931562cdc327bda","repo":"can1357/oh-my-pi","slug":"replace-memory-files-returned-all-empty-content-r","errorCode":null,"errorMessage":"replace_memory_files returned all-empty content; refusing to wipe memory files","messagePattern":"replace_memory_files returned all-empty content; refusing to wipe memory files","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/sharpshooter/consolidate.ts","lineNumber":268,"sourceCode":"\t\tconst rawContent = item.content;\n\t\tif (!isMemoryFileName(name) || typeof rawContent !== \"string\") {\n\t\t\tthrow new Error(\"replace_memory_files contains an invalid file entry\");\n\t\t}\n\t\tif (seen.has(name)) throw new Error(`replace_memory_files contains duplicate ${name}`);\n\t\tseen.add(name);\n\t\tconst redacted = redactSecrets(rawContent);\n\t\tlet lines = redacted.length > 0 ? 1 : 0;\n\t\tfor (let index = 0; index + 1 < redacted.length; index++) {\n\t\t\tif (redacted.charCodeAt(index) === 10) lines += 1;\n\t\t}\n\t\tif (lines > SHARPSHOOTER_MAX_FILE_LINES) {\n\t\t\tthrow new Error(`${name} exceeds the ${SHARPSHOOTER_MAX_FILE_LINES}-line limit`);\n\t\t}\n\t\tfiles.push({ name, content: redacted });\n\t}\n\tconst totalChars = files.reduce((sum, file) => sum + file.content.trim().length, 0);\n\tif (totalChars === 0 && SHARPSHOOTER_MEMORY_FILES.some(name => currentFiles[name].trim().length > 0)) {\n\t\tthrow new Error(\"replace_memory_files returned all-empty content; refusing to wipe memory files\");\n\t}\n\treturn files;\n}\n\nfunction isMemoryFileName(value: unknown): value is SharpshooterMemoryFile {\n\treturn typeof value === \"string\" && (SHARPSHOOTER_MEMORY_FILES as readonly string[]).includes(value);\n}\n\nasync function applyReplacementFiles(bankDir: string, files: readonly ReplacementFile[]): Promise<void> {\n\tconst staged = files.map(file => ({\n\t\t...file,\n\t\ttempPath: path.join(bankDir, `.${file.name}.${process.pid}.${crypto.randomUUID()}.tmp`),\n\t}));\n\ttry {\n\t\tawait Promise.all(staged.map(file => Bun.write(file.tempPath, file.content)));\n\t\tfor (const file of staged) await fs.rename(file.tempPath, path.join(bankDir, file.name));\n\t} finally {\n\t\tawait Promise.all(staged.map(file => fs.rm(file.tempPath, { force: true }).catch(() => {})));","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/sharpshooter/consolidate.ts#L250-L286","documentation":"A safety guard against data loss: if every replacement file's content is empty/whitespace while at least one current memory file has non-empty content, consolidation is refused so the model cannot accidentally wipe accumulated memory. Thrown after all per-file validation passes but the aggregate total trimmed character count is zero.","triggerScenarios":"The model calls `replace_memory_files` with `\"\"` or whitespace-only `content` for every file during consolidation.","commonSituations":"Model deciding memory is stale and emitting empty files; truncation bugs producing empty strings; prompt confusion about how to clear memory.","solutions":["Re-run consolidation so the model produces real summaries.","If clearing memory is genuinely intended, clear files via the normal file tools rather than replace_memory_files.","Check the upstream model response for truncation that emptied the content fields.","Ensure the prompt instructs the model to always summarize, never blank, memory files."],"exampleFix":"// before\n{ files: [{ name: \"AGENTS.md\", content: \"\" }] }\n// after\n{ files: [{ name: \"AGENTS.md\", content: \"- project: TS monorepo\\n- test: bun test\" }] }","handlingStrategy":"validation","validationCode":"const nonEmpty = files.some(f => f.content.trim().length > 0);\nif (!nonEmpty && Object.values(currentFiles).some(c => c.trim().length > 0)) {\n  throw new Error(\"refusing to wipe non-empty memory\");\n}","typeGuard":"null","tryCatchPattern":"try {\n  await consolidate();\n} catch (err) {\n  if (String(err).includes(\"all-empty content\")) {\n    logger.warn(\"consolidation produced empty memory; preserving existing files\");\n  }\n}","preventionTips":["Prompt the model to always write a meaningful summary, never blanks.","Confirm truncation isn't emptying streamed content fields.","Use explicit file-management tools (not replace_memory_files) when clearing memory intentionally."],"tags":["validation","data-loss-guard","tool-calling"],"backgroundTag":"destructive-operation-blocked","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}