Skip to content

board-snapshot: archive OPEN records first and move the cron off the patrol's minute — the first walk is oldest-first, so the cards a suspension would destroy are archived last, and both workflows spend one per-repo token hour #17434

Description

@os-litant

Filed by the skills seat (session session_01YKEjmbYNvYWJvWGSWx26zK) from the first scheduled run of the archiver that #17390 landed (PR #17404, 456a0afd), read at 13:5xZ on 2026-09-10; routed domain:skills · pm:queue · priority:p1 · Task (the tool is this lane's; the reading is the run's own manifest, not inferred).

Measured (run 34484192186, schedule, success)

  • board/manifest.json on board-archive (16259758): first run full, 800 requests spent (stopped_by: budget), 405 numbers read — 137 closed issues + 268 closed PRs, 0 open, walk_complete: false, resume cursor 2026-02-01T14:43:39Z. The listing walk ascends by update time from the beginning of the board, so the records that matter most for the purpose the card was filed for — the OPEN cards a suspension destroys (共享身份的限流纪律不存在:一次限流信号约束的是「身份」不是「客户端」,而规矩只说了不要重试 —— 2026-09-10 全 fleet 停摆事故 #17374 F3: branches survive, records do not) — come last. At ~405 numbers per 800-request run and four scheduled runs a day, the open set (598 open issues at 13:5xZ + open PRs) is days away from being archived.
  • .github/workflows/board-snapshot.yml schedules 37 1,7,13,19 * * * — copied from the half-state patrol's cadence, which is the SAME minute. Both run under the repository's GITHUB_TOKEN, whose rate limit is per repository per hour (1,000 requests), so a snapshot run that spends 800 leaves the patrol its remainder in the same window (the 13:37 patrol run's outcome is the first data point — read it before choosing the offset).

Deliverable

  1. Open first. The first (and any resumed-first) walk archives state=open issues and PRs completely before it starts the closed history; incremental runs (a since from a previous manifest) keep the single since walk. State the order in the manifest (walk_phase or equivalent) so a reader can tell "open set complete, history resuming" from "still in the open set". Keep the page-walk rule (short page ends the walk; re-anchor on the last row) and the resume cursor semantics.
  2. Cron offset. Move the schedule off the patrol's minute and hour set — e.g. 7 4,10,16,22 * * * — so the two standing callers never share a token hour; keep four runs a day and the workflow_dispatch entry.
  3. Budget statement. The workflow header documents the shared per-repo GITHUB_TOKEN hour and which other workflows spend it (the patrol, this one), with the per-run cap (800) chosen against it. ⛔ No retry loop, no cap raise.
  4. Self-test cases for the open-first ordering and the phase field; the offline fixtures cover a first run whose open set does not fit one budget (the open set resumes before the history starts).

Acceptance

  • A workflow_dispatch run after landing (or the next scheduled run) reports the open set complete in its manifest (issues_open = the repo's open_issues_count minus open PRs at that stamp, pulls_open = the open PR count) before any closed record newer than the cursor is added; the count check turns from pending to a verdict once the open set is complete, or the manifest says exactly why not.
  • Negative control: the incremental path (a manifest with next_since) is unchanged — an identical re-run still writes nothing.
  • The patrol's next scheduled run after the offset lands is green and its anchor's Swept line advances (the standing caller was not starved).

Governing text

#17390 (the card: what the archive is for), #17374 F3/F4 (which records a suspension destroys), the one-board rule (the archive is read-only; nothing here adds a write path to GitHub).

Dedup

Lane titles at 13:5xZ: #17390 (closed, landed — the tool), #17374 (the incident); no card names the walk order or the cron collision. Nearest: the half-state patrol's own header on GITHUB_TOKEN budget (its H-rows read the board four times a day).


Generated by Claude Code

Activity

  1. added theissue type on Sep 10, 2026
  2. self-assigned this
    on Sep 10, 2026
  3. os-litant commented on Sep 10, 2026

    @os-litant
    CollaboratorAuthor

    Claim: PM loop round 1
    Session: session_01YKEjmbYNvYWJvWGSWx26zK (GitHub os-litant, skills seat), claimed at 2026-09-10T14:01Z
    Branch: claude/issue-17434-board-snapshot-open-first
    Worktree: objectstack-issue-17434
    Domain: domain:skills (PM-loop tooling; follow-up to #17390 from its first run's manifest)
    File surface (region-declared): scripts/pm/board-snapshot.mjs (the walk order, the manifest phase field, self-test cases) and .github/workflows/board-snapshot.yml (cron + header); ⛔ nothing else — not lint.yml, not package.json, not .claude/** (stop on breach; explain in the report)
    Container & model: M (a walk-order change with its resume semantics), mode:subagent, model: opus (default tier; no path mandate — the two files derive none); review = skills-seat self-review at the contract-review tier.
    Clause-②: no — internal tooling; no accept set or public surface moves.
    Thread-read: none (0 comments at claim time; body read in full — this seat wrote it).
    Serial constraints cleared: both files landed at 456a0afd and no open PR touches them; no dev in flight on this lane; the three governed PRs at the terminal (objectos#283, #17368, #17415) touch other files; H17 hold index names neither path; verify-lock free.


    Generated by Claude Code

  4. os-litant commented on Sep 10, 2026

    @os-litant
    CollaboratorAuthor

    Dispatched — R1 wave 4, 2026-09-10T14:02Z, mode:subagent, build tier opus (no path mandate), skills-seat review at the contract-review tier. Claim 5619917192 is the dev's identity; branch claude/issue-17434-board-snapshot-open-first, cut from 92242041. Readings carried: the first run's manifest (405 records, 0 open, cursor at 1 Feb 2026, walk_complete: false), the landed listing order (state=all&sort=updated&direction=asc), and the shared cron minute — both the 13:37Z patrol run and the 13:41Z snapshot run were green today, so the offset is prevention. Terminal: not governed ⇒ seat review → ready → queue. In flight on the lane: 1.


    Generated by Claude Code

  5. os-litant commented on Sep 10, 2026

    @os-litant
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 17434,
      "status": "done",
      "branch": "claude/issue-17434-board-snapshot-open-first",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17442",
      "premise_still_valid": true,
      "summary": "The archiver's first walk now runs an OPEN phase to completion before the closed history: the /issues listing is asked for state=open (it carries pull requests too), walked to a short page with the existing value re-anchor, and only then does the state=all history walk start from its own cursor. The two cursors are kept apart, so a budget stop inside the open set resumes inside it while the history cursor waits untouched; walk_phase plus walk.open_set.complete separate 'open set complete, history resuming' from 'still inside the open set', and resume.phase records which walk a stop belongs to. An archive written before this order existed (the one on board-archive right now) re-walks the open board first and KEEPS its Feb-1 cursor as the history cursor rather than discarding it. The cron moves off the half-state patrol's minute and hour set. The workflow header now names the shared 1,000/hour per-repository GITHUB_TOKEN pool, every standing spender on it, and why the per-run cap is 800.",
      "tests": "All at head 9d2c7b83, exit codes captured before any pipe. SELF-TEST: pnpm check:pm-board-snapshot exit 0 -- 'OK board-snapshot self-test: 89 cases pass across 8 batteries' (70 before; all 70 still present, the 7 that named selectSince now name selectWalkPlan with their case names and assertions unchanged). GATES: node scripts/pm/dispatch-gates.mjs --commands derived 51 families on the real 2-path diff; all 51 run, each recorded as 'command :: exit N'; reconciled with --ran, which printed '51 derived famil(ies) accounted for -- 51 run, 0 NOT-MEASURED (a DERIVED zero -- all 51 recorded an exit code and none of them is 3)', exit 0. check:pm-dispatch-gates ran detached with a foreground tail --pid wait, exit read from the captured file: 0, log verdict 'dispatch-gates self-test: 1678 cases pass'. LINT: pnpm lint (eslint . --no-inline-config) run in FULL, not narrowed -- exit 0, no output; npx eslint --no-inline-config on the changed script also exit 0. GOVERNED: scripts/pm/check-governed-merges.mjs --test on the final 2-path file list said 'NOT governed -- ordinary queue landing applies', exit 0. BYTES: check:nul-bytes exit 0 plus a manual grep -naP over both files for the wider control-character class, no match (grep exit 1). LIVE DRILLS (narrow window, user token, no full-board pull) -- BEFORE/AFTER of the walk order, same flags, two builds: origin/main with --limit=20 read 20 numbers in 49 requests and archived 1 closed issue plus 19 closed pull requests from January, 0 open, cursor 2026-01-20T03:06:53Z; this branch with --limit=20 read 20 numbers in 19 requests and archived 20 OPEN issues, 0 closed, phase 'open', cursor 2026-08-18T04:39:33Z. RESUME: a second run with --limit=60 planned 'resuming the open set at 2026-08-18T04:39:33Z -- the closed history does not start until it is complete', read 60 more in 55 requests, stayed in the open phase, resume.phase 'open'. ARITHMETIC: 79 unique open issues for 74 requests = 0.94 requests per number; the board read in the same drill = open_issues_count 596 minus 14 open pull requests = 582 open issues; 582 x 0.94 plus 14 open PRs x 3 plus 7 listing pages plus 2 for the board's own arithmetic = about 598 requests, inside the 800 cap, so the open set fits ONE run. NEGATIVE CONTROL: the incremental path re-run identically (--since far in the future, phase 'incremental') -- run 1 'written 0 file(s) + manifest', run 2 'written 0 file(s)' plus 'idempotent nothing changed on the board since the last run', and the stored manifest's board.read_at stayed at run 1's instant. ABLATION of the materialManifest fix, both legs from the committed state: mutation proved on disk (marker count 2 to 0; blob 8a20b0597a4e09265e0cb29fdd7d2d3bfb655829 to 2470002619d8fe67e2273c1844273e57f668af51), MUTATED run 2 said 'written 0 file(s) + manifest' (the no-op commit every run); restore by git checkout HEAD -- path, proved by hash-object back to 8a20b059 and an empty git diff HEAD; RESTORED run 2 said 'written 0 file(s)' plus idempotent. Direction: turns red under the ablation, green on restore. The script has no build stage -- node executes the source path the drill names, so the on-disk proof is the whole proof; a trap on EXIT INT TERM held the restore, on absolute paths.",
      "mcp_calls": "3 -- mcp__github__issue_read x3 (the card body, the card's comments, and #17390's comments). Everything else went over repo-scoped REST after the branch push probed the route green: the drills, the PR create, the body read-back, the label write and its read-back, and this report comment.",
      "rest_requests": "147 -- 141 spent by the tool across 9 drill runs (49 + 19 + 55 + 3 + 3 + 3 + 3 + 3 + 3) and 6 by hand (PR create, PR body read-back, one 415 label attempt, the label write, two label reads). No full-board pull on the user token.",
      "gates": "51 derived, 51 run, 0 NOT MEASURED, 0 unrun; every family recorded its own exit code and none is 3; reconciled with --ran, exit 0. Outside the derivation: pnpm lint in full (exit 0) and check-governed-merges --test (NOT governed).",
      "line_budget": "scripts/pm/board-snapshot.mjs 1351 to 1616 lines (+353 / -88 as the PR counts it); .github/workflows/board-snapshot.yml 250 to 302 lines (+68 / -16). Two files, both region-declared on the claim; nothing else touched -- not lint.yml, not package.json, not .claude/**.",
      "deviations": [
        "CRON VALUE, deviating from the card's suggested '7 4,10,16,22' on a measurement: shipped '7 2,8,14,20'. The binding half of item 2 (no shared token hour with the patrol's 1,7,13,19, four runs a day, workflow_dispatch kept) holds either way. Of the five qualifying hour sets, 2,8,14,20 is the least contended on this repo's cron inventory (22 scheduled workflows read out of .github/workflows/): 08, 14 and 20 UTC carry no other scheduled workflow at all and 02 carries only the platform-checklist watchdog at :51 plus Monday CodeQL, while 4,10,16,22 drops an 800-request run into the busiest scheduled hour on the board (04:00 rerun-safety, 04:19 release-coverage patrol, 04:23 required-set patrol, 04:37 branch reaper on Mondays, 04:41 create-smoke) and shares hour 16 with the required-set patrol. The inventory table is in the PR body; a one-token change in review if the seat prefers the suggested value.",
        "ZONE 2C HALF FALSIFIED, and the check's own contract kept as the card instructs. The verdict does appear once the open set is complete, but only in the run that ENUMERATED it: the history walk takes days, every card closed or opened during it is still archived in its old state, so a verdict in a later history-phase run would report the clock as a board defect -- and a permanently red alarm is silence. The pending predicate therefore moved from 'this run stopped' to 'this run did not enumerate the open board', which is strictly more precise, and every pending verdict now carries the reason it is pending. The run that finishes the open phase reads the board's own arithmetic right there, which is also the tightest instant the two sides of the comparison can name.",
        "BOUNDED IN-PLACE FIX in the same file, required by the card's own negative control (item 4's 'an identical re-run writes nothing'): materialManifest stripped the three top-level run stamps, but board.read_at is nested and escaped it, so a steady-state run whose whole job is to write nothing would move that stamp and commit a manifest-only diff on EVERY scheduled run -- burying the real diffs exactly the way the file's header promises it never will. Same defect class, mechanical, no other claim on the file, no new verification surface. Ablation evidence in the tests field and in the PR body.",
        "selectSince was REPLACED by selectWalkPlan rather than kept beside it: two cursor decision functions would be a fork, and the phase and the cursor are one decision. Its 7 self-test cases kept their names and their assertions verbatim against the new callee, so no coverage was traded.",
        "countCheck's `resuming` boolean became `pending` (a reason string or null) and the returned object gained a `reason` key, null on every verdict branch so the key set and the manifest diff stay stable. Callers outside this file: none.",
        "ZONE 2A MEASURED and it holds, tighter than the estimate: 0.94 requests per number over 79 open issues gives about 598 requests for the whole open set (582 open issues plus 14 open PRs at 14:20Z), so it fits ONE 800-request run rather than 'one or at most two'. The resume-inside-the-open-phase path is still drilled and self-tested, because a busier board would land it in a second run.",
        "ZONE 2B MEASURED and it holds: an open-phase stop stores walk.open_set.cursor and resume.phase 'open' and the next run continues the open walk; the history cursor is a separate field and is untouched while that happens.",
        "ZONE 2D holds and is untouched: the PR-time dry run is still --out=board --dry-run --limit=5 --max-requests=25 into the runner's temp dir with no archive checkout and no commit. What the phase change moves is WHICH 5 numbers it reads (open instead of closed), not the cap.",
        "MIGRATION the next scheduled run will take, worth knowing before the acceptance run: the manifest on board-archive was written before this order existed, so selectWalkPlan reads it as legacy, plans the open phase, and preserves its 2026-02-01T14:43:39Z cursor as the history cursor. The 405 records already archived are neither re-read nor lost. Self-tested as its own case.",
        "COMMIT TRAILERS are AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude), not the harness-supplied model-named line: AGENTS.md forbids a model identifier in any repo artifact and outranks the harness note by its own terms.",
        "PR BODY FOOTER, one more reading for the create cell: a REST create whose sent body already ends in the rule line plus the session-URL footer stored it byte-identically with exactly ONE footer and appended nothing -- the stored body is the sent body minus its trailing newline (11647 to 11646 bytes, no other differing offset). Read back after the write, as the rule requires. The body was NOT edited afterwards, so the final gate numbers live in this report rather than in a second write that would have cost a second footer."
      ],
      "files_changed": [
        "scripts/pm/board-snapshot.mjs (the open phase, the two cursors, the manifest walk block, the pending predicate, the nested-stamp fix, 19 new self-test cases)",
        ".github/workflows/board-snapshot.yml (cron 37 1,7,13,19 becomes 7 2,8,14,20; the shared-budget statement; the walk-order section)"
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: a --limit drill can never end green -- the count check correctly reports a SHORTFALL against a deliberately partial scratch archive and exits 2, so a drill flag and a scheduled run share one exit register. Successor: whoever next adds a flag to this tool.",
        "noted, not filed: a card closed on the board WHILE the open phase is walking stays archived as open until the history walk reaches it, so the first non-pending verdict can carry a surplus equal to the walk-window churn. Inherent to comparing a windowed enumeration with a point-in-time count, and the board's arithmetic is now read at the tightest available instant to keep that window minimal. Successor: whoever acts on the first non-pending verdict."
      ]
    }

    Generated by Claude Code

  6. os-litant commented on Sep 10, 2026

    @os-litant
    CollaboratorAuthor

    ACCEPT — PR #17442 (head 9d2c7b83) reviewed in-seat at the contract-review tier (skills seat, session session_01YKEjmbYNvYWJvWGSWx26zK, 2026-09-10T14:49Z).

    • Implemented-by: os-dev subagent on branch claude/issue-17434-board-snapshot-open-first (claim 5619917192; default build tier; mode:subagent). Reviewed-by: session_01YKEjmbYNvYWJvWGSWx26zK (the skills seat) — independence pair holds; the seat's harness reading is the tier evidence.
    • PR shape: draft; base main; first line Fixes #17434; skip-changeset. Changed files read from the PR: exactly the two declared — scripts/pm/board-snapshot.mjs (+353/−88) and .github/workflows/board-snapshot.yml (+68/−16). Path face: check-governed-merges.mjs --test ⇒ NOT governed (run by this seat on a detached review worktree at 9d2c7b83) ⇒ this seat lands it through the queue.
    • Diff vs the card's four items: ① open first — selectWalkPlan replaces selectSince (its 7 cases kept verbatim against the new callee); the first walk asks /issues?state=open (issues and PRs in one listing) to a short page with the value re-anchor, then the state=all history from its own cursor; two cursors kept apart (walk.open_set.cursor / walk.history.cursor), walk_phase = open / history / incremental, resume.phase on a stop; a legacy manifest (today's board-archive) re-walks the open board first and keeps its Feb-1 cursor as the history cursor — the 405 archived records are neither re-read nor lost (self-tested). ② cron 37 1,7,13,19 → 7 2,8,14,20 — the binding half holds (no token hour shared with the patrol, four runs a day, workflow_dispatch kept); the dev's deviation from the suggested 4,10,16,22 is accepted on its measurement (22 scheduled workflows read: 08/14/20 carry no other schedule, 02 only the checklist watchdog; 04 is the busiest hour on the board). ③ the header names the per-repo 1,000/h GITHUB_TOKEN pool, its standing spenders and the 800 cap; ⛔ no retry loop, no cap raise. ④ self-test 70 → 89 cases (open-first order, the phase field, a resume inside the open phase, the incremental path unchanged).
    • Verified by this seat at 9d2c7b83: --self-test 「89 cases pass across 8 batteries」 exit 0; still exactly one fetch call site and no mutating method literal (the one-board structural cases). CI at 14:48Z: 34 check runs — 21 success, 11 skipped, 2 in progress (Lint & Repo Gates, Type Check · workspace); the PR-time dry run 「Snapshot the board to the archive branch」 success. Dev gates: 51 derived families run, 51 exit 0, --ran reconciled; pnpm lint full exit 0; check:pm-dispatch-gates 1678 pass.
    • Drills accepted from the report (narrow-window, user token, 147 REST requests total): before/after with --limit=20 — main archived 0 open / 20 closed from January; the branch archived 20 open, phase open, 19 requests; a resumed --limit=60 stayed in the open phase; arithmetic 0.94 requests per open issue ⇒ the whole open set (582 issues + 14 PRs at 14:20Z) ≈ 598 requests, inside one 800-request run. Negative control: the incremental path writes nothing on an identical re-run. Ablation of the nested-stamp fix: mutated ⇒ a manifest-only write every run; restored ⇒ idempotent — the bounded in-place fix (board.read_at escaping materialManifest) is the card's own negative control, accepted.
    • Zone-2: A measured and tighter than estimated (one run); B holds; C half-falsified and the check's own contract kept — pending now means "this run did not enumerate the open board", with the reason carried; D holds (the PR-time dry run untouched, 5 numbers / 25 requests). Deviations accepted as declared (the cron value; countCheck's resuming → pending with a reason key, no external callers).
    • noted, not filed (accepted as noted): a --limit drill always ends in a SHORTFALL exit 2 by design; a card closed during the open walk carries a surplus until the history walk reaches it.
    • Terminal: not governed ⇒ landing by this seat: flip ready → auto-merge/queue once every check on 9d2c7b83 is green (flip point armed); on MERGED: landing record here + pm:dispatched / assignee cleared; the next scheduled run (7 2,8,14,20, next slot 20:07Z, or a workflow_dispatch after landing) is the acceptance run — read its manifest for open_set.complete and the first non-pending count verdict.

    Generated by Claude Code

  7. os-litant commented on Sep 10, 2026

    @os-litant
    CollaboratorAuthor

    Landed — PR #17442 merged through the merge queue at 2026-09-10T15:32Z (merge commit fd62a66b, head 9d2c7b83), not governed (Governed Surface Guard green on the queue head): ACCEPT 5620609684 → every check on the head green 15:08Z (24 success / 11 skipped) → flipped ready 15:10Z → auto-merge → added_to_merge_queue 15:10Z (timeline; queue branch gh-readonly-queue/main/pr-17442-… at fd62a66b) → merged. Verified on origin/main at 2026-09-10T15:37Z: scripts/pm/board-snapshot.mjs and .github/workflows/board-snapshot.yml carry the open-first walk (selectWalkPlan, walk_phase, the two cursors) and the cron 7 2,8,14,20 * * * with the budget header; the merge is the queue's squash (one parent). Residue cleared in this same act: pm:dispatched off, assignee off; domain:skills, the priority, tooling and the type stay. The card was closed by the merge. Acceptance run: one workflow_dispatch of the Board Snapshot workflow fired at 2026-09-10T15:37Z (run 34496826690) — the next reading is its manifest on board-archive: walk_phase, open_set.complete, and the first non-pending count verdict; expected from the PR's arithmetic: the whole open set (~596 numbers) inside this one run, the count check turning to a verdict, the history walk resuming from the kept Feb-1 cursor. If the manifest reads otherwise, that is a new finding on this lane, not a reopen. Skills seat, session session_01YKEjmbYNvYWJvWGSWx26zK, R1.


    Generated by Claude Code

  8. removed their assignment
    on Sep 10, 2026
  9. os-litant commented on Sep 10, 2026

    @os-litant
    CollaboratorAuthor

    Acceptance run read — workflow_dispatch run 34496826690 (fired 2026-09-10T15:37Z on fd62a66b) completed success at 2026-09-10T15:40Z; archive commit 52bbc3a3 on board-archive; manifest blob 8be7b4c5 read at 2026-09-10T15:49Z: walk_phase: history; open_set.complete: true, completed inside this run (open_set_completed_here: true, 15:39:49Z); count_check.verdict: ok — expected 580 = archived 580, shortfall 0, surplus 0, gone_ledger 0 (board read at 15:39:50Z: 598 open issues including 18 open PRs); run: 665 numbers read, 1294 files written; the history walk resumed from the kept February cursor and advanced it to 2026-02-05T16:54:36Z before the 800-request budget stopped it (stopped_by: budget). Every reading matches the PR's arithmetic — the open set inside one run, the count check turning from pending to a verdict, the history resuming from the kept cursor — ⛔ no finding. The open board is archived whole from this run on; the closed history keeps walking four times a day at 7 2,8,14,20, and --restore N on the archive is the recreate path if the board is ever lost. Skills seat, session session_01YKEjmbYNvYWJvWGSWx26zK, R1.


    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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions