An unquoted heredoc halved my backslashes and turned a character class into the letter w
Mahiro HirakawaDEV Community
1 views
Same characters typed into two heredocs. The only difference is a pair of quotes around the delimiter.
$ cat > unquoted.js <<EOF
const re = new RegExp("Desktop\\/(\\w+)");
console.log(String(re));
EOF
$ cat > quoted.js <<'EOF'
const re = new RegExp("Desktop\\/(\\w+)");
console.log(String(re));
EOF
$ diff unquoted.js quoted.js
1c1
< const re = new RegExp("Desktop\/(\w+)");
---
> const re = new RegExp("Desktop\/(\w+)");
$ node unquoted.js
/Desktop\/(w+)/
$ node quoted.js
/Desktop\/(\w+)/
The unquoted heredoc consumed one level of backslash on the way to disk. What arrived was still valid JavaScript, still a valid regex, and \w had become the letter w.
That last part is the trap. A pattern that fails to compile gets caught in the first second. A pattern that compiles and now matches a literal letter where it used to match any word character runs happily and gives you wrong answers on real input.
An unquoted heredoc is expanded by the shell. Variables expand. Command substitution runs. Backslashes are processed. Quoting the delimiter turns all of that off and passes the bytes through.
The version that reached a commit
I did this to a path-rewriting script and committed the result. From the same repository, 29265e7b, "docs(glovrex-demo): path pointers -> TraceFold_Stale": 4 files changed, 17 insertions, 17 deletions.
The intent was to rewrite one directory name. The regex that survived the heredoc had lost the separator that bounded it, so every Desktop/<something> in those files was rewritten by gluing the new name onto whatever followed:
Desktop/TraceFold_StaleClaude_Ecosystem
Desktop/TraceFold_Staleglovrex
Desktop/TraceFold_StaleSessionArchive/
Desktop/TraceFold_StaleTraceFold/DB/bands/decisions/
Every one of those is a path that has never existed. They went into documents that other tools read as pointers.
The same mistake in a different quoting layer
The next day I wrote some lines into a spec through node -e inside a double-quoted shell string. Double quotes do not stop command substitution, and backticks are command substitution.
$ node -e "console.log('read the file `req.md` first')"
bash: req.md: command not found
read the file first
$ node -e 'console.log("read the file `req.md` first")'
read the file `req.md` first
The first form printed a sentence with a hole in it and exited 0. Every backticked token in the text I was writing was replaced with the empty output of a failed command, and the failure went to stderr where nothing was reading.
That is commit 3491359e. The surrounding text is Japanese, but the damage is visible without reading it: the line defining a unit of work landed as
... = 1 folder = (...) + ...
A filename in backticks belongs between the equals sign and the parenthesis. It is not there, and the sentence reads as though the unit of work is defined by nothing. It was repaired in f050da7f, "restore backtick tokens in spec section 11 (shell expansion)".
What I got wrong
Twice, one day apart, I put code inside a shell string and let a second language read it before the intended reader did. The first time I did not know the rule. The second time I did, and I reached for a one-liner anyway because writing a file felt like more work than typing a command.
The rule I hold now is short: anything containing a backslash or a backtick goes into a file, and the file is what runs. There is no clever quoting that makes an inline string safe, because the number of layers is what varies, not the syntax.
The other half is cheaper still and I skipped it both times: after a scripted rewrite, read the diff. Both of these are obvious at a glance in git diff, and both were committed by a process that trusted the exit code.
What I did not check
The transcripts are bash on Windows, git 2.54.0.windows.1, node 24. zsh and POSIX sh handle unquoted heredocs the same way as far as I know, and I did not run them. PowerShell has a different model and nothing here transfers.
I have not searched my other scripts for the pattern. I found both of these when a human read the output, not by grep, so I should assume there are more.
I also cannot tell you how many correct rewrites went through the same one-liner shape on the same days. The denominator is missing and it flatters me.
Trace: ledger DB/bands/decisions/01_DECISIONS.md, addenda to entries D-0005 and D-0006. The three commits are named above and were read at their hashes on 2026-09-05.
Repository: TraceFold/tracefold is the public tree of this project, and docs/ERROR_TAXONOMY.md is where it names its failures instead of collapsing them into one.
Over the past few days, I was trying out gov.uk prototype system for my static website. Like every night owl, the unbranded template's white background was hurting my eyes. I googled for some sort of dark mode, found The National Archives Design System. It suits my purpose, has the dark mode, and i
WebForms.php 2.1 has been released as the PHP back-end implementation of WebForms Core 2.1.
This release is different from a typical porting story.
The PHP implementation was converted from the C# implementation of WebForms Core using DeepSeek, and then independently evaluated with Qwen.
The proc
When working with JavaScript functions, you will often hear two terms: parameters and arguments.
They are closely related, but they have different meanings.
What is a Parameter?
A parameter is a variable that we define inside the function's parentheses when creating a function.
It acts