Memory-file Write/Edit destroys all frontmatter when YAML parse fails (originSessionId stamper writes empty stub instead of rejecting)

Open 💬 0 comments Opened Jul 12, 2026 by StotheC90

Summary

When a Markdown file under the auto-memory directory is written via the Write or Edit tool and its YAML frontmatter fails to parse, Claude Code does not reject the write or leave the content untouched — it replaces the entire frontmatter with an empty stub, destroying description, type, status, tags, related, and every other field. Only the harness-injected fields survive:

---
name: ""
metadata: 
  node_type: memory
  originSessionId: <current session id>
---

The body is preserved. The file never contains the content the model wrote — the transformation happens synchronously inside the Write/Edit tool before the disk write, so a follow-up Edit against the intended content fails with "String to replace not found", and the memory becomes invisible to recall (name: "").

This is the destructive sibling of #61099 / #74666 (same frontmatter re-serialization path, but total field loss instead of lossy rewrite).

Environment

  • Claude Code 2.1.207 (native binary), macOS (Darwin 25.5.0, arm64)
  • Auto-memory directory: ~/.claude/projects/<scope>/memory/

Reproduction

Ask Claude to write this file to <auto-memory-dir>/feedback/repro.md via the Write tool:

---
name: repro
description: "contains a left double quotation mark “ (U+201C)"
metadata:
  type: feedback
  status: active
---

Body text.

Result on disk (reproduced 10/10 on 2.1.207):

---
name: ""
metadata: 
  node_type: memory
  originSessionId: 00000000-0000-0000-0000-000000000000 (redacted)
---

Body text.

The same happens with a pure-ASCII trigger — an unescaped inner quote in a quoted value (genuinely invalid YAML):

description: "abc "def" ghi"

Root cause (from observed behavior + binary inspection)

The originSessionId stamper runs on every memory-file Write/Edit whose frontmatter lacks originSessionId: parse frontmatter → re-serialize as {name, description?, metadata:{node_type, ...}} → inject originSessionId. The parse path (Bun.YAML.parse with one quote-repair retry) swallows failures and continues with an empty frontmatter object (only a warn-level log "Failed to parse YAML frontmatter"). The serializer then emits name: "" (empty-string fallback), drops description (null filter), and writes metadata containing only the injected fields.

Character-level finding: the parse failure for the first repro is triggered specifically by U+201C (“) inside a double-quoted scalar — which is spec-valid YAML 1.2 (PyYAML and eemeli/yaml parse it fine). U+201E („), U+201D (”), U+2019 (’), em-dashes and umlauts do not trigger it. This looks like a Bun.YAML lexer quirk treating U+201C as a quote delimiter; German-style „…“ quoting dies on its closing character. You may want to forward that part to Bun.

Two secondary observations:

  1. The memory-dir check compares normalized path prefixes without resolving symlinks — if projects/<scope>/memory is a symlink, writes addressed via the symlink target path bypass stamping entirely, so stamping/clobbering behavior depends on which alias of the same file is used.
  2. Because the stamper skips files that already contain originSessionId, re-writing the destroyed frontmatter only survives if the model includes the field itself — without knowing this, models loop through failing repairs.

Expected behavior

On frontmatter parse failure, the stamper should either (a) return the content unchanged (skip stamping), or (b) reject the write with a visible error. Silently substituting derived-empty frontmatter turns a YAML quirk into unrecoverable data loss inside the user's persistent memory.

Impact

  • Silent destruction of user memory metadata (description/tags/relations/status)
  • Model-side confusion: file state diverges from what the tool reported as written; subsequent Edits fail
  • Clobbered memories are excluded from recall (name: ""), so the loss is invisible until manually discovered

View original on GitHub ↗