Generating a refactor commit in gitmoji style produced a subject with the emoji split in half and a second type wedged into the gap.
What it generated
♻ chore: ️ refactor(claude-code): use shui for hook adoption prompts
Codepoints, in order:
|
|
U+267B |
♻ RECYCLING SYMBOL — the base codepoint, alone |
chore: |
an injected type that was never asked for |
U+FE0F |
the orphaned variation selector, now attached to nothing |
refactor(claude-code): … |
the real type and summary |
So ♻️ (U+267B U+FE0F) was emitted as two pieces with chore: inserted between them.
Why it is worse than a cosmetic glitch
The subject now contains two conventional-commit types. Any downstream tooling that reads the type — a changelog generator, a semver bump, a lint rule, or a glyph-mapping pass — reads chore and acts on it, while a human reads refactor.
In my setup a wrapper asserts a closed type→gitmoji mapping after each commit. It parsed chore, decided the glyph was wrong, and "corrected" it:
🧹 chore: ️ refactor(claude-code): use shui for hook adoption prompts
Which is worse than what it started with, because it now looks deliberate. A visibly wrong glyph gets amended; a plausible wrong glyph never gets looked at again.
Reproduction
Not reliably reproducible on demand — it has happened once, on a staged diff of ~33 insertions across two files, with --style gitmoji. Recorded here because the reflog preserves the pre-amend subject as direct evidence:
HEAD@{1} commit: ♻ chore: ️ refactor(claude-code): use shui for hook adoption prompts
The chore: prefix appearing at all is the second half of the bug — nothing in the diff suggested a chore, and the model clearly also produced refactor(claude-code), so something concatenated two candidate subjects rather than choosing one.
Suggested fix
Validate the generated subject before returning it. It should match ^<single-emoji> <type>(<scope>)?: <summary>$ with exactly one type, and a U+FE0F anywhere past the first grapheme is a reliable tell that an emoji was split. Regenerate or fail loudly rather than emitting it — a malformed commit message is permanent in a way a failed generation is not.
Emoji should also be handled as graphemes rather than codepoints wherever the subject is assembled; splitting on codepoints is what allows an insertion to land between a base character and its variation selector.
Generating a
refactorcommit in gitmoji style produced a subject with the emoji split in half and a second type wedged into the gap.What it generated
Codepoints, in order:
U+267Bchore:U+FE0Frefactor(claude-code): …So
♻️(U+267B U+FE0F) was emitted as two pieces withchore:inserted between them.Why it is worse than a cosmetic glitch
The subject now contains two conventional-commit types. Any downstream tooling that reads the type — a changelog generator, a semver bump, a lint rule, or a glyph-mapping pass — reads
choreand acts on it, while a human readsrefactor.In my setup a wrapper asserts a closed type→gitmoji mapping after each commit. It parsed
chore, decided the glyph was wrong, and "corrected" it:Which is worse than what it started with, because it now looks deliberate. A visibly wrong glyph gets amended; a plausible wrong glyph never gets looked at again.
Reproduction
Not reliably reproducible on demand — it has happened once, on a staged diff of ~33 insertions across two files, with
--style gitmoji. Recorded here because the reflog preserves the pre-amend subject as direct evidence:The
chore:prefix appearing at all is the second half of the bug — nothing in the diff suggested a chore, and the model clearly also producedrefactor(claude-code), so something concatenated two candidate subjects rather than choosing one.Suggested fix
Validate the generated subject before returning it. It should match
^<single-emoji> <type>(<scope>)?: <summary>$with exactly one type, and aU+FE0Fanywhere past the first grapheme is a reliable tell that an emoji was split. Regenerate or fail loudly rather than emitting it — a malformed commit message is permanent in a way a failed generation is not.Emoji should also be handled as graphemes rather than codepoints wherever the subject is assembled; splitting on codepoints is what allows an insertion to land between a base character and its variation selector.