Skip to content

check-agent-record: prose inside ## Owed counts as owed, so a sentence saying an issue is NOT owed makes it owed #1042

Description

@localai-bot

Found while re-running the gate on PR #1038 as operator, and confirmed by executing the checker's own function rather than inferring from its message.

The defect

scripts/check-agent-record.py:1354-1369:

body = text.split("\n## Owed", 1)[1].split("\n## ", 1)[0]
owed |= set(re.findall(r"#(\d+)", body))
owed |= set(re.findall(r"issues/(\d+)", body))

The section body is scraped for any #NNNN. There is no requirement that the number appear in the table, in a list item, or in any position implying the issue is actually owed. The count site at :1443 is:

if row_id is None and number not in owed:
    unowned.append(number)

So mentioning an issue anywhere in a ## Owed section exempts it from the unowned ratchet.

Demonstrated, not argued

.agents/specs/ltx25-decode-speed.md on row/LTX25-DECODE-SPEED-R2 deliberately excludes #1008 from its ## Owed table and follows the table with a paragraph beginning:

#1008 is NOT owed here. It landed.

That paragraph contains three occurrences of 1008. Executing owed_issues() directly against that tree returns #1008 owed: True.

The consequence is inverted semantics: a spec that carefully documents why an issue is not owed thereby registers it as owed.

I verified the direction by mutation. Reverting the row's owner column to alone leaves the checker green — the prose is carrying it. Neutralising the three prose references as well produces:

ERROR: .agents/issue-index.md: 34 rows name no owner, above the recorded 33: #1008.

exit 1. So the instrument does fire; it is simply pointed at the wrong text. Tree restored byte-identically after each arm (sha256 verified, git status empty).

Why it matters

The unowned ratchet is the mechanism AGENTS.md relies on to keep "filing without fixing" a gate failure rather than a habit. This defect makes it satisfiable by prose. A row can name as its owner and stay green merely by mentioning the issue number in a ## Owed section — including in a sentence that disclaims ownership, or in an unrelated aside, or in a link to a different issue that happens to sit in that section.

It is a false negative only: it can excuse a row that should be counted, never red a row that should not be. Nothing currently on main is wrong because of it, and PR #1038 is not affected — row #1008 there names LTX25-DECODE-DTYPE, which is correct on its own merits and is what the column is for. The double protection is harmless there; the general hole is not.

What a fix owes

  • Parse the ## Owed table (or an explicit list form) rather than scraping the whole section, so a number's position carries meaning.
  • A red-before test: a spec whose ## Owed section mentions an issue only in prose must leave that issue counted as unowned.
  • A control in the other direction: an issue genuinely listed in the table must still be exempt, so the fix does not simply tighten into false positives.
  • Consider whether issues/(\d+) should be scraped at all — a markdown link to an unrelated issue inside the section has the same effect.

Per AGENTS.md this changes checker semantics, so it needs its own row, spec, and fresh review rather than an in-flow repair. Unowned as filed — it needs a row ID before its index entry can be appended without raising the ratchet, which is itself an instance of the thing being reported.

Related: #632 (ENG-RECORD-ANCHOR-RATCHET, the sibling gap where no gate protects a records anchor at all), #1022, #1031.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions