Compaction summary corrupts `$` sequences in quoted code — fingerprint of an unescaped String.replace replacement pattern

Status Open
Reported on v2.1.246
Maintainer reply None cached
Activity 0 comments · opened Aug 27, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Summary

After context compaction, code quoted inside the model-written summary arrives corrupted in a very specific pattern: $$ collapses to a single $, and ` $ ` (dollar immediately followed by a closing backtick) disappears entirely, replaced by what looks like the text preceding the insertion point. Meanwhile ${...}, $name, and $(...)` survive untouched.

This is exactly the semantics of JavaScript's String.prototype.replace() replacement patterns ($$ → literal $, ` $ ` → "portion of the string preceding the match", $ before a letter/brace/paren → not special). It strongly suggests the summary text is inserted into the continuation-message template via .replace() without escaping $` in the replacement string — the model writes the code correctly, and the assembly pipeline mangles it.

Environment

  • Claude Code 2.1.246, macOS desktop app 1.37937.1, macOS (Darwin 24.6.0)
  • Observed in auto-compaction summaries (isCompactSummary: true records in the session transcript jsonl)

Evidence (three real compactions, 25–26 Aug)

  1. A fenced SQL block quoting a PostgreSQL function verbatim. On disk (migration file, intact, already applied):
... LANGUAGE plpgsql SECURITY DEFINER SET search_path = myschema AS $$
DECLARE
  ...
END;
$$;

In the compaction summary, both $$ became a single $:

... AS $
DECLARE
  ...
END; $;

AS $ does not open a dollar-quote — the summary presents syntactically invalid SQL as a verbatim quote of a migration that already ran in production.

  1. Inline code ` DO $verify$ ` (4 occurrences across 3 summaries) became:
the migration's own `DO $verify

 blocks), then closed the cap gate...

The $ + closing-backtick sequence vanished and was replaced by two newlines — cutting the sentence mid-word and unbalancing the markdown code spans that follow. This matches ` $ ` being interpreted as "preceding portion" where the portion before the placeholder was \n\n`.

  1. Unharmed in the same summaries: ${...} inside TypeScript template literals (8 occurrences), $sha$, $spec, $(grep ...) — precisely the sequences that are not special in a replacement string. That selectivity is the fingerprint.

Impact

  • Deterministic for any codebase using PostgreSQL dollar-quoting ($$, DO $tag$ ... $tag$) — i.e. essentially every Postgres/Supabase migration.
  • An agent resuming after compaction sees broken code presented as a verbatim quote of files that are intact on disk. Likely failure modes: the agent "repairs" a healthy file to match the mangled quote, or copies the mangled SQL into a new migration. (We now inject a post-compaction warning about this on our side, but the root cause is upstream.)

Suggested fix

Escape the replacement (summary.replace(/\$/g, '$$$$')) — or use the function form, template.replace(PLACEHOLDER, () => summary), which bypasses replacement-pattern interpretation entirely.

Repro sketch

  1. Run a session long enough to auto-compact, whose work involves SQL containing $$ / DO $tag$ (e.g. reviewing a Postgres migration), so the summary quotes it.
  2. Read the isCompactSummary: true record in the transcript jsonl.
  3. Observe $$$ and ` $ ` → preceding-text insertion, while ${, $word, $(` survive.

What Should Happen?

Quoted code should survive compaction byte-for-byte: the summary-assembly step must not reinterpret $ sequences in the summary text. $$ should remain $$ and ` $ ` should remain $ `, so quoted PostgreSQL dollar-quoting (AS $$ ... $$;, DO $tag$ ... $tag$;`) stays syntactically valid and matches the file on disk. Apart from the summarizer's own deliberate elisions, no character-level changes should be introduced by the assembly pipeline.

Error Messages/Logs

Steps to Reproduce

  1. Create a folder with one file, demo.sql, containing minimal dollar-quoted PostgreSQL:
CREATE OR REPLACE FUNCTION demo_fn() RETURNS int
LANGUAGE plpgsql AS $$
BEGIN
  RETURN 1;
END;
$$;

DO $verify$
BEGIN
  RAISE NOTICE 'ok';
END;
$verify$;
  1. Start a Claude Code session in that folder and spend a few turns on the file, so the eventual compaction summary will quote it — e.g.: "Read demo.sql, quote the full function definition verbatim in a fenced sql block, and explain the DO $verify$ block", followed by a couple of follow-up questions about the same code.
  1. Trigger a compaction. We observed the corruption on automatic compaction (context-window overflow; three separate real summaries on v2.1.246). The quickest route is running /compact, which produces the same kind of summary record; if manual compaction does not show it, fill the context until auto-compaction fires.
  1. Read the summary record back from the session transcript:
grep -m1 '"isCompactSummary":true' ~/.claude/projects/<encoded-project-dir>/<session-id>.jsonl | python3 -c "import sys,json; print(json.loads(sys.stdin.read())['message']['content'])"
  1. Observe, wherever the summary quoted the SQL: AS $$ arrives as AS $ and the closing $$; as $; — syntactically invalid SQL presented as a verbatim quote — and the inline form DO $verify$ immediately followed by a closing backtick loses the $+backtick sequence (in our transcripts it was replaced by two newlines, cutting the sentence mid-word). Meanwhile ${...}, $name, and $(...) in the same summary survive untouched. That selectivity matches JavaScript String.replace replacement-pattern semantics exactly ($$ → literal $, ` $ `` → the portion preceding the match).

Claude Model

None

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

1.1260x.x

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

_No response_

View original on GitHub ↗