Summary
adr_approve with approval_notes silently produces invalid YAML in the approved ADR file. The bug causes the approval_date/approval_notes keys to be concatenated onto the last character of the previous line with no newline separator, creating a YAML parse error on every subsequent read.
Root Cause
In adr_kit/workflows/approval.py, _update_adr_status (lines 264–280):
if input_data.approval_notes:
yaml_end = new_content.find("\n---\n") # finds the \n before the closing ---
if yaml_end != -1:
approval_metadata = (
f'approval_date: {datetime.now().strftime("%Y-%m-%d")}\n'
)
if input_data.approval_notes:
approval_metadata += f'approval_notes: "{input_data.approval_notes}"\n'
# BUG: new_content[:yaml_end] ends at the last char of the previous line (no \n).
# approval_metadata has no leading \n.
# Result: the new keys are pasted directly onto the end of the previous line.
new_content = (
new_content[:yaml_end] + approval_metadata + new_content[yaml_end:]
)
new_content.find("\n---\n") returns the index of the \n character that precedes ---. So new_content[:yaml_end] ends at the last non-newline character of the preceding YAML line. When approval_metadata is appended immediately after, the result is:
rationales: ['...last item']approval_date: 2026-05-19
instead of:
rationales: ['...last item']
approval_date: 2026-05-19
Confirmed Impact
ADR-0011 in the wild:
rationales: ['...when passed to generateSasToken']approval_date: 2026-05-19
approval_notes: "Surfaced by Task 6 security review..."
YAML parse result:
yaml.scanner.ScannerError: while parsing a block mapping
expected <block end>, but found '<scalar>'
in "<unicode string>", line 8, column 241:
... hen passed to generateSasToken']approval_date: 2026-05-19
The corruption is silent. adr_approve returns success: true and the file is written — but the resulting file cannot be parsed by a standards-compliant YAML parser. Future calls to adr_planning_context, adr_preflight, and the constraints contract builder will silently skip or fail on the corrupted ADR.
Secondary Bug: Unsafe String Interpolation in approval_notes
approval_metadata += f'approval_notes: "{input_data.approval_notes}"\n'
If approval_notes contains a double-quote, backslash, or newline, this produces malformed YAML. The value should be YAML-serialized (e.g. via yaml.dump({'approval_notes': value})), not string-interpolated.
Steps to Reproduce
- Create any ADR with a
policy.rationales list (multiline YAML value at the end of the frontmatter).
- Call
adr_approve(adr_id="ADR-XXXX", approval_notes="Any notes here").
- Open the produced
.md file and attempt yaml.safe_load() on the frontmatter.
- Observe
ScannerError.
Also reproducible when any YAML field other than rationales is last in the frontmatter and lacks a trailing newline (this is the general case; YAML spec does not require one).
Fix
Change line 278 to insert a leading newline in the metadata string:
# Before
new_content = (
new_content[:yaml_end] + approval_metadata + new_content[yaml_end:]
)
# After — add a leading \n so metadata starts on its own line
new_content = (
new_content[:yaml_end] + "\n" + approval_metadata + new_content[yaml_end:]
)
Additionally, replace the string-interpolated approval_notes value with a proper YAML serialization:
import yaml as _yaml
approval_metadata += "approval_notes: " + _yaml.dump(
input_data.approval_notes, default_flow_style=True
).strip() + "\n"
Related
Environment
- adr-kit version: current PyPI release (tested via
uv tool install adr-kit)
- Python 3.14
- Triggered by approving an ADR with a
policy.rationales array in its frontmatter and passing approval_notes
Summary
adr_approvewithapproval_notessilently produces invalid YAML in the approved ADR file. The bug causes theapproval_date/approval_noteskeys to be concatenated onto the last character of the previous line with no newline separator, creating a YAML parse error on every subsequent read.Root Cause
In
adr_kit/workflows/approval.py,_update_adr_status(lines 264–280):new_content.find("\n---\n")returns the index of the\ncharacter that precedes---. Sonew_content[:yaml_end]ends at the last non-newline character of the preceding YAML line. Whenapproval_metadatais appended immediately after, the result is:instead of:
Confirmed Impact
ADR-0011 in the wild:
YAML parse result:
The corruption is silent.
adr_approvereturnssuccess: trueand the file is written — but the resulting file cannot be parsed by a standards-compliant YAML parser. Future calls toadr_planning_context,adr_preflight, and the constraints contract builder will silently skip or fail on the corrupted ADR.Secondary Bug: Unsafe String Interpolation in approval_notes
If
approval_notescontains a double-quote, backslash, or newline, this produces malformed YAML. The value should be YAML-serialized (e.g. viayaml.dump({'approval_notes': value})), not string-interpolated.Steps to Reproduce
policy.rationaleslist (multiline YAML value at the end of the frontmatter).adr_approve(adr_id="ADR-XXXX", approval_notes="Any notes here")..mdfile and attemptyaml.safe_load()on the frontmatter.ScannerError.Also reproducible when any YAML field other than
rationalesis last in the frontmatter and lacks a trailing newline (this is the general case; YAML spec does not require one).Fix
Change line 278 to insert a leading newline in the metadata string:
Additionally, replace the string-interpolated
approval_notesvalue with a proper YAML serialization:Related
adr_approvefailure mode (parallel MCP call rejections in Claude Code). This issue is independent: it affects sequential calls that complete successfully but produce corrupt output.Environment
uv tool install adr-kit)policy.rationalesarray in its frontmatter and passingapproval_notes