Emoji written into source files and commit messages despite an explicit CLAUDE.md prohibition (instruction adherence decays within a session)

Status Open
Reported on v2.1.222
Maintainer reply None cached
Activity 1 comment · opened Aug 5, 2026

Preflight Checklist

  • [x] I am using the latest version of Claude Code (v2.1.222)
  • [x] I searched existing issues. Related and complementary: #82257 ("Emoji in generated output should be opt-in, not default, and the instruction to suppress them does not hold"), which covers emoji in prose documents and chat output for regulated delivery. This report is about a different surface with a different failure mode: glyphs written into source files, code comments and commit messages, where the cost is a hard encoding failure and broken machine parsing rather than tone. The two may share one root cause; if maintainers prefer, this can be folded in as the code-surface half of #82257. Also distinct from #80475 (TUI cell-width render), #74389 (Edit tool corrupting PUA glyphs on write) and #72995 (surrogate 400s), which are rendering and transport bugs rather than generation.

What's Wrong

Claude Code writes emoji and decorative pictographs into files it edits -- Python comments, Markdown docs, commit messages -- and it does so even when the project's own CLAUDE.md carries an explicit, detailed, repeatedly-stated prohibition that is loaded into context at the start of every session.

The adherence problem is the substance of this report. This is not a model that has never been told. It is a model that is told every session, in the file designed for exactly this purpose, and complies at first and then drifts.

Measured today, in a single session, by me:

  1. The project's CLAUDE.md devotes a full section to forbidding glyphs, including the reasoning and the measured costs. It is loaded at session start.
  2. While writing a fix, I added U+26D4 to a Python comment.
  3. The user asked me to strip them. I identified all five in the file, explained why they were a problem, and stated I would remove them.
  4. In the very next edit I added two more (U+26D4 and U+26A0 U+FE0F) -- to the comment explaining a correction, in the same file, minutes after committing to remove them.
  5. A previous session working on the same file had left three.
  6. A separate concurrent session in the same repository was, at that moment, doing dedicated cleanup work removing glyphs (arrow forms, U+26A0, U+2B50, a lambda) from four other files.

So: one 420-line file accumulated five glyphs from two sessions, a third session was running cleanup on the same class elsewhere, and the instruction was violated by the same session that was actively enforcing it. The rule was not ambiguous, not buried, and not stale.

Why this is a correctness problem on the code surface, not a style preference

  • It is a hard failure on Windows, not a rendering wobble. Every one of those five characters raises UnicodeEncodeError on a stock cp1252 console. The file did not cp1252-encode at all until stripped. The project's own notes record four separate failures from this in one session.
  • Variation selectors silently double the codepoint count. U+26A0 arrives as U+26A0 U+FE0F. Every regex, length check, replacement and column calculation that touches it needs explicit VS16 handling, and code written against the single-codepoint assumption is quietly wrong.
  • Glyph meaning is positional, and that is invisible to a reader who learns it from examples. In this repository, a status glyph means "this item is closed" only in one specific block position; quoted in an item's prose it is narrative. Two parsers of the same file disagreed on exactly that, and agreed on the current corpus only by luck. Words carry their scope in the surrounding sentence; a bare pictograph does not.
  • It defeats grep, diff review, and screen readers in files whose entire purpose is to be searched, diffed and reviewed.

What Should Happen

In rough order of value:

  1. A project instruction forbidding emoji should hold for the whole session, not just the first turns. Whatever mechanism keeps CLAUDE.md constraints live, negative formatting constraints appear to decay under it. Glyph emission is an unusually clean proxy for measuring that decay, because it is trivially detectable in a diff -- it would make a good regression metric for instruction adherence generally.
  2. Default to ASCII when writing to files, independently of chat style. Chat output and file content are different products. A user who enjoys emoji in the transcript rarely wants them in a Python comment or a commit message. Separating those defaults would fix most of this without touching conversational tone.
  3. Offer an enforced setting rather than an instruction. A settings.json key (for example "asciiOnlyFileWrites": true) checked at the tool boundary, so Edit/Write refuse or strip on the way out, would make compliance structural instead of aspirational. A writer that refuses is strictly stronger than an instruction that is hoped-for, which is precisely the gap this report documents: the instruction existed, was read, and did not hold.

Steps to Reproduce

  1. Put an explicit prohibition in CLAUDE.md, e.g. "Never use emoji or glyphs in prose, code comments, or commit messages -- say the word instead."
  2. Start a session and work on a substantial task for a while, ideally one involving explanatory comments or documentation.
  3. Grep the resulting diff for codepoints above U+007F.

Observed: glyphs appear, with likelihood rising as the session lengthens, and notably even in text discussing the prohibition itself.

Claude Code Version

2.1.222 (native install)

Platform

Claude Code Desktop, Windows

OS

Windows 11 Pro 10.0.26200

Additional Info

The self-referential case in step 4 above seems the most diagnostic detail available: the glyphs were added to a comment whose subject was a correction, in a file being actively edited to remove glyphs, immediately after stating they would be removed. Whatever governs adherence to a negative formatting constraint was not consulted at that write, even though the topic was in immediate context.

Detection is cheap if useful as a guard: any(ord(c) > 127 for c in line) over a diff catches the entire class, and attempting str.encode('cp1252') catches exactly the subset that breaks Windows consoles.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