can1357/oh-my-pi · error

replace_memory_files returned all-empty content; refusing to

Error message

replace_memory_files returned all-empty content; refusing to wipe memory files

What it means

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.

Source

Thrown at packages/coding-agent/src/sharpshooter/consolidate.ts:268

		const rawContent = item.content;
		if (!isMemoryFileName(name) || typeof rawContent !== "string") {
			throw new Error("replace_memory_files contains an invalid file entry");
		}
		if (seen.has(name)) throw new Error(`replace_memory_files contains duplicate ${name}`);
		seen.add(name);
		const redacted = redactSecrets(rawContent);
		let lines = redacted.length > 0 ? 1 : 0;
		for (let index = 0; index + 1 < redacted.length; index++) {
			if (redacted.charCodeAt(index) === 10) lines += 1;
		}
		if (lines > SHARPSHOOTER_MAX_FILE_LINES) {
			throw new Error(`${name} exceeds the ${SHARPSHOOTER_MAX_FILE_LINES}-line limit`);
		}
		files.push({ name, content: redacted });
	}
	const totalChars = files.reduce((sum, file) => sum + file.content.trim().length, 0);
	if (totalChars === 0 && SHARPSHOOTER_MEMORY_FILES.some(name => currentFiles[name].trim().length > 0)) {
		throw new Error("replace_memory_files returned all-empty content; refusing to wipe memory files");
	}
	return files;
}

function isMemoryFileName(value: unknown): value is SharpshooterMemoryFile {
	return typeof value === "string" && (SHARPSHOOTER_MEMORY_FILES as readonly string[]).includes(value);
}

async function applyReplacementFiles(bankDir: string, files: readonly ReplacementFile[]): Promise<void> {
	const staged = files.map(file => ({
		...file,
		tempPath: path.join(bankDir, `.${file.name}.${process.pid}.${crypto.randomUUID()}.tmp`),
	}));
	try {
		await Promise.all(staged.map(file => Bun.write(file.tempPath, file.content)));
		for (const file of staged) await fs.rename(file.tempPath, path.join(bankDir, file.name));
	} finally {
		await Promise.all(staged.map(file => fs.rm(file.tempPath, { force: true }).catch(() => {})));

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-run consolidation so the model produces real summaries.
  2. If clearing memory is genuinely intended, clear files via the normal file tools rather than replace_memory_files.
  3. Check the upstream model response for truncation that emptied the content fields.
  4. Ensure the prompt instructs the model to always summarize, never blank, memory files.

Example fix

// before
{ files: [{ name: "AGENTS.md", content: "" }] }
// after
{ files: [{ name: "AGENTS.md", content: "- project: TS monorepo\n- test: bun test" }] }
Defensive patterns

Strategy: validation

Validate before calling

const nonEmpty = files.some(f => f.content.trim().length > 0);
if (!nonEmpty && Object.values(currentFiles).some(c => c.trim().length > 0)) {
  throw new Error("refusing to wipe non-empty memory");
}

Type guard

null

Try / catch

try {
  await consolidate();
} catch (err) {
  if (String(err).includes("all-empty content")) {
    logger.warn("consolidation produced empty memory; preserving existing files");
  }
}

Prevention

When it happens

Trigger: The model calls `replace_memory_files` with `""` or whitespace-only `content` for every file during consolidation.

Common situations: Model deciding memory is stale and emitting empty files; truncation bugs producing empty strings; prompt confusion about how to clear memory.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/5931562cdc327bda. Report an issue: GitHub.