Skip to content
C.W.K.
Stream
Lesson 01 of 05 · published

The Line That Renders as Nothing

~14 min · markdown, commonmark, reporting, parsing

Level 0Loose Note
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

Two Readers, One Final

Every final answer a Coop worker writes has two readers, and they want opposite things. Dad reads the chat: in the Desktop app, in a terminal, on his phone. He wants a few lines in his own language that say what happened, where it landed, and whether he has to do anything. The engine reads the very same text and wants a record it can commit: which binding, which outcome, what evidence. One string, two audiences, and for a while the engine won and Dad lost.

It started with the engine being too strict. Until 2026-09-30, report_text demanded that the whole final be JSON, or one fenced block of it. A worker that wrote one sentence to Dad before the JSON, or a harness that appended its own tail after it (Codex Desktop's memory citations), lost the report without a word. Commit a17c3a3 made the parser tolerant: scan from every { with json.JSONDecoder.raw_decode, and the first object carrying a coop_reports list is the report, wherever it sits. That was right. It also meant workers could paste their JSON anywhere, and they did. Dad's chats ended in braces and quoted keys.

A Construct Markdown Defines and Never Draws

The fix came from a corner of Markdown most people never look at: the link reference definition. You have seen the reference style of links, [docs][1] in the text and [1]: https://example.com at the bottom. That bottom line defines a label. The CommonMark spec says plainly that it does not correspond to a structural element of the document, and its own example shows a document holding nothing but a definition rendering with no visible content at all.

So a definition is a line that a renderer parses and then draws as nothing. Commit ba0fb04 (todo #360, 2026-10-09) built the ending on it. A worker's final now has two parts: two to four plain lines in Korean for Dad (반말, addressed to 아빠), saying what was done, where it landed and what he must do; then a blank line; then the report on one line in this shape:

[coop-report]: <{"coop_reports":[{"binding_id":…,"outcome":…,"body":…,"for_dad":…,"evidence":{}}]}>

The pointy brackets turn the entire JSON into the definition's link destination. The commit records that the Claude Code terminal, react-markdown and pulldown-cmark all draw the line as nothing. Dad sees his Korean lines. The engine still finds the JSON, because its scanner never cared about Markdown in the first place.

The blank line is not decoration. A link reference definition cannot interrupt a paragraph. Put the definition directly under the Korean lines and CommonMark reads it as one more line of that paragraph, drawn in full.

One Angle Bracket Breaks It Open

A destination in pointy brackets may contain no line ending and no unescaped < or >. That rule bit within a day. One worker's report body carried a <repo> placeholder, the most ordinary thing an engineer types. The line stopped being a definition, every renderer drew it as text, and Dad got the whole JSON with its for_dad key showing and no answer meant for him. In the same stretch, workers answering a reply review or a follow-up several turns after their first work envelope had lost the rule entirely and pasted fenced JSON blocks, or opened with an English paragraph.

Commit d2bea99 (todo #375, 2026-10-10) answered both. final_form became the one ending text, and every envelope kind carries it: work, reply review and follow-up alike, because a rule stated once in the first envelope is a rule the worker no longer has three turns later. It now says the JSON must contain no < or > anywhere, body and evidence included, and tells the worker what to write instead: to for an arrow, {repo} for a placeholder, at least for a comparison. Acknowledgement tokens moved onto hidden lines of their own, [coop-read]: <COOP_READ id> and [coop-reply]: <COOP_REPLY id>, so the visible final is only the Korean lines.

The Engine's Model of Hidden

The engine keeps its own description of a hidden line, REPORT_LINE. The first version, from ba0fb04, accepted any characters between the brackets. That made it a liar in exactly the case that mattered: it would call a line hidden that every renderer was drawing. d2bea99 narrowed the destination to <[^<>\n]*>, CommonMark's own destination grammar minus backslash escapes, which the ending never needs. Even the leading {0,3} copies the spec's allowance of up to three spaces of indentation.

The commit says the pattern now matches a definition exactly as CommonMark reads one. That holds for the line and not for its surroundings, and the difference matters. The regex runs with re.M and judges one line at a time, so it cannot see what sits above it. A perfect definition line written directly under the Korean lines is drawn as part of that paragraph, and the same line inside a code fence is drawn as code. REPORT_LINE matches both. That is why the blank line is a written instruction in final_form rather than something the engine checks, and why the pattern's only job is cutting lines out of what Dad reads, a job where matching a line the renderer drew does no harm.

Notice the separation of jobs. Finding the report and deciding what Dad reads are two different functions. report_spans finds every JSON object with a coop_reports list, hidden or not, so a broken line still commits its report. dad_text decides what is shown when the engine relays a final: the reports' for_dad lines win, cut at FOR_DAD_LIMIT, 1,200 characters; without them it cuts the report spans, the REPORT_LINE lines and any empty code fences out of the prose. Every notification line passes through it, so whatever a worker pasted, report JSON never reaches Dad through Coop's own relay. The body stays the English record.

A Closed Set of Fields

One more guard sits on the report itself. REPORT_FIELDS is exactly binding_id, outcome, body, evidence and for_dad. A report carrying any other key is refused as malformed instead of being accepted with the extra quietly dropped. A misspelled evidnce that vanished without a trace would be worse than a rejection, because the worker would believe its evidence was on the record. The refusal happens in the adapter, before the engine is asked, and the third lesson of this track shows what that placement costs.

When one text serves a person and a machine, give each its own layer, and make the machine's idea of hidden match the renderer's, then name what it still cannot see. A parser that is generous about where the data sits is good. A regex that is generous about what counts as invisible is a regex that lies to you on the day it matters.

Code

From the engine, trimmed: the hidden-line pattern, the closed field set, and what Dad reads·python
# engine.py
REPORT_LINE = re.compile(r'^ {0,3}\[coop-[a-z]+\]:[ \t]*<[^<>\n]*>[ \t]*$', re.M)

def dad_text(text):
    """What Dad reads of one worker final: its for_dad lines, never report JSON."""
    spans=report_spans(text)
    lines=[r['for_dad'].strip() for _,_,reports in spans for r in reports
           if isinstance(r,dict) and isinstance(r.get('for_dad'),str) and r['for_dad'].strip()]
    if lines:
        return '\n'.join(lines)[:FOR_DAD_LIMIT]
    for start,end,_ in reversed(spans):
        text=text[:start]+text[end:]
    text=REPORT_LINE.sub('',text)
    text=re.sub(r'```[A-Za-z]*\s*```','',text)
    return re.sub(r'\n{3,}','\n\n',text).strip()

# adapters/host.py
REPORT_FIELDS={'binding_id','outcome','body','evidence','for_dad'}
A runnable sketch: render three finals with a real CommonMark parser (pip install markdown-it-py)·python
import json
from markdown_it import MarkdownIt

md = MarkdownIt("commonmark")
lines = "아빠, 끝났어. 커밋은 origin에 푸시됐어.\n아빠가 할 일은 없어."

def final(body, gap="\n\n"):
    report = {"coop_reports": [{"binding_id": "b1", "outcome": "done", "body": body,
                                "for_dad": lines, "evidence": {"commits": ["abc123"]}}]}
    return lines + gap + "[coop-report]: <" + json.dumps(report, ensure_ascii=False) + ">"

cases = {
    "clean": final("Hook covers ~/CWK/{repo}."),
    "angle bracket": final("Hook covers ~/CWK/<repo>."),
    "no blank line": final("Hook covers ~/CWK/{repo}.", gap="\n"),
}
for name, text in cases.items():
    html = md.render(text)
    print(f"{name:14} leaks JSON: {'coop_reports' in html}")

# clean          leaks JSON: False
# angle bracket  leaks JSON: True
# no blank line  leaks JSON: True

External links

Exercise

Pick a tool of yours that puts machine-readable data inside text a person reads: a CI bot comment, a commit trailer, a chat bot's answer. Move the machine part onto one link reference definition under a blank line, render it with a CommonMark parser, and confirm the person sees only the human lines. Then break it on purpose three ways (an angle bracket in the payload, no blank line, a stray character after the closing bracket) and write a regex that agrees with the renderer on all four inputs.
Hint
Start from the spec's grammar, not from your eye: label, colon, optional spaces, a destination in pointy brackets holding no line ending and no unescaped angle bracket, optional spaces, end of line. A pattern that reads one line at a time cannot see the missing blank line, so yours has to look at the line above too. If your regex and the renderer disagree on any input, the regex is the one that is wrong.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.