Description
Finding
PR #400's nesting-aware fence scanner (fixes #393) still lets a trigger-issue draft escape the link check when the draft's embedded code example is itself unclosed. Everything in the draft from the example's opening fence onward is stripped before link checking, so any pointer/link after the example is silently unchecked — the same silent-escape failure mode as #382 / #391 / #393.
Two shapes reproduce it on current main 2b9b424a via links() in scripts/research_workflow.py:
-
Inner example never closed — the draft's own closing fence is consumed as the example's close:
```markdown ```python print(1) - [concept](missing/a.md) <!-- workflow:issue {"package": "pkg", "concept": "C001", "stage": "S1"} --> ```
Result: no error. The broken pointer
missing/a.mdis never checked. Adding a second closing fence after it changes nothing — still no error. -
Inner example closed with a shorter fence than it opened with (opens
````python, closes```): the short bare fence is literal content underfence_events()' length rule, so the example is again unclosed and a broken pointer after it passes silently.
Control (the shape PR #400's regression tests cover — pointer before a properly closed ```python example) correctly raises missing link target: missing/a.md.
Evidence
- PR: fix(workflow): check draft links past inner code fences (#393) #400 (merged 2026-10-20T08:08:38Z, commit
b29a56e445ce95416101990dacbed49d42811817, merge2b9b424a98bc207e592680e686d55933c01e1b68): fix(workflow): check draft links past inner code fences (#393) #400 scripts/research_workflow.py:407—fence_events(): only a bare fence at least as long as the innermost one closes a level; an unclosed inner fence therefore consumes the draft's closing fence and leaves the draft stack open.scripts/research_workflow.py:456—fenced_blocks()returns the trailing unclosed block (per fix(workflow): check draft links past inner code fences (#393) #400), so the draft is selected: itsworkflow:issuemarker is seen.scripts/research_workflow.py:491—issue_drafts()then strips nested blocks viaunfenced(block). Reparsing the block standalone, the unclosed inner fence opens at the example and never closes, so every line from the example onward classifies as fence content and is dropped from the cleaned draft. Verified directly: for shape 1,issue_drafts()yields['']whilelinks()on the control shape raises.- Single-repo:
scripts/research_workflow.pyexists only in developing-today/code (absent from the opencode / openchamber / nix clones). - Deduped: issue searches for
fence,unclosed draft,research_workflowreturn only fix(workflow): trigger-issue drafts with an inner code fence escape the #391 link check — fenced_blocks() splits the draft at the inner fence #393 and docs(workflow): C001 trigger-issue draft (PR #381) mixes link bases — one pointer is dead, and the checker strips the fenced draft so it cannot catch it #382, both closed; neither covers an unclosed inner fence.
Why it matters
research_workflow.py check is the guard that draft pointers resolve before a draft is copied into a GitHub issue. #400 explicitly adopted the principle that unclosed shapes are "checked rather than silently dropped" for the draft's outer fence; an author who forgets to close an embedded example — or closes it with a shorter fence — is exactly the hand-authoring slip this checker exists to catch, and today it re-opens the silent escape #400 was filed to close, for every link after the example.
Acceptance criteria
- A regression test with a draft whose inner code example is unclosed and which carries a broken pointer after the example fails
check(and passes when the pointer is valid). - A regression test for the shorter-closing-fence variant (example opens with 4 backticks, closes with 3) likewise fails
checkon a broken post-example pointer. - Links before the example, and the existing nested/unclosed-draft behaviour from fix(workflow): check draft links past inner code fences (#393) #400, are unchanged: the full workflow suite (48 tests) still passes and
python3 scripts/research_workflow.py check --repo-root .still returnsok: trueon the real tree. - Embedded example contents themselves remain exempt from link checks (a link-shaped line inside the example, closed or not, is not checked as draft prose).
Activity