adr skill: the template's unquoted adr: NNNN is a YAML octal trap that bites at 0008, 0009, 0018… #50

Open
opened 2026-09-16 13:58:49 +00:00 by mathias · 0 comments
Owner

The bug

The adr skill's template shows the frontmatter number unquoted:

adr:           0004

Under YAML 1.1 — which PyYAML's safe_load implements — a leading-zero integer is parsed as octal. So:

  • 0001–0007 parse as ints 1–7
  • 0008 and 0009 parse as the strings "0008" and "0009", because 8 and 9 are not octal digits
  • the same happens at 0018/0019, 0028/0029, and so on

The block still loads, so nothing fails loudly. The corpus simply acquires a silent type split, and a query like [a for a in adrs if a['adr'] == 8] misses the ADR it was looking for. That is the failure mode the skill's own "The frontmatter must actually parse" section is written against, one level subtler — the two documented traps (supersedes: -, unquoted deciders: pending: X) break loading, this one corrupts typing.

Found where

Writing cobalt-dingo ADR-0007 and ADR-0008 on 2026-09-16. Validated with the skill's own check command, which prints ok for both and hides the discrepancy because it does not show the parsed type:

$ python3 -c "import yaml,sys;d=yaml.safe_load(open(sys.argv[1]).read().split('---')[1]);print(repr(d['adr']))" docs/adr/0007-*.md
7
$ ... docs/adr/0008-*.md
'0008'

Fixed in that repo by quoting the value in all eight files (adr: "0008"), which makes every entry a consistently-typed string and keeps the four-digit convention intact.

Estate-wide exposure

Every repo keeping an independently-numbered series is on the same trajectory: infra, cad, swedsl, brain-gardener, openbanking-api, agentsquad, cobalt-dingo. Any series that has reached 8 already has the split; infra is well past 0019 by the skill's own examples, so it likely has several.

Proposed fix

  1. Change the template in skills/adr/SKILL.md to adr: "0004" and add the trap to the "must actually parse" table, alongside the bare-dash and nested-colon entries.

  2. Strengthen the check command so it reveals typing rather than just success:

    python3 -c "import yaml,sys;d=yaml.safe_load(open(sys.argv[1]).read().split('---')[1]);assert isinstance(d['adr'],str),f\"adr must be a quoted string, got {type(d['adr']).__name__}\";print('ok',d['adr'])" docs/adr/0004-*.md
    
  3. Optionally sweep the existing series. Mechanical: sed -i -E 's/^(adr: +)([0-9]{4})$/\1"\2"/' docs/adr/0*.md. This is frontmatter quoting, not a content change, so it does not violate the skill's own "never edit an accepted ADR" rule — but worth noting in the sweep commit that it is the amendment class.

Low risk, and cheaper now than after another two series cross 0008.


Created via git-mcp on behalf of @mathias

## The bug The `adr` skill's template shows the frontmatter number unquoted: ```yaml adr: 0004 ``` Under YAML 1.1 — which PyYAML's `safe_load` implements — a leading-zero integer is parsed as **octal**. So: - `0001`–`0007` parse as ints `1`–`7` - **`0008` and `0009` parse as the strings `"0008"` and `"0009"`**, because 8 and 9 are not octal digits - the same happens at `0018`/`0019`, `0028`/`0029`, and so on The block still loads, so nothing fails loudly. The corpus simply acquires a silent type split, and a query like `[a for a in adrs if a['adr'] == 8]` misses the ADR it was looking for. That is the failure mode the skill's own "The frontmatter must actually parse" section is written against, one level subtler — the two documented traps (`supersedes: -`, unquoted `deciders: pending: X`) break loading, this one corrupts typing. ## Found where Writing `cobalt-dingo` ADR-0007 and ADR-0008 on 2026-09-16. Validated with the skill's own check command, which prints `ok` for both and hides the discrepancy because it does not show the parsed type: ```console $ python3 -c "import yaml,sys;d=yaml.safe_load(open(sys.argv[1]).read().split('---')[1]);print(repr(d['adr']))" docs/adr/0007-*.md 7 $ ... docs/adr/0008-*.md '0008' ``` Fixed in that repo by quoting the value in all eight files (`adr: "0008"`), which makes every entry a consistently-typed string and keeps the four-digit convention intact. ## Estate-wide exposure Every repo keeping an independently-numbered series is on the same trajectory: `infra`, `cad`, `swedsl`, `brain-gardener`, `openbanking-api`, `agentsquad`, `cobalt-dingo`. Any series that has reached 8 already has the split; `infra` is well past 0019 by the skill's own examples, so it likely has several. ## Proposed fix 1. Change the template in `skills/adr/SKILL.md` to `adr: "0004"` and add the trap to the "must actually parse" table, alongside the bare-dash and nested-colon entries. 2. Strengthen the check command so it reveals typing rather than just success: ```sh python3 -c "import yaml,sys;d=yaml.safe_load(open(sys.argv[1]).read().split('---')[1]);assert isinstance(d['adr'],str),f\"adr must be a quoted string, got {type(d['adr']).__name__}\";print('ok',d['adr'])" docs/adr/0004-*.md ``` 3. Optionally sweep the existing series. Mechanical: `sed -i -E 's/^(adr: +)([0-9]{4})$/\1"\2"/' docs/adr/0*.md`. This is frontmatter quoting, not a content change, so it does not violate the skill's own "never edit an accepted ADR" rule — but worth noting in the sweep commit that it is the amendment class. Low risk, and cheaper now than after another two series cross 0008. --- _Created via git-mcp on behalf of @mathias_
mathias added the risk/lowsize/stype/fix labels 2026-09-16 22:21:45 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/skills#50