Three Attempts to Fix the Same Encoding Bug, and Why Only the Third One Actually Explains It

The same inline JavaScript snippet got moved to a different delivery mechanism three times in thirty minutes. Two of the three moves were blamed on an "& encoding" bug. Only the third move actually fixes the kind of bug that explanation describes — and checking the file at each step shows the first two blame something that isn't there.

Move one: straight into the template

The first attempt puts a <script> tag with a Superfish menu accessibility fix directly into html.html.twig, as static markup above the </head> tag. The script uses a normal JavaScript && operator in a few places, like if(mutation.addedNodes&&mutation.addedNodes.length). Checking the actual committed file at this point shows exactly that: two literal ampersand characters, correct JavaScript, no HTML entities anywhere near it.

Move two: blamed on encoding, five minutes later

Five minutes after the first commit, a second commit reverts it, with a message citing "HTML-escaped inline JS... causing & issues" and switching the fix back to a Drupal theme library — an external custom.js file loaded the normal way, through libraries.yml. That's a real, working fix for a real class of bug: if a Twig template's literal text ever did get run through an HTML-escaping filter before output, the fix is exactly to stop putting raw script content inline and load it as an external asset instead. The problem is that the file removed in this commit — checked directly — never contained escaped ampersands. It contained the same clean && as before. Whatever the escaping symptom was, it wasn't sitting in the template source.

Move three: the fix that actually explains it

Twenty-two minutes later, a third commit abandons the library approach too, and moves the same script into hook_page_attachments_alter() — a PHP hook that assembles a Drupal render array and hands it to the page. The script string is passed as the render array's #value, and this time it's explicitly wrapped: \Drupal\Core\Render\Markup::create($js_code).

This is the fix that actually names a real mechanism. Drupal's render system treats a plain string assigned to #value or #markup as unsafe user input by default, and runs it through its own HTML-escaping filter before output — turning && into &amp;&amp; in exactly the way the earlier commit messages described, except this only happens to strings going through Drupal's render pipeline. A Twig template's own static markup was never part of that pipeline in the first place — Twig outputs literal template text as-is; it only auto-escapes values inserted via {{ }} expressions. Markup::create() exists specifically to tell Drupal's renderer "this string is already safe HTML, don't touch it," and it's the one change across all three commits that addresses a documented, real Drupal behavior.

What actually happened, most likely

The first two commits describe a symptom — mangled ampersands in the rendered output — that was probably real, just misattributed. The most likely explanation, given what the code actually does at each step, is that the encoding problem only started once the script moved into a Drupal render array (somewhere between the second and third attempt, or in an intermediate state not captured by these three commits alone), and the fix that happened to land last is also the only one whose stated mechanism matches Drupal's actual, well-documented behavior. The first "fix" wasn't wrong to move away from inline markup, but the reason it gave for doing so doesn't hold up against the file it was editing.

The habit worth keeping

A commit message that names a specific bug is a claim, not a fact, the same way a code comment is. "This fixes an encoding issue" is checkable — read the file at the commit before the claimed fix, and see whether the symptom described is actually present. In this case it took one command, git show <commit>:path/to/file | grep pattern, to find that two of three stated diagnoses didn't match the evidence, even though the sequence of fixes still ended up in the right place. Trusting the destination without checking the reasoning along the way would have meant repeating this exact pattern — reaching for a library-file fix — the next time a genuinely different bug produced a similar-looking symptom, for a different reason entirely.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Please share this article on your favorite website or platform.