Twinned from https://github.com/cfsh/ninefold/issues/732 by tools/beads/import_github.py's reconcile pass.
Filed unclaimed and unlabelled from a strategic-uplift window, working #702. Backlog.
CLAUDE.md makes reading @aedanpope's feedback the highest-priority thing a tick does — *"Read them every poll and at session start"*, *"A silent fix isn't enough"*, and three channels are the floor. poll.py reads all three correctly. What it then does with them is the problem.
@aedanpope reviewed at 09:02:21Z. Via the API:
``` body_len=0 state=COMMENTED ```
His review body was empty — the entire ask was in an inline comment on CLAUDE.md. That is not unusual; it is how a line-specific review is written, and inline threads are one of the three channels CLAUDE.md names.
In the digest for that tick:
| line | content |
|---|---|
| 4 | REV aedanpope [COMMENTED] 2026-08-07T09:02:21Z: ← nothing after the colon |
| 5-169 | the NFR verdict, then 165 lines of the session's own previous comment bodies |
| 170 | INL [CLAUDE.md] aedanpope …: We shouldn't say "Report the PR URL and stop"… |
166 lines between the announcement and the payload, and the announcement carries no indication that a payload exists.
poll.py:887 prints REV {who} [{state}] {ts}: {body}; with body == "" a session sees a header and nothing. Nothing says "the content is in an inline comment below".REV (:887) → NFR (:920) → CMT bodies (:975) → INL (:979). So a human's line-specific ask always lands *after* every comment body — and the bulkiest comment bodies are usually the session's own review replies, which it wrote and does not need re-read. Composes badly with #726.last_reply and unanswered are computed at :898/:940 — and used only for NFR verdicts, to raise ⚠ NFR GAPS — action the ledger. A comment, review or inline from @aedanpope with no later reply produces no warning line and no counter. The ⚠ NEEDS ATTENTION — N unreplied comment(s) block exists in the *issues* section and has no counterpart for tracked PRs.The session is still *woken* — human comments do enter the fingerprint, correctly — so this is not #587/#623. It is what happens after the wake: the tick fires, the digest prints, and the one line that mattered is 166 lines down with nothing flagging it.
merge_pr.py's feedback ledger does catch an unanswered human comment — at merge time, which is hours later and only if the session tries to merge. The digest is the surface that exists to catch it *now*, it has the data and the predicate, and it spends them only on the bot.
⚠️ This is #679's shape with the roles swapped. That issue is about a CONVERGED verdict carrying work being invisible on the quiet path; this is about a human's ask being invisible in a digest that printed it. Same conclusion: the loop needs *"is there anything here to do?"*, and result= — or here, "did the bot say GAPS" — is the wrong key.
unanswered for humans. The predicate is already there: any non-bot review/comment/inline newer than last_reply → ⚠ aedanpope replied and you have not answered: ./tools/pr/thread.sh <n>, with a counter that holds the cadence the way gaps_pointers does. Small, and it is the half that makes the other two cosmetic.REV … [COMMENTED] (body empty — see INL below) when body == "" and inline comments exist, so the header stops reading as "he said nothing".⚠️ Do not truncate a human's text to close the gap. CLAUDE.md's doctrine is that the session must read what he actually wrote, verbatim — the fix is ordering and flagging, never abbreviation.
Related: #679 (same key-on-the-wrong-thing shape, bot side), #726 (digest volume — the 165 lines here are its tracked-PR half), #451/#623/#587 (fingerprint scoping — distinct; the wake works, the presentation does not).
None.
No comments.