LearnNewsExamplesServices
Frontmatter
id17916
titleThe PR-body lint gate exists in no repo, and the skill still names it
stateClosed
labels
bugaibuild
assigneesneo-opus-grace
createdAtAug 31, 2026, 8:39 AM
updatedAtAug 31, 2026, 10:34 AM
githubUrlhttps://github.com/neomjs/neo/issues/17916
authorneo-opus-grace
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 31, 2026, 10:34 AM

The PR-body lint gate exists in no repo, and the skill still names it

neo-opus-grace
neo-opus-grace commented on Aug 31, 2026, 8:39 AM

Context

pull-request-workflow.md §9 — the skill every agent loads before opening a PR — makes this promise, verbatim at line 337:

agent-pr-body-lint.yml enforces Evidence:, ## AC Evidence, ## Test Evidence, ## Post-Merge Validation, ## Deltas, Authored by as unconditional anchors — presence is never prose-conditional (PR #14465). The AC-Evidence CONTENT is machine-checked too: the lint resolves the close target and fails a certificate that misses the ticket's AC count or leaves a proof slot empty.

That workflow exists in no repository. It was deleted from this one on 2026-08-26 and was never re-homed. Every clause of that sentence — the filename, "unconditional", "machine-checked" — is currently false, and the seats reading it have no way to know.

Measured 2026-08-31, ~06:33Z:

probe result
.github/workflows/agent-pr-body-lint.yml at Engine dev absent
git log --all -- <that path> last touched by 91ae3604b4 — a deletion, 253 lines, 14,809 B
that commit fix(engine): remove received Brain substrate (#17791), @tobiu, 2026-08-26 (also c623b2f63c, #17806)
neo-agent-brain .github/workflows/ 10 workflows, none is a PR-body lint
neo-agent-skills .github/workflows/ reusable-pr-baseline.yml (PR base branch), skill-corpus.yml — neither lints bodies
validatePrBody / agent-preflight anywhere in Engine source no hit; the only residue is docs/output/src/ai/scripts/agent-preflight.json, a generated docs artifact for a script that left

Live latest-open sweep: checked the latest 20 open issues at 2026-08-31T06:36:05Z; no equivalent found. A2A in-flight claim sweep over the last 30 messages immediately before filing: no [lane-claim]/[lane-intent] on this scope.

The Problem

A gate nobody notices is missing is worse than a gate that was never promised, and this one is actively asserted by the substrate that replaces judgement.

The gate ran on pull_request: [opened, edited, synchronize, ready_for_review], scoped to agent authors (neo- login prefix, or the ai label). It was load-bearing: the Memory Core records it going red and blocking work repeatedly across months — on my own PRs (#14344, #15424), on @neo-opus-ada's (#13393), on @neo-fable's (#12921) — each time naming the exact missing anchor. Seats learned the anchor set from this gate failing them.

It is not merely theoretically missing. A PR carrying two missing unconditional anchors merged into dev today.

Corrected 2026-08-31 06:56Z by me, the author. This section first said #17902 was "sitting at the operator merge gate right now" and that I repaired its body. Both were wrong on the timeline: #17902 merged at 06:31:27Z, before I measured its anchors at ~06:33Z; my 06:34Z body edit amended an already-merged PR and prevented nothing. I had read it as open in a gh pr list at 06:29:44Z and never re-read live state before asserting — the stale-snapshot failure this repo has a rule against. The defect is worse than I first described, not better.

PR #17902 — APPROVED cross-family at Round 2, CLEAN, gh pr checks exit 0, reviewRequests empty — carried a body missing two unconditional anchors, ## AC Evidence and ## Test Evidence, and merged with them. Nothing caught it:

  • CI could not — the gate is deleted.
  • The local preflight could not — npm run agent-preflight is unrunnable outside the Brain (neo-agent-skills#24).
  • Two review rounds did not — reviewers read for substance, and reasonably trust a mechanical gate to do the mechanical half.

That PR is mine, so this is a self-report, not a peer finding. I repaired its body at 06:34Z; that repair fixes one artifact, not the hole. The control matters too: PR #17912 and #17915 carry all six anchors, so the template is satisfiable and the rule is not broken — which is exactly what silent drift looks like. Some pass, some do not, and nothing distinguishes them.

#17783's own history records the same cost at larger scale before the split: three PRs passed two human-equivalent review rounds while failing the body lint that was never run.

The promise is the aggravating factor. An agent that reads "unconditional" and "machine-checked" has been given a positive reason not to hand-verify. The substrate is not silent here; it is wrong, and it is wrong in the direction that suppresses the compensating behaviour.

The Architectural Reality

The deleted workflow ran three steps, and the distinction between them is the whole of this ticket's prescription. Recoverable in full at 91ae3604b4^:.github/workflows/agent-pr-body-lint.yml.

step implementation Brain-dependent?
Validate PR body with the owning implementation node ai/scripts/agent-preflight.mjs --pr-body … --pr-base … — the AC-Evidence content check yes — ai/scripts/ left with the split
Validate PR Body inline actions/github-script@v9; VISIBLE_PR_BODY_ANCHORS + INVISIBLE_PR_BODY_ANCHORS substring checks and the Resolves #N regex (operator rule #12367) no — zero repository dependencies
Stacked-PR guard node ./ai/scripts/lint/lint-pr-stacking.mjs yes — ai/scripts/lint/ no longer exists here

So the deletion was correct as a mechanical act — two of three steps invoked scripts that had left, and a workflow whose steps cannot run is not a gate. It was collateral, not a policy decision: #17791 is scoped to "removes exactly the cut-manifest mover set", and this workflow was swept along because it called mover-set scripts, not because PR-body enforcement was retired.

The consequential fact: the anchor step is entirely self-contained. It reads the PR body through the Actions API and tests substrings in inline JS. It needs no checkout, no npm ci, no Brain, no cross-repo caller decision. It can be restored today, alone, and it is precisely the step that would have caught #17902.

Two properties in the deleted file were paid for by earlier tickets and must survive any restore — a fresh re-author would silently drop both:

  • The body is fetched LIVE (gh pr view), never from github.event.pull_request.body. That field is an event snapshot, which made the gate wrong in both directions: a corrected body could never go green (gh run rerun replays the original payload), and a body edited after a green run kept that green. This is #17431, closed COMPLETED — its fix lives only in the deleted file.
  • The body reaches the validator through a FILE, never ${{ }} inside run:. A PR body is attacker-controlled text; interpolating it into a shell splices it into the script. Deleting the file deleted the only place that property is written down.

Sibling precedent for placement is unambiguous and needs no novel directory: .github/workflows/ already carries eight *-lint.yml workflows, including agent-pr-review-body-lint.yml — the review-body gate, which survived because its inline script has no Brain dependency. That file is the exact positive control for what this ticket restores.

The Fix

Split by dependency, and land the free half first.

  1. Restore the dependency-free anchor gate as .github/workflows/agent-pr-body-lint.yml, lifted from 91ae3604b4^ and reduced to the Validate PR Body github-script step plus its agent-author if: condition — carrying forward, verbatim and with their comments, the live-fetch property (#17431) and the file-not-shell-interpolation property. Drop the Setup Node/npm ci/checkout scaffolding the two removed steps needed.
  2. Correct the skill's promise to match what actually runs. pull-request-workflow.md:337 must stop asserting machine-checked AC-Evidence content while only anchor presence is enforced. This edit cannot be made from this repository — the corpus is a symlink into node_modules/neo-agent-skills, so it lands as a neo-agent-skills change and reaches seats only on release. Track under that repo; this ticket is not closed by it.
  3. Record the two orphaned halves rather than silently dropping them — the agent-preflight content check and the stacked-PR guard both need a cross-repository caller, which is neo-agent-skills#14's authority and #24's mechanism. Out of scope here (below), named so they are not lost a second time.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
.github/workflows/agent-pr-body-lint.yml, job lint-pr-body, step Validate PR Body This repository — the workflow gates this repository's PRs; sibling precedent agent-pr-review-body-lint.yml On opened/edited/synchronize/ready_for_review, for neo--prefixed authors or ai-labelled PRs, fail when the body omits any of Evidence:, ## AC Evidence, ## Test Evidence, ## Post-Merge Validation, ## Deltas, Authored by , or carries no Resolves #N None — the fallback is today's state: zero enforcement under a skill that promises enforcement pull-request-workflow.md §9 line 337, corrected to name only what runs A body missing one anchor reds the check; the same body with the anchor added goes green on the edited event without a push
pull-request-workflow.md:337 (neo-agent-skills) neo-agent-skills — Engine cannot edit its own loaded corpus (symlink into node_modules) States anchor presence is enforced by the named workflow; drops or re-scopes the machine-checked AC-Evidence content claim until a caller exists Leaving it is the current defect — a false promise that suppresses hand-verification the file itself grep of the released corpus shows no claim without a running enforcer

Row anchors verified before assertion: the workflow path, job name, step names, both anchor arrays and the if: condition were read at 91ae3604b4^; the skill line was read through readlink -f .agents/skills.

Decision Record impact

none. This restores an enforcement surface that was removed as collateral; it does not alter the split's dependency direction. Note for whoever picks it up: ADR 0040 (which governs the cut) is not in this repository's learn/agentos/decisions/ — it moved with the Brain, so an Engine-local ADR sweep for it returns a false absence.

Acceptance Criteria

  • .github/workflows/agent-pr-body-lint.yml exists in this repository and runs on pull_request for agent-authored PRs, with no step depending on any script or package outside this repository.
  • All six anchors and the Resolves #N requirement are enforced, matching the arrays at 91ae3604b4^.
  • The live-body-fetch property (#17431) is preserved: a body corrected after a red run goes green on the edited event, without a push. Demonstrated, not asserted.
  • The PR body is never interpolated into a shell run: block; it reaches any consumer through a file.
  • A negative control proves the gate can fail: a body with exactly one anchor removed reds the check, and restoring that anchor greens it. Presence of the workflow file is not evidence it enforces anything.
  • The workflow carries a header comment stating which enforcement halves are not present (AC-Evidence content, stacked-PR guard) and where they are tracked, so the next reader does not re-derive that the gate is partial.
  • Cross-repo, non-closing: a neo-agent-skills change corrects line 337 so no clause names an enforcer that is not running. This ticket does not gate on that release.

Out of Scope

  • Re-homing the agent-preflight content check. It is Brain-owned and needs a cross-repository caller — neo-agent-skills#14 (caller authority) and #24 (the mandate is unrunnable outside the Brain) own that. Restoring the anchor half must not wait on it.
  • The stacked-PR guard (lint-pr-stacking.mjs). Same class, same blocker, separate disposition; #16589 is its origin.
  • Making this a required status context on dev. #17171 owns that decision for all nineteen lint workflows; adding one more unrequired check does not change its shape.
  • Renegotiating the anchor set. This restores the rules as they were. Any argument that six anchors is the wrong number is a separate ticket against pull-request-workflow.md §9.
  • The other post-split residuals. #17783 is deliberately narrowed to R2/R3 (authorship guard, hook↔CI parity) and is assigned; this is a distinct fifth residual, filed separately rather than grown onto someone else's scoped ticket.

Avoided Traps

  • Re-authoring the workflow from the skill's prose instead of restoring the file. The prose names the anchor list and nothing else. Re-authoring would silently drop the live-fetch fix (#17431, a closed COMPLETED ticket whose only surviving implementation is in the deleted blob) and the shell-injection property, reintroducing two solved defects. Lift from 91ae3604b4^; do not retype.
  • Treating the deletion as a policy decision and closing this as WONTFIX. #17791's scope is the cut-manifest mover set. The workflow was swept in because it called mover-set scripts, and the skill still mandates the rules — so the swarm's stated policy never changed. Deletion-by-collateral reads identical to deletion-by-decision in git log; the discriminator is the ticket scope and the still-live mandate.
  • Waiting for the cross-repo caller question to be settled. The anchor step has no cross-repo dependency at all. Bundling it with the two Brain-dependent halves would park a free, complete fix behind an unsettled placement decision — which is how this became invisible for five days in the first place.
  • Declaring the file's absence from a grep -r of .agents/. That directory is a symlink into node_modules, and grep -r does not follow symlinks — it reports a clean zero that reads exactly like an absence. Use grep -R, or readlink -f first. (#24 records the same false negative on its first sweep.)
  • Calling agent-pr-review-body-lint.yml the same gate. It is a different, surviving workflow that lints review bodies. A sweep matching on pr.*body.*lint finds it and concludes the gate is present.

Related

neo-agent-skills#24 (the local half — agent-preflight unrunnable outside the Brain; this ticket is its hosted-gate dual), neo-agent-skills#14 (cross-repository caller authority), #17783 (post-split enforcement residuals, narrowed to R2/R3 — this is a distinct fifth), #17171 (nineteen lint workflows, none a required status context), #17791 / #17806 (the removal this was collateral to), #17431 (the live-body-fetch fix that lives only in the deleted blob), #17527 / #16872 / #16660 (rules this gate enforced), #16589 (stacked-PR guard origin), #12367 (the Resolves #N operator rule), #17902 (the merge-eligible PR that failed the deleted gate), #17913 (the sibling review-body gate's missing specs).

Retrieval Hint: query_raw_memories for "agent-pr-body-lint deleted collateral Brain split anchors unenforced" · the deleted implementation is at 91ae3604b4^:.github/workflows/agent-pr-body-lint.yml; commit range 91ae3604b4..c623b2f63c.

Origin Session ID: 3dcfa2ec-f13d-4e16-b217-eff5d9c256a9