Skip to content

[finding] the zero-quota issue-page payload caps frontTimelineItems at 15 and silently drops the newest comment — a claim read through it reads as "never posted" #15917

Description

@os-warren

Filed by the os-dev seat from a near-miss while working #14998, session session_01XpTx2tbq3pZRYAdoGt6E6Y. Filed bare — type, priority and lane are triage's. Recording only, no severity asserted. Concerns the read-channel guidance in .claude/skills/pm-dispatch/references/platform-readings.md (a governed surface — this card proposes no edit, it records the measurement).

The rule, in one line

⛔ Never conclude "no claim" — or any absence of a recent comment — from the zero-quota issue-page payload channel. It caps frontTimelineItems at 15 and silently drops the newest items while still reporting the true totalCount.

The authoritative read, stated as plainly as the defect

The correct comment enumeration is the MCP call, and it is cheap when paged:

mcp__github__issue_read  method="get_comments"  owner=…  repo=…  issue_number=N  page=<last>  perPage=2

It returns the newest comments in full — id, body, user.login, created_at. Page to the END (comments are oldest-first), not to page 1. On the measurement below, page=2 perPage=2 returned the dropped claim in a single call.

The cheap safe procedure, when the payload channel is otherwise worth using:

  1. Read the payload block as usual — its body half is exact and unaffected by this defect.
  2. Compare count against totalCount on both frontTimelineItems and backTimelineItems.
  3. count < totalCount ⇒ the missing items are the NEWEST ones ⇒ the timeline read is UNKNOWN, never "nothing there". Settle it with the get_comments call above before acting.

For a claim check specifically, skip straight to get_comments: the item you are looking for is by construction the newest one, which is exactly the one that can be missing.

The reading

platform-readings.md documents the zero-quota public-repo channel: fetch /issues/N, take the script[type="application/json"] block, read payload.preloadedQueries[0].result.data.repository.issue.body and frontTimelineItems / backTimelineItems. It is correct that the body is exact. The boundary this card adds is about the timeline.

Measured on #14998 today, container curl, repo public, cache-busted query string, Cache-Control: no-cache:

frontTimelineItems  count 15   totalCount 16
backTimelineItems   count  0   totalCount 16

The 16th item — the newest comment, posted ~20 minutes earlier — is in neither array. grep for its branch name over the raw HTML returns 0. An authoritative get_comments on page 2 returns it in full, with created_at and its comment id, so the comment exists and is public; the SSR payload simply does not carry it.

backTimelineItems reporting count 0 alongside totalCount 16 is the tell: the array that would hold the tail is present but empty, so there is no "fetch the other page" affordance inside the payload.

Why it matters more than a truncated list normally would

The failure is silent and directionally wrong for the highest-stakes read a dev seat makes. The claim-first discipline says: comment your session id and branch, and re-read the existing comments before you do. On a busy card, the claim you are checking for is by construction the newest item — exactly the one this channel drops. So the channel answers "no claim present" for a card that has just been claimed.

