{"record":{"id":"31cc35da97e54604","repo":"can1357/oh-my-pi","slug":"replace-memory-files-requires-a-files-array","errorCode":null,"errorMessage":"replace_memory_files requires a files array","messagePattern":"replace_memory_files requires a files array","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/sharpshooter/consolidate.ts","lineNumber":240,"sourceCode":"\t}\n\treturn truncateApproxTokens(blocks.join(\"\\n\\n\"), PROJECT_DOC_TOKEN_LIMIT);\n}\n\nfunction parseReplacementFiles(\n\tcontent: readonly unknown[],\n\tcurrentFiles: Readonly<Record<SharpshooterMemoryFile, string>>,\n): ReplacementFile[] {\n\tconst toolCalls = content.filter(\n\t\t(block): block is { type: \"toolCall\"; name: string; arguments: unknown } =>\n\t\t\ttypeof block === \"object\" && block !== null && \"type\" in block && block.type === \"toolCall\",\n\t);\n\tif (toolCalls.length !== 1 || toolCalls[0]?.name !== replaceMemoryFilesTool.name) {\n\t\tthrow new Error(\"sharpshooter consolidation must call replace_memory_files exactly once\");\n\t}\n\n\tconst args = toolCalls[0].arguments;\n\tif (!args || typeof args !== \"object\" || !(\"files\" in args) || !Array.isArray(args.files)) {\n\t\tthrow new Error(\"replace_memory_files requires a files array\");\n\t}\n\n\tconst seen = new Set<SharpshooterMemoryFile>();\n\tconst files: ReplacementFile[] = [];\n\tfor (const item of args.files) {\n\t\tif (!item || typeof item !== \"object\" || !(\"name\" in item) || !(\"content\" in item)) {\n\t\t\tthrow new Error(\"replace_memory_files contains an invalid file entry\");\n\t\t}\n\t\tconst name = item.name;\n\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++) {","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/sharpshooter/consolidate.ts#L222-L258","documentation":"parseReplacementFiles validates the arguments of the single `replace_memory_files` tool call that sharpshooter consolidation requires. This error is thrown when the call's arguments are missing, not a plain object, or lack an array-valued `files` field. It guards the downstream per-entry validation loop which assumes `args.files` is an array.","triggerScenarios":"The LLM calls `replace_memory_files` with no arguments object, with `files` absent, or with `files` set to a non-array value (e.g. an object keyed by filename, or a string).","commonSituations":"Model schema drift or weak tool-calling models emitting malformed arguments; hand-crafted or replayed tool calls in tests; a prompt change causing the model to pass files as a map instead of an array.","solutions":["Ensure the model call forces a strict tool schema so `files` is always an array (toolChoice required plus a JSON-schema-typed tool definition).","Check the raw tool-call arguments in the response to see what shape was actually emitted.","Retry the consolidation run; malformed arguments from an LLM are usually transient.","If integrating programmatically, pass `{ files: [{ name, content }, ...] }` with `files` as an array."],"exampleFix":"// before: args shaped as an object map\nreplace_memory_files({ \"AGENTS.md\": \"...\" })\n// after: files must be an array of {name, content}\nreplace_memory_files({ files: [{ name: \"AGENTS.md\", content: \"...\" }] })","handlingStrategy":"type-guard","validationCode":"function hasFilesArray(args: unknown): args is { files: unknown[] } {\n  return !!args && typeof args === \"object\" && \"files\" in args && Array.isArray((args as { files?: unknown }).files);\n}","typeGuard":"const isFilesArgs = (a: unknown): a is { files: Array<{ name: string; content: string }> } =>\n  typeof a === \"object\" && a !== null && Array.isArray((a as any).files);","tryCatchPattern":"try {\n  const files = parseReplacementFiles(toolCall, currentFiles);\n} catch (err) {\n  logger.warn(\"invalid replace_memory_files arguments; skipping consolidation\", { err });\n}","preventionTips":["Enforce a JSON schema on the tool definition so `files` must be an array.","Include a usage example in the tool description.","Validate model output before acting on it."],"tags":["validation","tool-calling","schema"],"backgroundTag":"tool-arguments-schema-violation","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}