I spent an embarrassing amount of time today chasing a dquote> prompt in zsh. Not because the commands were wrong. Not because of hidden characters in the source files. Not because of clipboard managers, bracketed paste mode, or terminal emulator quirks. Because TextEdit, Apple’s own plain text editor, was silently replacing a closing ASCII double quote with a Unicode smart quote, and doing it inconsistently enough to make the problem look like something else entirely.
Here’s how that debugging session went and what actually proves it.
The Setup
I was building a set of curl commands to hit the Cloudflare API to specifically the cache purge endpoint across multiple zones. The commands are long. Each one looks roughly like this:
curl -s -X POST -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" -d '{"purge_everything": true}' "https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache"
I had six zones, grouped into two sets. I was keeping them in a TextEdit plain text document so I could paste them into a terminal as needed.
Some of them worked. Some of them didn’t. The failure mode was dquote>: zsh waiting for a closing quote that it believed was never delivered.
The Investigation
The natural first suspects:
Clipboard manager interference. Quit CopyClip. Same problem.
Bracketed paste mode. Ran printf '\e[?2004l' to disable it. Same problem.
Multi-line paste behavior. Tested pasting a single failing line in isolation. Same problem.
Terminal emulator. Tested in both iTerm2 and native Terminal.app. Same problem.
Hidden characters in the file. Ran cat -v on the file. Clean output. Ran xxd and grep’d for the zone ID in question. Bytes looked like straight ASCII.
At this point I had eliminated everything that should have been the problem and the command was still failing. The line was visually identical to the working ones. Same structure, same quoting, same everything.
Then I ran this, copying the failing line from TextEdit first, then immediately:
pbpaste | xxd | tail -5
Output:
00000260: 3061 3631 3330 3536 6135 342f 7075 7267 0a615046a54/purg
00000270: 655f 6361 6368 65e2 809f 0a0a e_cache.....
There it is. e2 809d after purge_cache. That’s U+201D, a Unicode right double quotation mark. Not 0x22. Not an ASCII straight quote. A curly right quote that zsh cannot use to close a string it opened with an ASCII quote.
Why cat -v Lied
cat -v is supposed to show non-printing characters. U+201D is a printable character that is a valid Unicode glyph that renders as ". So cat -v showed nothing wrong because nothing was technically wrong from its perspective. The character was printable. It just wasn’t the character the shell needed.
xxd on the file itself also looked clean at a glance because the smart quote is three bytes (e2 80 9d) and when you’re scanning hex output quickly it doesn’t jump out. You need to know what you’re looking for.
pbpaste | xxd was the right tool because it showed exactly what the clipboard was handing to the terminal at paste time, byte for byte.
Why TextEdit Did This
TextEdit has many “Smart” options (Smart quotes, smart dashes, etc) under Settings > New Document > Options. I unchecked it. Didn’t matter, the substitution had already happened when I originally pasted the commands in. The smart quote was baked into the file before the setting was ever relevant.
The setting controls what TextEdit does going forward when you type. It does not sanitize existing content. It does not warn you that the file contains Unicode quote characters. It does not offer to fix them. It just sits there with Plain Text selected in the toolbar, looking completely innocent, while e2 809d quietly ruins your afternoon.
The particularly evil part: it only substituted the closing quote on the last character of the last line in the document. Every other quote in every other command was straight ASCII. This is consistent with how smart quote substitution works: it replaces a closing quote at the end of a word or line boundary. The last character of the last line before a newline is exactly where that heuristic fires.
So, for an engineer doing technical work: shut all the “Smart” features off. For visual representation-only work, it’s fine.
The Proof
# Copy the failing line from TextEdit, then:
pbpaste | xxd | tail -3
# You will see e2 809d where the closing " should be
# That is U+201D RIGHT DOUBLE QUOTATION MARK
# ASCII straight quote is 0x22
# They are visually identical in most fonts
# zsh treats them as completely different characters (because they are)
What to Actually Use
If you’re using TextEdit as a scratch pad for shell commands, stop. Your options:
nano already in macOS, no smart quote substitution, what you type is what gets stored.
VS Code has explicit control over quote characters, won’t substitute behind your back.
The Broader Lesson
When a shell command fails with dquote> and you can’t find the problem visually, don’t trust visual inspection. Don’t trust cat -v. Go straight to pbpaste | xxd and look at the actual bytes the clipboard is delivering. The problem is almost always a character that looks right but isn’t and TextEdit on macOS is a reliable source of exactly that class of corruption.
The debugging process here burned more time than it should have because every symptom pointed somewhere else. The file looked clean. The setting was off. The line worked in a heredoc. The only thing that told the truth was the hex dump of the clipboard contents at paste time.
That’s the tool. Use it first.