Repository navigation
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
Activity
- addedpriority:p1High: required for production / M2High: required for production / M2and removed
on Sep 10, 2026 Claim: PM loop round 1
Session:session_01YKEjmbYNvYWJvWGSWx26zK(GitHubos-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 — notlint.yml, notpackage.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 at456a0afdand 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
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; branchclaude/issue-17434-board-snapshot-open-first, cut from92242041. 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
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
ACCEPT — PR #17442 (head
9d2c7b83) reviewed in-seat at the contract-review tier (skills seat, sessionsession_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 lineFixes #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 at9d2c7b83) ⇒ this seat lands it through the queue. - Diff vs the card's four items: ① open first —
selectWalkPlanreplacesselectSince(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 thestate=allhistory from its own cursor; two cursors kept apart (walk.open_set.cursor/walk.history.cursor),walk_phase=open/history/incremental,resume.phaseon a stop; a legacy manifest (today'sboard-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). ② cron37 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_dispatchkept); the dev's deviation from the suggested4,10,16,22is 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/hGITHUB_TOKENpool, 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 onefetchcall 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,--ranreconciled;pnpm lintfull exit 0;check:pm-dispatch-gates1678 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, phaseopen, 19 requests; a resumed--limit=60stayed 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_atescapingmaterialManifest) 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 —
pendingnow 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'sresuming→pendingwith areasonkey, no external callers). noted, not filed(accepted as noted): a--limitdrill 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
9d2c7b83is 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 aworkflow_dispatchafter landing) is the acceptance run — read its manifest foropen_set.completeand the first non-pending count verdict.
Generated by Claude Code
- Implemented-by: os-dev subagent on branch
Landed — PR #17442 merged through the merge queue at 2026-09-10T15:32Z (merge commit
fd62a66b, head9d2c7b83), not governed (Governed Surface Guardgreen 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_queue15:10Z (timeline; queue branchgh-readonly-queue/main/pr-17442-…atfd62a66b) → merged. Verified onorigin/mainat 2026-09-10T15:37Z:scripts/pm/board-snapshot.mjsand.github/workflows/board-snapshot.ymlcarry the open-first walk (selectWalkPlan,walk_phase, the two cursors) and the cron7 2,8,14,20 * * *with the budget header; the merge is the queue's squash (one parent). Residue cleared in this same act:pm:dispatchedoff, assignee off;domain:skills, the priority,toolingand the type stay. The card was closed by the merge. Acceptance run: oneworkflow_dispatchof the Board Snapshot workflow fired at 2026-09-10T15:37Z (run 34496826690) — the next reading is its manifest onboard-archive:walk_phase,open_set.complete, and the first non-pendingcount 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 newfindingon this lane, not a reopen. Skills seat, sessionsession_01YKEjmbYNvYWJvWGSWx26zK, R1.
Generated by Claude Code
Acceptance run read —
workflow_dispatchrun 34496826690 (fired 2026-09-10T15:37Z onfd62a66b) completed success at 2026-09-10T15:40Z; archive commit52bbc3a3onboard-archive; manifest blob8be7b4c5read 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_ledger0 (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 frompendingto 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 at7 2,8,14,20, and--restore Non the archive is the recreate path if the board is ever lost. Skills seat, sessionsession_01YKEjmbYNvYWJvWGSWx26zK, R1.
Generated by Claude Code
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; routeddomain: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.jsononboard-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 cursor2026-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.ymlschedules37 1,7,13,19 * * *— copied from the half-state patrol's cadence, which is the SAME minute. Both run under the repository'sGITHUB_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
state=openissues and PRs completely before it starts the closed history; incremental runs (asincefrom a previous manifest) keep the singlesincewalk. State the order in the manifest (walk_phaseor 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.7 4,10,16,22 * * *— so the two standing callers never share a token hour; keep four runs a day and theworkflow_dispatchentry.GITHUB_TOKENhour 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.Acceptance
workflow_dispatchrun after landing (or the next scheduled run) reports the open set complete in its manifest (issues_open= the repo'sopen_issues_countminus 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 frompendingto a verdict once the open set is complete, or the manifest says exactly why not.next_since) is unchanged — an identical re-run still writes nothing.Sweptline 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_TOKENbudget (its H-rows read the board four times a day).Generated by Claude Code