Skip to content

System-prompt sync writes undecoded \" into the generated .md, so the markdown shows JS escape syntax and --apply embeds a spurious backslash #921

Description

@StreamDemon

~/.tweakcc/system-prompts/data-claude-api-reference-csharp.md contains 66 occurrences of the two-character text \" and zero bare " characters:

…`Betas = [\"code-execution-2025-08-25\", \"skills-2025-10-02\"]`…

Those backslashes are JavaScript string-literal syntax from the bundle. They are not part of the prompt. Sync copies them into the markdown instead of decoding them, so anyone opening that file to customise it sees escape syntax rather than the text Claude Code actually receives.

28 of the 610 files generated for Claude Code 2.1.220 are affected.

The same decode gap shows up in a smaller variant: skill-artifact-whiteboard.md and skill-artifact-data-table.md carry literal \uXXXX text for the same reason. A fix should cover both spellings rather than quotes alone.

What it does on the way back

Because the markdown holds \" as content, the backslash-preserving pass from #664 doubles it, and the quote-escaping pass then escapes the quote:

vanilla : `Betas = [\"code-execution-2025-08-25\", …
patched : `Betas = [\\\"code-execution-2025-08-25\\\", …

Measured inside that one prompt: \" 66 to 0, \\" 0 to 66. Claude Code ends up reading \" where the bundle originally had ".

The output is valid JavaScript, so node --check, boot and -p all pass. #908's parse-gate cannot see it.

Relationship to #660

#660 reported data-http-error-codes-reference and skill-stuck-slash-command breaking JS through literal \' in their content, and was closed. Both of those files are still in the affected set here.

What changed is the symptom, not the cause. #664 began doubling backslashes for " and ' delimiters, which turns the previously-invalid escape into valid JavaScript. The crash went away. The spurious backslash did not.

So this is the same root cause #660 was pointing at, still live, now silent instead of loud.

Why it is separate from the \uXXXX issue

There is a related defect where --apply corrupts \uXXXX escapes, filed as #920. The two look identical in a diff and are not the same bug:

#920 this issue
markdown contains the real character (em dash) the escape text \"
introduced by apply, after sync was correct sync, when writing the .md
fix location src/patches/systemPrompts.ts markdown generation in src/systemPromptSync.ts

The discriminator is just reading the generated .md: a real character means apply broke it, escape text means sync never decoded it. Fixing #920 leaves these 28 files wrong.

Reproducing

node dist/index.mjs --apply -y
cd ~/.tweakcc/system-prompts

# escape text where the prompt should just have a quote
grep -oP '(?<!\\)\\"' data-claude-api-reference-csharp.md | wc -l   # 66
grep -oP '(?<!\\)"'   data-claude-api-reference-csharp.md | wc -l   # 0

# how many generated files carry undecoded quote escapes
grep -lP '(?<!\\)\\"' *.md | wc -l                                 # 28

grep -P is needed for the lookbehind; grep -c would count lines rather than occurrences.

Suggested direction

Decode JavaScript string escapes when generating the markdown, so the file holds " and the user edits the text Claude Code actually sees.

The constraint to respect: buildSearchRegexFromPieces matches literal, \uXXXX and \xHH spellings when searching the bundle, and the stored pieces must keep their raw escapes for that to work. Only the markdown rendering should decode. Round-tripping an unedited prompt must stay byte-identical, which is the natural gate for this change.

Environment

  • tweakcc 48ac856 (markdown generation has no decode step in released v4.3.2 either)
  • Claude Code 2.1.220, native Linux install
  • Node 24

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions