task.py set-body: Goal-H2 drop guard is now workflow-aware — v2 report bodies auto-exempt (merged PR #2023)
Highlight text on any card (body, plan, or an event) to anchor a comment, or leave a whole-task comment here. Mention @claude to summon a reply.
No comments yet.
Goal
set-body refuses to write a kind: experiment body that removes a ## Goal
H2 present in the prior body (GoalH2DropError, incident #1112), overridable
with --allow-goal-drop. That guard is correct for markdown v4 clean-results,
where ## Goal is a required section.
It is wrong for workflow: v2 report bodies. The v2 report template has five
required H2 sections — Motivation / TLDR / Methodology (shared) / Results /
Conclusion and next steps (scripts/verify_report.py REQUIRED_SECTIONS) —
and ## Goal is deliberately not among them; the Motivation section
carries that context instead. So every v2 report body legitimately drops
## Goal, and the guard fires on every one of them.
Make the guard skip (or invert) for a body whose frontmatter is
workflow: v2 — or, more precisely, key it on the body being a report-v1 body
rather than a v4 clean-result. The v4 path must be left exactly as strict as it
is today.
Why this matters rather than being a harmless extra flag
The override trains the caller to pass --allow-goal-drop reflexively on
report posts. Once it is habitual it is also passed on the runs where the drop
is the #1112 accident the guard exists to catch, and the guard stops working
for the case it was built for. A guard that fires on a legitimate,
spec-mandated action every single time is worse than no guard, because it
teaches people to silence it.
What actually happened (#2162, first v2 task)
Posting the assembled v2 report body was refused. Verified before overriding:
verify_report.pyREQUIRED_SECTIONScontains no## Goal.- The assembled body has exactly the five required H2s and no others.
- The canonical Goal was never at risk:
goal:frontmatter was intact at 1,326 characters before and after the write, andset-bodypreserves frontmatter. The Goal that downstream agents read is the frontmatter field, whichset-goalwrites — not the body H2. - The prior body (with its
## Goal) was snapshotted tooriginal-body.mdby--snapshot, so nothing was lost.
So the drop was correct-by-spec and the override was safe here. It was still an override on the happy path, which is the defect.
Scope notes
- Do NOT weaken the v4 path. The check should stay byte-identical in behavior for any body that is not a v2 report; ideally add the branch, do not restructure the guard.
- Decide deliberately whether the discriminator is
workflow: v2frontmatter or the presence of the<!-- report-v1 -->sentinel in the NEW body. The sentinel is the more precise signal (it describes the body being written rather than the task's routing), and it also covers a v2 task whose body is something other than a report. Prefer the sentinel, or require both. - Consider whether the inverse guard is worth adding for the v2 path: refuse a
report-v1 body that is MISSING one of the five required sections, so the same
call site protects both shapes rather than just going quiet for v2. That is a
natural extension but a judgement call —
verify_report.pyalready covers it at the gate, so this may be redundant. #2162is the only v2 task so far, so there is exactly one real call site to test against; its posted body is a good fixture.- Confidence: high that the current behavior is wrong for v2 and that the fix is small; moderate on the discriminator choice, which is the one real design decision here.