{"record":{"id":"cd951ae6d500c5fb","repo":"can1357/oh-my-pi","slug":"lesson-was-empty-after-sanitization-nothing-store","errorCode":null,"errorMessage":"Lesson was empty after sanitization; nothing stored.","messagePattern":"Lesson was empty after sanitization; nothing stored\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/learn.ts","lineNumber":87,"sourceCode":"\t\t\t\tscope: \"bank\",\n\t\t\t\textract: true,\n\t\t\t\textractEntities: true,\n\t\t\t\tveracity: \"tool\",\n\t\t\t\tmemoryType: \"fact\",\n\t\t\t});\n\t\t\t// rememberScoped returns undefined when the retain failed (closed DB /\n\t\t\t// disk error); mirror mnemopiBackend.save and fail loudly rather than\n\t\t\t// reporting (and minting a skill for) a lesson that was silently dropped.\n\t\t\tif (!id) {\n\t\t\t\tthrow new Error(\"Mnemopi did not store the lesson (no memory id returned).\");\n\t\t\t}\n\t\t} else if (backend === \"local\") {\n\t\t\tconst result = await localBackend.save?.(\n\t\t\t\t{ agentDir: this.session.settings.getAgentDir(), cwd: this.session.settings.getCwd() },\n\t\t\t\t{ content: params.memory, context: params.context, source: \"coding-agent-learn\", importance: 0.8 },\n\t\t\t);\n\t\t\tif (!result || result.stored === 0) {\n\t\t\t\tthrow new Error(\"Lesson was empty after sanitization; nothing stored.\");\n\t\t\t}\n\t\t} else {\n\t\t\tconst state = this.session.getHindsightSessionState?.();\n\t\t\tif (!state) {\n\t\t\t\tthrow new Error(\"Hindsight backend is not initialised for this session.\");\n\t\t\t}\n\t\t\tstate.enqueueRetain(params.memory, params.context);\n\t\t\tmemoryMessage = \"Lesson queued for retention\";\n\t\t}\n\n\t\t// 2) Optionally mint/enhance a managed skill. A failure here is surfaced\n\t\t// as a partial outcome — the lesson is already stored or queued.\n\t\tif (params.skill) {\n\t\t\t// A managed skill resolves below any authored skill of the same name, so\n\t\t\t// minting one under a claimed name writes a file that never surfaces. The\n\t\t\t// lesson is already stored/queued; refuse the skill rather than report a\n\t\t\t// false \"Created\" (mirrors ManageSkillTool).\n\t\t\tlet safeSkillName: string | undefined;","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/learn.ts#L69-L105","documentation":"With memory.backend === \"local\", learn saves the lesson via localBackend.save(), which sanitizes the content before appending to learned.md and reports how many items were stored. If save returns nothing or stored === 0, the lesson text was stripped to nothing by sanitization, so nothing was persisted and the tool throws this Error to avoid a false success.","triggerScenarios":"Calling the learn tool with the local backend when params.memory consists only of content the sanitizer removes — e.g. whitespace, control characters, or otherwise empty/invalid text after normalization.","commonSituations":"The model calls learn with an effectively empty memory string (only whitespace/newlines) or with content that sanitization reduces to an empty string.","solutions":["Re-call the learn tool with a non-empty, self-contained lesson in params.memory.","Check that the memory argument is not just whitespace or placeholder text.","If the sanitizer is over-aggressive for legitimate content, review the local-backend sanitization rules."],"exampleFix":"// before: memory is only whitespace, sanitizer stores 0 items\n{ \"memory\": \"   \\n\\t  \" }\n// after: provide an actual lesson\n{ \"memory\": \"Always run bun check before committing TypeScript changes.\" }","handlingStrategy":"validation","validationCode":"const memory = params.memory?.trim();\nif (!memory) {\n  throw new Error(\"learn requires a non-empty lesson string.\");\n}","typeGuard":"function isNonEmptyLesson(s: unknown): s is string {\n  return typeof s === \"string\" && s.trim().length > 0;\n}","tryCatchPattern":"try {\n  await learnTool.execute(id, params);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"empty after sanitization\")) {\n    // re-issue the call with real lesson text\n  } else {\n    throw err;\n  }\n}","preventionTips":["Always pass a substantive, self-contained lesson in params.memory.","Trim/validate the memory string before invoking learn.","Don't call learn with placeholder or whitespace-only content."],"tags":["memory-backend","sanitization","validation","local-backend"],"backgroundTag":"empty-input-after-sanitization","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}