can1357/oh-my-pi · error
sharpshooter consolidation must call replace_memory_files ex
Error message
sharpshooter consolidation must call replace_memory_files exactly once
What it means
parseReplacementFiles enforces the consolidation model's tool-use contract: the response content must contain exactly one tool call and it must be the replace_memory_files tool. Anything else — zero calls, multiple calls, or a different tool — throws this error, protecting the memory bank from ambiguous or malformed consolidation output.
Source
Thrown at packages/coding-agent/src/sharpshooter/consolidate.ts:235
for (const name of ["AGENTS.md", "CLAUDE.md"]) {
const content = await Bun.file(path.join(cwd, name))
.text()
.catch(() => "");
if (content.trim()) blocks.push(`--- ${name} ---\n${content.trim()}`);
}
return truncateApproxTokens(blocks.join("\n\n"), PROJECT_DOC_TOKEN_LIMIT);
}
function parseReplacementFiles(
content: readonly unknown[],
currentFiles: Readonly<Record<SharpshooterMemoryFile, string>>,
): 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");
}View on GitHub (pinned to 9690622007)
Solutions
- Re-run the consolidation, ideally with a stronger model or with toolChoice:'required' enforced.
- Check the consolidation prompt/tool schema so the model is clearly instructed to call replace_memory_files exactly once.
- Inspect the raw response content (log it) to see what the model actually emitted and adjust the prompt.
- Switch to a provider/model known to honor tool-choice reliably for this step.
- Add retry-around-parse logic that re-prompts when the contract is violated.
Example fix
// before
// model replied with prose, no tool call -> throws
// after
// enforce tool selection
...complete({ ..., toolChoice: "required", tools: [replaceMemoryFilesTool] }) Defensive patterns
Strategy: validation
Validate before calling
const toolCalls = response.content.filter(b => b?.type === "toolCall");
if (toolCalls.length !== 1 || toolCalls[0].name !== "replace_memory_files") {
// re-prompt or reject before calling parseReplacementFiles
}
Type guard
function isSingleReplaceCall(content: unknown[]): content is [{ type: "toolCall"; name: "replace_memory_files"; arguments: unknown }] {
const calls = content.filter((b): b is { type: "toolCall"; name: string } =>
typeof b === "object" && b !== null && "type" in b && (b as { type: string }).type === "toolCall");
return calls.length === 1 && calls[0].name === "replace_memory_files";
} Try / catch
try {
const files = parseReplacementFiles(response.content, currentFiles);
} catch (err) {
if (err instanceof Error && err.message.includes("must call replace_memory_files exactly once")) {
// re-run consolidation with toolChoice:'required' or log response for prompt tuning
} else throw err;
} Prevention
- Always pass toolChoice:'required' with only replace_memory_files in the tools list for consolidation.
- Log raw model output on parse failure to tune the prompt.
- Prefer models/providers with reliable forced tool-use for this step.
- Retry the consolidation automatically when the tool contract is violated.
When it happens
Trigger: The consolidation model returns no tool call (plain text answer), multiple tool calls, or calls a different tool instead of replace_memory_files; also occurs when toolChoice:'required' is not honored by the provider/model.
Common situations: Using a model/provider that poorly supports forced tool choice; prompt drift causing the model to answer in prose; a model emitting parallel tool calls; provider quirk returning toolCall blocks with unexpected names after a schema change.
Related errors
- Soft tool requirement '${softRequiredTool}' was not satisfie
- Tool ${toolCall.name} not found
- Tool "${toolCall.name}" not found
- Validation failed for tool "${toolCall.name}": Tool call arg
- Validation failed for tool "${toolCall.name}":\n${errors}\n\
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/1e03a73072ffd201.
Report an issue: GitHub.