can1357/oh-my-pi · error

replace_memory_files contains an invalid file entry

Error message

replace_memory_files contains an invalid file entry

What it means

Each entry of the `files` array in a `replace_memory_files` call must be a non-null object containing both `name` and `content`, the name must be one of the known sharpshooter memory filenames, and content must be a string. This error is thrown when either the structural check (missing name/content, null/primitive entry) or the value check (unknown name or non-string content) fails.

Source

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

): ReplacementFile[] {
	const toolCalls = content.filter(
		(block): block is { type: "toolCall"; name: string; arguments: unknown } =>
			typeof block === "object" && block !== null && "type" in block && block.type === "toolCall",
	);
	if (toolCalls.length !== 1 || toolCalls[0]?.name !== replaceMemoryFilesTool.name) {
		throw new Error("sharpshooter consolidation must call replace_memory_files exactly once");
	}

	const args = toolCalls[0].arguments;
	if (!args || typeof args !== "object" || !("files" in args) || !Array.isArray(args.files)) {
		throw new Error("replace_memory_files requires a files array");
	}

	const seen = new Set<SharpshooterMemoryFile>();
	const files: ReplacementFile[] = [];
	for (const item of args.files) {
		if (!item || typeof item !== "object" || !("name" in item) || !("content" in item)) {
			throw new Error("replace_memory_files contains an invalid file entry");
		}
		const name = item.name;
		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 });
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Use exactly the allowed memory file names for `name` (check SHARPSHOOTER_MEMORY_FILES / the tool description).
  2. Ensure every entry has both `name` (string) and `content` (string) keys.
  3. Regenerate the tool call with a stricter schema (enum on name, type string on content).
  4. Retry the run — this is model output, not caller input.

Example fix

// before
{ files: [{ name: "notes.md", content: 42 }] }
// after: allowed name, string content
{ files: [{ name: "AGENTS.md", content: "..." }] }
Defensive patterns

Strategy: validation

Validate before calling

const valid = files.every(f => f && typeof f === "object" && "name" in f && "content" in f);

Type guard

const isReplacementFile = (x: unknown): x is { name: unknown; content: unknown } =>
  typeof x === "object" && x !== null && "name" in x && "content" in x;

Try / catch

try {
  return parseReplacementFiles(call, currentFiles);
} catch (err) {
  if (err instanceof Error && err.message.includes("invalid file entry")) {
    return retryConsolidation();
  }
  throw err;
}

Prevention

When it happens

Trigger: An entry in `args.files` is null, a primitive, or missing `name`/`content` keys; or `name` is not one of SHARPSHOOTER_MEMORY_FILES; or `content` is a number/boolean/object instead of a string.

Common situations: Model inventing a new memory filename not in the allowed set; hallucinated entries with truncated or typed content (e.g. numeric line counts); null entries from malformed JSON streaming.

Related errors


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