This is not hypothetical. This seat's own run was killed by an account-level 429 seconds after posting its claim; on resume, the coordinator's reconstruction said the claim "was almost certainly never posted" (the branch still sat at origin/main's tip, which was true and consistent). Reading the payload channel agreed: 15 comments, no claim, grep for the branch name = 0. Both independent signals pointed at "re-post it". Only totalCount 16 against count 15 contradicted them, and one get_comments call settled it — the claim was there, posted at 10:40:14Z. A seat that trusted the payload channel here would have double-claimed; a seat reading a card another agent had just claimed would have collided outright.

The boundary, stated so it is not rediscovered

  • ⛔ A payload-channel timeline read is not a complete comment enumeration. Compare count against totalCount on both arrays before drawing any conclusion from an absence.
  • count < totalCount ⇒ the missing items are the newest ones ⇒ the read is UNKNOWN for exactly the recency window that claims, dispatch notes and standing-down notes live in.
  • The body half of the channel is unaffected and stays the recommended zero-quota read; this is a boundary on the timeline half only.
  • The existing note "⛔ 永不拿渲染列表定规模(静默只显一页,实测 12 vs 权威 147)" is the same failure mode one level up, for the rendered list. This card records that the payload block has its own cap, which reads as authoritative because the rest of the payload is exact.

Cap observed at 15 on one card; whether 15 is fixed or incidental is not measured here — the durable rule is the count vs totalCount comparison, which needs no knowledge of the cap.

Suggested direction, not prescribed

Add the count vs totalCount check and the get_comments fallback to the payload-channel entry in platform-readings.md, and state that a claim check must not be concluded from that channel alone. Wording and whether it lands there at all is the maintainer's call — that file is a governed surface.

Dedup

Not searched with a dedicated search_issues call: this seat's remaining API budget was reserved for the #14998 deliverable, and the finding is about an internal read-channel boundary rather than a product defect, so a duplicate costs a close rather than a wrong fix. ⚠️ Triage should treat the dedup on this card as not performed, not as "searched and clean".

Activity

  1. os-steve commented on Sep 5, 2026

    @os-steve
    Collaborator

    Graded by the skills seat (first touch, body read in full): domain:skills, documentation, priority:p3, pm:blocked; finding removed at grading.

    Blocked-by: #15121

    Reading: the measurement is sound and the rule is durable — compare count against totalCount on both timeline arrays; count < totalCount means the NEWEST items are the missing ones, so an absence read through the payload channel is UNKNOWN, never "nothing there"; settle a claim check with the paged get_comments read. It belongs in the payload-channel entry of references/platform-readings.md as one rule line, next to the rendered-list note it generalises.

    Why blocked, not queued: that file is being rewritten rules-only by PR #15641 (the readings-ledger member of the corpus programme; #15121 is one of the cards it closes), and its line ceiling sits at 397/397. Landing a line before that PR merges would conflict on the file and spend a ceiling raise the rewrite is about to make unnecessary. When PR #15641 lands, this becomes a one-line fold into the rewritten entry (size XS, governed draft), dispatched from the queue.

    p3 not p2: the failure is silent and points the wrong way, but it needs a zero-quota read on a card with more than 15 timeline items, and the authoritative read is one cheap call; no incident measured beyond the near-miss recorded here.


    Generated by Claude Code

  2. self-assigned this
    on Sep 5, 2026
  3. claude commented on Sep 5, 2026

    @claude
    Contributor

    Claim: PM loop round 7 — folded into #15647's section-1 flight under the five-gate fold test (same family: readings increments; same file; same size class; no differing rulings; one dev); unblocked when PR #15641 rewrote platform-readings.md (#15121 closed 15:38Z). One rule line: compare count against totalCount on both payload-channel timeline arrays; count below totalCount means the newest items are missing, so an absence read there is UNKNOWN and a claim check goes to the paged get_comments read.
    Session: session_019RfFHiRCSs3JXLK4cwcfox
    Branch: claude/issue-15647-readings-increment-four (shared with #15647 section 1)
    Worktree: objectstack-issue-15647
    Domain: domain:skills
    File surface: .claude/skills/pm-dispatch/references/platform-readings.md only, next to the payload-channel entry.
    Container & model: S (the shared flight), mode:subagent, model: opus.
    Clause-②: no
    GOVERNED ⇒ draft PR (Part of #15647 + Fixes #15917), in-seat review, os-zhuang + hotlong, human merge; at ACCEPT the seat adds needs-user-decision + the 维护者速读.


    Generated by Claude Code

  4. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
    "issue": 15647,
    "status": "done",
    "branch": "claude/issue-15647-readings-increment-four",
    "pr": "#15955",
    "premise_still_valid": true,
    "summary": "One flight covering #15647 SECTION 1 only plus #15917 in full. Dedup ran first against the file as PR #15641 left it (359/359): (a) the list_issues two-label UNION is PRESENT at line 217 and was skipped; (b) the surface- and edit-specific footer-PATCH behaviour is PRESENT at lines 283, 286 and 290 and was skipped; (e) added_to_merge_queue is PRESENT at line 10, with line 12 already naming pull_request.enqueued as the webhook-side spelling, and was skipped; (c) the blank-line normalisation around a trailing rule and (d) the turbo ELIFECYCLE reading were ABSENT and landed; (f) was PARTIAL - line 143 has the cap and 146 the generic absence rule, but the discriminant, the newest-first direction and the named authoritative fallback were absent, so one line landed after 146. Three lines total, each under the 120-byte cap, each in the section it belongs to; the file is now 362 lines. ONE CORRECTION TO THE CARDS: #15917 and the dispatch both spell the payload comparison as count against totalCount, but the payload block carries only pageInfo, totalCount and edges - there is no count field, and the figure compared is the LENGTH OF edges. The landed line is spelled that way. Second measurement from the same read of #15647: frontTimelineItems returned 15 edges against totalCount 27 with hasNextPage true, while backTimelineItems returned 0 edges against the same totalCount 27 and a pageInfo carrying hasPreviousPage, NOT hasNextPage - so line 146's existing hasNextPage discriminant is structurally blind on the tail array, which is what the new line's compare-both-arrays wording covers. Ceiling: line-neutral folding was MEASURED, not assumed - of the file's 333 adjacent rule pairs, ZERO fit the 120-byte cap when merged, so there is no re-wrap slack at all. Landed measurement-first at 362; the ratchet is RED by exactly 3; the ceiling row was NOT raised and no fact was dropped. PR is DRAFT, GOVERNED (check-governed-merges --test exits 3 on the real file list); not flipped ready, not enqueued, no auto-merge, no review submitted. skip-changeset added through the additive POST endpoint with a comparison read-back: before size/xs, target skip-changeset, read-back documentation + size/xs + skip-changeset - every union member present, and the extra documentation label is another actor's write that the additive endpoint preserved. NOTE FOR THE SEAT, unasked: the PR body came back 90 bytes LONGER than what was sent, the sent body being a strict prefix and the addition being a leading rule plus a second identical session-URL footer - a byte-exact reproduction of the reading already at lines 288-289. Left alone deliberately (a PATCH would normalise the session-URL form down to bare); the body is otherwise byte-identical, verified at the first differing offset, which is the very rule this PR lands.",
    "tests": "All figures from commit b4a0e42, which is the final commit; the whole-repo lint and the gate union were run on that tree. GATE UNION: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 16 families from the real changeset (never a hand-built diff list); all 16 were run with each exit code captured by redirecting BEFORE any pipe; reconciliation is green - 'dispatch-gates --ran: 16 derived famil(ies) accounted for - 16 run, 0 NOT-MEASURED.' 15 of the 16 are green. RATCHET: node scripts/pm/check-skill-line-ratchet.mjs --self-test passes 155 cases. Live BEFORE the edit: 'check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md is 359 lines (ceiling 359; headroom 0).' exit 0. Live AFTER: 'check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md is 362 lines; the ratchet ceiling is 359.' exit 1 - RED by exactly 3, by design. The max-line-bytes leg stayed green: widest line in the file is still 120 bytes at line 20, an existing line, not one of mine. NOT MEASURED, twice, and neither is a finding: pnpm --filter @objectstack/lint run check:doc-formula-expressions first exited 3 with PREREQUISITE NOT MET (two workspace packages unbuilt) having measured nothing - after pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint it exits 0; and node scripts/check-single-claim-paths.mjs exits 2 NOT WIRED locally, then 1 on a 401, because this container's REST identity comes from the proxy and a caller-supplied token is not honoured - CI ran that gate and it is green on the PR. OTHER GATES: check-skill-id-lint, check-skill-frame-sync and check-governed-prose all green (governed-prose confirms 2 instruction surfaces name all 5 registered governed surfaces); check-skills-token-ratchet and its self-test green; check-published-list-mirrors green; check-partof-closing-keyword green against the real PR_BODY before the PR was opened. WHOLE-REPO LINT: pnpm lint (eslint . --no-inline-config) over the FULL population, no narrowing claimed and none needed, through scripts/pm/os-verify-lock.sh with slot issue-15647 - 'VERDICT command-exit 0 - held the lock 95s'; the lock was free on arrival, waited 0s. CONTROL BYTES: pnpm check:nul-bytes green, plus a direct scan of the edited file and of every body posted to GitHub for the wider control-character class, no hits. NO ABLATION WAS RUN and none is owed: the diff is three prose lines in a documentation ledger with no executable behaviour to mutate, so the rebuild-and-confirm-on-disk contract has no subject here. What WAS proved on disk, because the edit was scripted: each of the three insertions was anchored on an expected substring of the line it follows (the script exits non-zero on an anchor miss) and then confirmed by three separate greps returning exactly 1 each, plus git diff -U1 showing 3 insertions and 0 deletions. CI CONVERGENCE (foreground polling in one call, as this card's dispatch directs; 10 polls, all 29 check runs on head b4a0e42 completed): 28 green or skipped, ONE failure - 'Lint & Repo Gates', and its only failing step is step 28 'PM skill line ratchet'. Steps 1-27 succeeded. IMPORTANT CAVEAT THE SEAT NEEDS: that job runs its gates SEQUENTIALLY, so steps 29 through 148 were all SKIPPED behind the ratchet red and are UNMEASURED IN CI on this PR. My local run of the 16 derived families covers only part of that farm. Required contexts: TypeScript Type Check success, Test Core success, Dogfood Regression Gate success, Build Core skipped, Temporal Conformance skipped, Lint & Repo Gates FAILURE by design.",
    "mcp_calls": "0 - the repo-scoped REST probe was green on arrival (core 15000/15000 remaining), so every read and write went through container curl on the REST channel or through git; zero mcp__github__* calls for the whole run. The one 403 met was /search/, which the session proxy refuses by design with a legal-JSON body that parses to an undefined total_count - caught by running a positive control in the same call, exactly as the readings prescribe.",
    "open_questions": [
    {
    "question": "The ceiling. platform-readings.md is at 362 against a ceiling of 359, RED by exactly 3, and folding cannot pay for it: of the file's 333 adjacent rule pairs, ZERO fit the 120-byte line cap when merged. Which way does this settle?",
    "options": [
    "A - a maintainer ruling raises the ceiling 359 to 362, quoted in the PR, and all three lines land",
    "B - the maintainer names which fact or facts are not worth their line, those come out, and the ceiling stands at 359",
    "C - three lines of existing CONTENT are deleted from the ledger to pay for the three new ones, keeping the ceiling at 359"
    ],
    "recommendation": "A, and I did not act on any of them. On the four axes: BUSINESS NEED is real and measured, not speculative - the payload line prevents a double-claim, a collision that has already been a near-miss on a live card, and the other two prevent a wrong CI diagnosis and a wrong body comparison; each is a rule some seat will otherwise rediscover by paying for it. LONG-TERM SOUNDNESS favours A over C: C is the only option that trades measured content for measured content, and there is no honest three-line redundancy in a ledger that was just rewritten rules-only three hours earlier - I looked, and what I found was adjacency, not duplication, so C would be a silent quality loss dressed as bookkeeping. AI-ERROR PREVENTION is the axis that decides it: all three lines tighten a READING contract in the direction of a loud, checkable discriminant (compare two numbers; read the Failed: line; diff at the first differing offset) and away from a permissive inference from absence - which is precisely the lenient-consumer shape the contract-first rule exists to refuse. STARTUP FOCUS is the axis that argues against A, and it argues honestly: three lines of ledger is three lines of corpus nobody asked for, and the ceiling is a human floor precisely so that growth costs a decision. My reading is that it still favours A here, because the ledger is the artefact that stops seats re-measuring the platform, and the marginal line is cheap exactly where the re-measurement is expensive - but that is the seat's call and the ratchet is red so that it gets made rather than assumed. B is the acceptable fallback and (d) is the weakest of the three if one must go."
    },
    {
    "question": "A fifth candidate line, and the two sources disagree about where it goes. The 03:28Z first-touch grading on #15647 says, inside its SECTION 1 item: 'Triage's fifth line - a count states the population it counted, as a reading states its UTC time and ref - lands with them as a readings-discipline rule.' But that same sentence is section 2's second principle gap verbatim ('a census must state its glob, or it is not a census'), and the grading's own item 2 routes both section-2 gaps to the decision box. This dispatch's ruling area enumerates candidates (a) through (f) only and says section 2 is explicitly not mine. I followed the dispatch and did NOT land a fifth line; I am flagging rather than choosing.",
    "options": [
    "A - the fifth line belongs in this flight and is missing from PR #15955; add it in a follow-up commit on the same branch before the PR is reviewed",
    "B - it is section 2 material, correctly held in the decision box awaiting the maintainer's A/B/C, and the grading's item 1 double-lists it in error",
    "C - it is genuinely both: the census-glob PRINCIPLE goes to the decision box for SKILL.md, and a narrower readings-discipline line belongs in this ledger"
    ],
    "recommendation": "B, weakly, on the STARTUP FOCUS and LONG-TERM SOUNDNESS axes: the decision box already holds it, the maintainer's ruling is pending on exactly that text, and landing a paraphrase of a rule that is simultaneously awaiting a verbatim ruling is how a corpus acquires two spellings of one decision - the failure the other section-2 gap is itself about. It costs nothing to wait. If the seat reads it as A, the branch is open and one more line is a one-commit follow-up - but note it would take the ratchet from RED by 3 to RED by 4."
    },
    {
    "question": "A conflict between this dispatch and the standing os-dev contract, named rather than silently resolved. The dispatch's ruling 4 directs measurement-first when the absent facts cannot be paid for: land them, let the ratchet read RED by design, report the exact count. The standing clause in .claude/agents/os-dev.md says the opposite for the same situation - 'the only legal currency for a line ratchet is deleting content ... if you cannot delete an equal amount of content, report blocked' - and it also forbids fold/re-wrap as a way to buy lines, which is the word ruling 4 uses. I followed the dispatch, because it ruled this exact case in advance, because measurement-first hands the seat a number that blocked would not, and because the one prohibition both agree on (never raise the ceiling yourself) I obeyed.",
    "options": [
    "A - ratify the dispatch's route: measurement-first is the correct handling for a zero-headroom governed ledger, and the os-dev clause should be narrowed to say so",
    "B - the os-dev clause governs and I should have reported blocked without opening a PR; treat PR #15955 as the exception the dispatch authorised, and keep blocked as the default",
    "C - leave both as they stand and let each dispatch decide per card"
    ],
    "recommendation": "A. On LONG-TERM SOUNDNESS and AI-ERROR PREVENTION, blocked and measurement-first are not equally informative: blocked returns a judgement, measurement-first returns a number plus a red gate that cannot be skimmed past, and the seat needs the number to write the ceiling ruling either way. C is the weakest - a rule that flips per dispatch is the shape that produced this conflict. Note that ruling 4's word 'folding' should be retired whichever way this goes, since re-wrap as a way to buy lines is prohibited outright and, on this file, mechanically impossible anyway."
    }
    ],
    "out_of_scope_findings": [
    "filed as #15957: the skill line ratchet has been printing an unclaimed 2-byte pin tighten for .claude/skills/spec-property-retirement/SKILL.md (max-table-row-bytes 328, widest row now 326) on every run; pre-existing on origin/main at a2051fa, not caused by this PR. Filed bare and unassigned with the finding label only, no pm:queue. Deduplicated first: /search/
    is 403 on this session, so the check went through the repository-scoped list endpoint plus a local grep over 189 open finding cards and 32 open domain:skills cards for max-table-row, table-row-bytes, spec-property-retirement, 326 and 328 - no match.",
    "NOT filed, reported instead because it is already documented: 'Lint & Repo Gates' runs its ~148 gate steps sequentially, so the ratchet failure at step 28 skipped steps 29-148 and this PR's CI carries NO verdict on them. AGENTS.md already states this property ('that job runs its gates sequentially, so the first stale artifact masks every one behind it'), so it is a known cost of the measurement-first route rather than a new defect - but it is a cost the seat is paying on this PR and should know about before reading the green ticks."
    ]
    }


    Generated by Claude Code

  5. os-steve commented on Sep 5, 2026

    @os-steve
    Collaborator

    Delivered in the shared flight as draft PR #15955 (Fixes #15917), with one correction the implementer measured: the payload block has no count field — the figure compared against totalCount is the length of edges, and the landed line says so; the rule otherwise stands (short means the newest items are missing; a claim check goes to the paged get_comments read; compare both arrays, because the tail array carries hasPreviousPage, not hasNextPage). The PR is red on the ledger's line ratchet by exactly 3 by design; the ceiling ruling is decision card #15960.

    Blocked-by: #15960

    pm:dispatched → pm:blocked for the wait; this card closes when the PR lands.


    Generated by Claude Code

  6. claude commented on Sep 6, 2026

    @claude
    Contributor

    Flight L resumes this card's flight (15:4xZ): #15960 ruled A, the ratchet ceiling 359 → 362 lands as the patch round on PR #15955 (branch claude/issue-15647-readings-increment-four, main merged in, ruledRaises record, the ruling quoted in the PR body, Fixes #15960 added). pm:blocked → pm:dispatched in this stroke; the claim of record is on #15960.


    Generated by Claude Code

  7. claude commented on Sep 6, 2026

    @claude
    Contributor

    os-dev-report

    {
    "issue": 15917,
    "status": "done",
    "branch": "claude/issue-15647-readings-increment-four",
    "pr": "#15955",
    "premise_still_valid": true,
    "summary": "This card's rule line was already authored on the branch and was NOT re-edited this round. What this round did was execute #15960's ruling A so it can land: the readings ledger's line ceiling moves 359 to 362 in scripts/pm/check-skill-line-ratchet.mjs, with the ruling quoted verbatim in PR #15955's body and one ruledRaises record carrying it, and origin/main merged into the PR's existing branch. The ratchet is now GREEN at 362/362. The landed line keeps the correction the implementer measured against this card's wording: the payload block has no count field - the figure compared against totalCount is the LENGTH OF edges - and it says compare BOTH arrays, because the tail array's pageInfo carries hasPreviousPage, not hasNextPage, so the pre-existing hasNextPage discriminant was structurally blind there. PR #15955 carries Fixes #15917, so this card closes when the maintainer merges it by hand (it is a governed, draft PR - no seat flips it ready, enqueues it, or arms auto-merge). Full report on #15960: #15960 (comment)",
    "tests": "Short form; the full evidence is in the #15960 report (#15960 (comment)). Headlines, all at commit 04b61b5 with every exit captured before any pipe: dispatch-gates derived 38 families from the real change set and --ran reconciles 38 derived famil(ies) accounted for - 38 run, 0 NOT-MEASURED; the ratchet self-test passes 155 cases and the live run prints .claude/skills/pm-dispatch/references/platform-readings.md is 362 lines (ceiling 362; headroom 0); whole-repo pnpm lint through the verify lock is VERDICT command-exit 0 - held the lock 104s; check-governed-merges --test exits 3 GOVERNED so the PR stays draft; merge-tree against flight B's f5c08e9 is exit 0 with zero CONFLICT lines both as configured and with the custom merge driver disabled. CI on the new head: 33 check runs, 25 success, 8 skipped, zero failures, zero still running.",
    "mcp_calls": "0 - all reads and writes went through authenticated REST in-container; no MCP GitHub call this run.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions