CommonMark flanking rules print ** on Japanese and Chinese pages
CommonMark only lets ** close emphasis when it is right-flanking. Japanese and Chinese have no space after a full stop, so bold ships as literal asterisks.

On this page
Introduction
I published an article in three languages, opened the Japanese page to read it, and found asterisks printed around a sentence that was meant to be bold.
**SIGTERM を受け取った Chrome は、終了する際に自分の SingletonLock を削除します。**これはテスト用に…The cause is in CommonMark. A ** run closes emphasis only when it is right-flanking, and a run sitting between a 。 and a letter is neither left- nor right-flanking. English never meets the condition, because a space follows every full stop. CJK has no such space.
What decides whether a ** can close
A run of asterisks is not an instruction. It is a candidate, and the two characters on either side of it decide what the candidate is allowed to become.
Left-flanking and right-flanking
Bold is delimited by a pair of runs, the way brackets are. One run opens the emphasis and the other closes it, and the parser decides which job each run is eligible for before it can pair them at all.
A run that qualifies for neither job is not an error. It is left on the page as two asterisks.
Eligibility is decided per run, and it is decided by adjacency. A run can open only if it is touching the text it would open, and close only if it is touching the text it would close. A space on the inside breaks that contact:
This is **bold text** here both runs touch the text → rendersThis is ** bold text** here opening run has a space → stays literalThis is **bold text ** here closing run has a space → stays literalCommonMark gives those two eligibilities names, and states each as a condition on the characters either side of the run:
- A run is left-flanking when the run is not followed by whitespace, and either not followed by punctuation, or else is preceded by whitespace or punctuation.
- A run is right-flanking when the run is not preceded by whitespace, and either not preceded by punctuation, or else is followed by whitespace or punctuation.
The subject of every clause is the run itself, and “followed” means the single character sitting immediately after it. Left-flanking is the eligibility to open, right-flanking the eligibility to close.
A run may open emphasis only if it is left-flanking, and close it only if it is right-flanking. Nothing else about the document matters: not the paragraph, not the matching run, only the character before and the character after.
Read the closing case slowly, because it is the one that bites. A run preceded by punctuation is right-flanking only if what follows is whitespace or punctuation. A letter on the right, with punctuation on the left, satisfies neither clause.
Why a full stop in English is always safe
English writes a space after a full stop, and that space is doing load-bearing work:
**It removes the lock.** This is a test.The closing run is preceded by . and followed by a space, so the second clause of the right-flanking test is satisfied and the run closes. That space is the only reason English works. Every sentence-final bold span in English lands in that shape by default, which is why the rule is invisible to anyone writing in it.
Japanese and Chinese put no space between sentences. The closing run therefore sits directly against the first character of the next sentence, and that character is almost always a letter.
The three shapes that fail in CJK
Three arrangements come up constantly in CJK technical prose, and all three produce a run that can do nothing:
…削除します。**これは closing run: 。 on the left, こ on the right…対象を**「引用」**とする opening run: を on the left, 「 on the right…ではなく**`client-id`**です opening run: く on the left, ` on the rightThe first is a bold span ending on its own sentence. The second is a quoted phrase, where the Japanese bracket is punctuation and disqualifies the opener. The third is emphasis wrapped around an inline code span, where the backtick does the same.
Each repair moves the punctuation out of the emphasis rather than adding a space inside it:
…削除します**。これは…対象を「**引用**」とする…ではなく **`client-id`** ですThe third repair is the ugly one, because a space between Japanese text and a bold run is visible. It is the only option when the delimiter sits against a backtick that cannot move.
Four sweeps, and the one that broke three paragraphs
Repairing this by hand was not practical, so I swept it: move sentence-final punctuation outside a failing closer, move paired brackets outside a failing opener, and fall back to a space where neither applies.
The third sweep is where I broke things. I paired the delimiter runs per line, which is wrong whenever a paragraph wraps:
…把同一段流程分別跑在釋出版與修正版的打包檔上,**組字中的狀態完全相同,結果卻不同**:Read that second line alone and the ** on it is the first run on the line, so a per-line pass calls it an opener. It is a closer. The sweep tested it for left-flanking, found it wanting, and inserted a space in front of it — which is exactly the thing that stops a closer from being right-flanking.
A repair that can introduce the defect it repairs needs the same check pointed at its output. Running the renderer over every file afterwards is what found the three paragraphs I had broken, and a fourth sweep put them back.
The check that blocks it now
The rule is a function of two characters, so the check is short. It pairs runs per paragraph, applies both flanking tests, and reports any run that can neither open nor close where it is needed:
const canOpen = !isSpace(next) && (!isPunct(next) || isSpace(prev) || isPunct(prev));const canClose = !isSpace(prev) && (!isPunct(prev) || isSpace(next) || isPunct(next));const closing = i % 2 === 1;if (closing ? canClose : canOpen) continue;Two details are worth more than they look. A paragraph ends at a blank line, not at a gap in line numbers, or every stretch of prose between two code fences is treated as one unit.
And an odd number of runs is reported rather than skipped. A lone ** always renders literally, so it is itself the defect. Skipping the region would switch the check off for everything after it while still printing a clean count.
A gate nobody has seen fail is not a gate, so I broke a repaired file on purpose:
INnode scripts/prose-check.ts <slug> jaOUTemphasis delimiters that cannot render: 1 line 184 closing …を削除します。**これはテスト用に起動した…Restoring the file returned it to zero and exit 0. The check runs for ja and zh-tw only; English cannot produce the shape.
Summary
- A
**run opens emphasis only when left-flanking and closes only when right-flanking, and both tests read just the character before and the character after. - A closing run preceded by punctuation needs whitespace or punctuation on its right, so
。**これはcan neither open nor close and ships as literal asterisks. - English never meets the condition because it writes a space after every full stop. The defect is invisible in a source locale and systematic in its translations.
- The repairs move punctuation out of the emphasis; a space outside the delimiter is the fallback for a run against a backtick.
- Pair delimiters per paragraph. A wrapped paragraph can begin a line with its closing run, and a per-line pass reads it as an opener.
- The rule is two characters wide, which makes it worth checking mechanically rather than reviewing for.



