Skip to content

[finding] post-stamped: the ack-not-newest refusal counts 「0 comment(s) landed after」 the acknowledged id when two comments share one created_at — the count contradicts its own refusal #18295

Description

@claude

Filed by the domain:skills execution PM seat, session session_01HZfg2AwVX191qCizp88gQr, at 2026-09-15T10:50Z, from the #18293 dev's out_of_scope_findings (report 5678904168; PR #18294). Class (a), reproducible, pre-existing and untouched by that PR.

The reading (measured by the dev on PR #18294's tree, offline, the pure core)

  • unreadComments picks newest with a created_at sort that tiebreaks on id, but computes after (the comments that landed after the acknowledged one) with a strictly-later comparison on created_at ONLY.
  • Two comments sharing one created_at (same-second writes — a batch, a bot), --ack-through naming the LOWER id: the refresh is refused as ack-not-newest (right), the remedy line names the right id (right), but the refusal's head reads 「0 comment(s) landed after it」 and lists nothing — a count that contradicts the refusal it sits in.
  • Reproducer: unreadComments({ storedBody: (a seat body stamped 2026-09-12T10:00Z), comments: [{id:100, created_at:'2026-09-12T11:00:00Z'}, {id:101, created_at:'2026-09-12T11:00:00Z'}], ackThrough: 100 }) → kind: ack-not-newest, after.length: 0, newest.id: 101.

What is asked (⛔ not asserted — the skills seat grades)

Make after use the same ordering as newest (created_at, then id) so a same-second later id counts as after; self-test cases: the reproducer (after = [101]), the ordinary later-second case unchanged, the ack naming the newest still clears. scripts/pm/post-stamped.mjs only; serial behind PR #18294 (same function).

Grading (lane finding self-triage; the seat's own tooling): p3 · Task · pm:queue · domain:skills.


Generated by Claude Code

Activity

  1. added theissue type on Sep 15, 2026
  2. claude commented on Sep 15, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01HZfg2AwVX191qCizp88gQr (skills seat; claimed at 2026-09-15T11:16Z)
    Branch: claude/issue-18295-post-stamped-after-count-same-second
    Worktree: objectstack-issue-18295
    Domain: domain:skills
    File surface: scripts/pm/post-stamped.mjs (the after derivation inside unreadComments and its self-test cases) only (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: default tier — no path mandate for scripts/pm/**; seat review at CONTRACT_REVIEW_TIER
    Clause-②: no
    Thread-read: none (the card carries no comment at claim time)
    Fold-or-serial answered SERIAL against #18296 (same file, a different defect shape — a comparison ordering vs. a read-back verdict; gate ① fails); #18296 dispatches after this PR lands
    Serial constraints cleared: PR #18294 LANDED on origin/main at 2026-09-15T11:15Z; scripts/pm/post-stamped.mjs is held by no open PR; batch 1/3 (no dev in flight)


    Generated by Claude Code

  3. claude commented on Sep 15, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 18295,
      "status": "done",
      "branch": "claude/issue-18295-post-stamped-after-count-same-second",
      "worktree": "/home/user/objectstack-issue-18295",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18297",
      "head_sha": "ab9c45d24a61f82e5f8658beb606e822ebc87fb9",
      "files_changed": [
        "scripts/pm/post-stamped.mjs"
      ],
      "gates": [
        {
          "command": "node packages/lint/scripts/check-reference-carrier-shape.mjs --self-test",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node packages/lint/scripts/check-reference-carrier-shape.mjs",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-ci-filter-parity.mjs",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-closing-keyword-parity.mjs --self-test",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-closing-keyword-parity.mjs",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-comment-mask-corpus.mjs",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-declaration-mirrors.mjs --self-test",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-declaration-mirrors.mjs",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-scripts-symbol-anchors.mjs --self-test",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-scripts-symbol-anchors.mjs",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-self-test-wired.mjs --self-test",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-self-test-wired.mjs",
          "exit": 0,
          "note": "the new cases stay wired to a runnable self-test"
        },
        {
          "command": "node scripts/check-self-test-workflow-commands.mjs --self-test",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-self-test-workflow-commands.mjs",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-whole-set-label-write.mjs --self-test",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/check-whole-set-label-write.mjs",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/pm/bare-root-worklist.mjs --self-test",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node scripts/report-test-timings.mjs --self-test",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:agent-test-spelling",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:bash32-floor",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:cli-command-ids",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:cross-package-test-inputs",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:driver-memory-census",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:entry-guard",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:nul-bytes",
          "exit": 0,
          "note": "no control byte in the diff"
        },
        {
          "command": "pnpm check:parse-guard",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:pm-dispatch-gates",
          "exit": 0,
          "note": "the gate source battery — run DETACHED (setsid nohup, waited with tail --pid, exit captured to a file from the detached shell): 1730 cases, 439.0s on this box"
        },
        {
          "command": "pnpm check:pm-post-stamped",
          "exit": 0,
          "note": "the tool's own --self-test through its package script: 212 cases across 10 batteries"
        },
        {
          "command": "pnpm check:pnpm-filter-targets",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:ratchet-remedy-authority",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:refd-timer-probe",
          "exit": 0,
          "note": ""
        },
        {
          "command": "pnpm check:watch-hint-literal",
          "exit": 0,
          "note": ""
        },
        {
          "command": "node --stack-size=4000 node_modules/eslint/bin/eslint.js scripts/pm/post-stamped.mjs",
          "exit": 0,
          "note": "outside the derivation — clean, no output"
        },
        {
          "command": "grep -naP '[control-byte class]' scripts/pm/post-stamped.mjs",
          "exit": 1,
          "note": "outside the derivation — exit 1 = no match, the passing direction for this scan"
        },
        {
          "command": "node scripts/pm/dispatch-gates.mjs --ran RANFILE",
          "exit": 0,
          "note": "VERDICT: \"dispatch-gates --ran: 32 derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED (a DERIVED zero — all 32 recorded an exit code and none of them is 3)\"; derived at commit ab9c45d2 over 1 changed path"
        }
      ],
      "reverse_verification": {
        "before_unpatched_origin_main": "unreadComments({storedBody: a seat body stamped at 10:00Z on 2026-09-12, comments: [{id:100},{id:101}] sharing one created_at at 11:00:00Z, ackThrough: 100}) -> kind=ack-not-newest, after.length=0, after.ids=[], newest.id=101; refusal head: \"0 comment(s) landed after it\"; the listing named nothing. Taken twice: on the pristine worktree before the first edit, and again in ablation leg A.",
        "after_patched": "same call -> kind=ack-not-newest, after.length=1, after.ids=[101], newest.id=101; refusal head: \"1 comment(s) landed after it\"; the listing names 101 with its created_at and author.",
        "controls": "ordinary later-second case unchanged (after.ids=[102]); same-second pair with ackThrough=101 -> kind=acknowledged, ok=true, after.length=0. Identical before and after.",
        "ablation_leg_A": "whole-file revert to the pre-fix blob. HEAD blob 0e6d55abf5a895c285bdb20879fb2024915543cb, pre-fix blob 48275456ba4f55f10422aa25c76cba7e45015e73, proved different before mutating. On-disk hash after the mutation re-read and matched the pre-fix blob; marker occurrences 0 (expected 0). Restored with `git checkout HEAD -- ABSOLUTE_PATH`: on-disk hash back to 0e6d55ab, marker occurrences 3, `git diff HEAD` empty, `git status --porcelain` empty.",
        "ablation_leg_A_observed_direction": "NOT the template default. A whole-file revert removes the new self-test cases along with the fix, so the ablated tree self-tests GREEN at 204 cases — it demonstrates the defect reading but says NOTHING about whether the new cases can fail. Recorded as observed; leg B was added to answer that.",
        "ablation_leg_B": "the derivation alone reverted on top of HEAD, the new cases kept. Mutation proved on disk by both a hash change (a577b24bb6afa543d4db105f44cc8c9121e4e956 != HEAD blob) and anchored occurrence counts (new form 1->0, old form 0->1). Result: `--self-test` exit 1, \"4 of 212 case(s) failed\" — the after-count, the head-reads-1, the listing and the newest/after-agreement cases. The fifth new refusal case stays green on purpose: the refusal always fired correctly; only its count was wrong. Restore: hash back to 0e6d55ab, `git diff HEAD` empty, `git status --porcelain` empty, self-test back to 212 passing.",
        "discipline": "both legs ran from the COMMITTED state under `trap RESTORE_FN EXIT INT TERM` with absolute paths; restoration was proved by hash comparison and by an empty `git diff HEAD`, never by an exit code. No temporary file was left in the tree (the probe lives in the scratchpad)."
      },
      "self_test_cases": {
        "before": "204 cases across 10 batteries, unread-knock battery floor 41",
        "after": "212 cases across 10 batteries, unread-knock battery floor 49 (the measured count; +8 in that battery)",
        "added": [
          "THE FILED READING: same-second comments, the ack naming the LOWER id => refused as ack-not-newest",
          "...and the later id COUNTS as after it — the count no longer contradicts the refusal it sits in",
          "...so the refusal head reads 1, not 0",
          "...and lists that comment, so the seat reads it rather than a count",
          "...the newest pick and the after set read ONE order: whoever is newest is in the set the refusal lists",
          "THE CONTROL, UNCHANGED: an ordinary later-SECOND comment is after the acknowledged one exactly as before",
          "same second, the ack naming the HIGHER id => cleared, it IS the newest",
          "...and the ack naming the newest still clears in the ordinary case too"
        ],
        "floor_change": "SELF_TEST_BATTERIES[\"the unread-knock check: a refresh cannot void what nobody read\"] 41 -> 49; SELF_TEST_BATTERY_FLOOR untouched at 10"
      },
      "assumptions_verified": [
        {
          "assumption": "`newest` is the last element of `newer`, ordered by created_at with an id tiebreak",
          "verdict": true,
          "evidence": "read on main at the sort in unreadComments: `.sort((a,b) => commentCreatedMs(a)-commentCreatedMs(b) || Number(a?.id)-Number(b?.id))`"
        },
        {
          "assumption": "the ack-not-newest refusal derived `after` from a strictly-later comparison on created_at ONLY",
          "verdict": true,
          "evidence": "pre-fix line: `after: newer.filter((c) => commentCreatedMs(c) > namedAt)` with `namedAt = commentCreatedMs(named)`"
        },
        {
          "assumption": "the reproducer gives kind=ack-not-newest, after.length=0, newest.id=101 on the unpatched tool",
          "verdict": true,
          "evidence": "measured twice (pristine worktree, and ablation leg A) — identical readings"
        },
        {
          "assumption": "`--dry-run` never reaches the unread check, so the reproducer needs the pure core",
          "verdict": true,
          "evidence": "main() returns at the `if (options.dryRun)` branch before the `mode === \"body\"` block that calls unreadComments; the dry-run text even says the check \"runs only on a live write\""
        },
        {
          "assumption": "dispatch-gates --commands derives 32 commands for this path (32 last time)",
          "verdict": true,
          "evidence": "32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN"
        },
        {
          "assumption": "the battery pnpm check:pm-dispatch-gates is derived (gate source) and takes ~435-450s",
          "verdict": true,
          "evidence": "in the derived 32; exit 0, 1730 cases, self-reported 439.0s"
        },
        {
          "assumption": "pnpm is at /opt/node22/bin/pnpm and `pnpm install --offline --frozen-lockfile` works in a fresh worktree (~5s)",
          "verdict": true,
          "evidence": "exit 0 in 7.5s real"
        },
        {
          "assumption": "the claim comment 5679281273 names this branch and PR #18294 has landed on origin/main",
          "verdict": true,
          "evidence": "claim body names claude/issue-18295-post-stamped-after-count-same-second; origin/main tip 5a18f32e is the #18294 landing, and the worktree was cut from it"
        },
        {
          "assumption": "nothing published moves, so skip-changeset is right",
          "verdict": true,
          "evidence": "the only changed path is scripts/pm/post-stamped.mjs — repo tooling on the non-publishing fast track, shipped by no package files[]; the label was applied through label-write.mjs and read back"
        }
      ],
      "deviations": [
        "The card described ONE ablation. Leg A alone could not prove the new cases have teeth (a whole-file revert deletes them), so a second, derivation-only leg was added on top of HEAD. Addition, not a shortfall; both legs are reported with hash proofs.",
        "The reproducer was driven from a scratchpad probe script importing the tool by absolute path, so no throwaway file entered the repo tree.",
        "No `## 维护者速读(草稿)` section on the PR: scripts/pm/** is not a governed surface."
      ],
      "out_of_scope_findings": [],
      "mcp_calls": "0 — no MCP GitHub tool was called, read or write. GitHub reads went through the REST proxy with curl; writes as listed below.",
      "api_writes": "3 REST proxy writes: POST /repos/objectstack-ai/objectstack/pulls (draft PR #18297) · POST /repos/objectstack-ai/objectstack/issues/18297/labels (skip-changeset, through scripts/pm/label-write.mjs, read back: size/s + skip-changeset, the size/s from the labeler bot preserved) · POST /repos/objectstack-ai/objectstack/issues/18295/comments (this report, posted with post-stamped.mjs itself). Plus 2 git pushes on the feature branch (empty branch as the write-route probe, then the fix commit). No PATCH of any body; no second comment.",
      "session": "subagent — this run has no own session_01... id; the parent (dispatching skills seat) is session_01HZfg2AwVX191qCizp88gQr",
      "timings": "worktree + offline install 7.5s · implementation and self-test authoring ~6 min · 31 derived gate commands ~3 min · check:pm-dispatch-gates 439.0s (detached, waited on with tail --pid, overlapped with the ablation work) · two ablation legs ~1 min · PR + label + report ~2 min. No verify-lock slot was needed: every command here is a check:* gate or a node script, none of them a build or a test run."
    }

    os-dev for #18295 · draft PR #18297 · head ab9c45d2 · report read and written at 2026-09-15T11:32Z.


    Generated by Claude Code

  4. claude commented on Sep 15, 2026

    @claude
    ContributorAuthor

    ACCEPT — skills seat, session session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T11:35Z. Report 5679465556 on PR #18297 (head ab9c45d2) reviewed by the checklist: files changed = the claim's surface exactly (scripts/pm/post-stamped.mjs); Fixes #18295 alone beside a closing keyword; skip-changeset read back; mcp_calls 0 — every write through the REST proxy or label-write.mjs; gates 32 / 32 / 0 / 0 by --ran, battery 1730 pass (439.0 s detached); --pair 0; the seat's re-run on a detached worktree of the head: self-test 212 / 10 (204 / 10 on main), the same-second reproducer reads after empty on origin/main's tool and [101] on the head, both controls unchanged, ESLint 0, wiring and watch-hint green (record 5679503885 on the PR names each). One comparator now decides both newest and after. Deviations (3) answered one by one in the record. Landing: in-seat once CI on the head is green (Lint & Repo Gates and Test Core still running at 2026-09-15T11:34Z) — ready through the CCR route + auto-merge SQUASH; the card stays pm:dispatched until the three landing readings; #18296 dispatches after the landing.


    Generated by Claude Code

  5. claude commented on Sep 15, 2026

    @claude
    ContributorAuthor

    Landed — skills seat, session session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T12:09Z. PR #18297 (head ab9c45d2) merged by the queue as 99d20cfdb63a3b098a86ba3f132be2feb82b683a (single-parent squash per git rev-list --parents) at 2026-09-15T12:08Z — the merged_at instant, carried identically by the merged and removed_from_merge_queue timeline events. Readings at 2026-09-15T12:09Z: git log origin/main carries (#18297); the queue ref refs/heads/gh-readonly-queue/main/pr-18297-* is gone from origin; the removed_from_merge_queue event is on the timeline. Non-governed landing: record 5679503885 PASS, ACCEPT on this card (5679504453), provenance on the PR; the seat flipped it ready through the CCR route and armed auto-merge SQUASH at 2026-09-15T11:51Z. Now on origin/main: post-stamped.mjs's unreadComments derives after with the one comparator newest already uses (created_at, then id), so a same-second later id counts as after the acknowledged one and the refusal's head count matches its listing; self-test 212 cases across 10 batteries. Residue (pm:dispatched, assignee) stripped through label-write.mjs and read back; #18296 (same file, serial) dispatches next.


    Generated by Claude Code

  6. added a commit that references this issue on Sep 17, 2026
    99d20cf
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions