Skip to content

fix(pm): re-pin H22 closed-issue update rate from a card-only measurement of the stream the patrol actually pages - #17627

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-17254-h22-rate-pin-remeasure
Sep 11, 2026
Merged

os-litant merged 1 commit into
mainfrom
claude/issue-17254-h22-rate-pin-remeasure

Conversation

@os-litant

@os-litant os-litant commented Sep 11, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #17254

Dispatched to the domain:skills os-dev seat by the skills seat, PM loop R1 wave 11 — session session_01YKEjmbYNvYWJvWGSWx26zK, branch claude/issue-17254-h22-rate-pin-remeasure.

H22's MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY is re-pinned by hand from a fresh measurement. ⛔ The divisor stays a hand pin — see the one-sentence answer to the triage seat below.

B — the premise clause as the patrol renders it TODAY

One read of the anchor's body (GET /repos/objectstack-ai/objectstack/issues/9857), swept 2026-09-11T01:55:32.270Z, run 34552285557, commit 2f8ad091466686cc81a723f7e7b6c695a8ddf8dd, trigger schedule. Verbatim:

⚠️ RATE PREMISE DRIFTED — this sweep observed ~141.3 closed-issue updates/day against a pinned MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY = 415.1/day, measured 2026-09-06 (5d ago): a factor of 0.34, outside the 2x band. H22's window states its reach in days by dividing by that constant, so the coverage this very line quotes is misdescribed by the same factor. ⛔ A sweep never overwrites the pin: it is re-pinned by hand, from a fresh measurement. Re-measure.

State drifted, observed 141.3, factor 0.34. ⚠️ The card was filed off the 2026-09-09 run (~167.5/day, factor 0.40); today the factor is 0.34, so the drift is 2.9x, not the 2.5x the card quotes — and the same clause names 5 pages, 428 in-window closures and a reach of 2026-09-07, which is the reading the measurement below turns on.

⛔ The post-merge clause is the seat's to quote in the landing record; this one is pre-merge.

The measurement — and the population, which is the whole finding

stats.closedRateObserved = observedRatePerDay(rateRows, 'updated_at'), and rateRows is every row the pass READ — PRs not dropped, projected to updated_at — off closedWindowPagePath: GET /repos/{repo}/issues?state=closed&sort=updated&direction=desc&per_page=100&page=N. So the recipe is "every row of that stream", and the only question is which stream.

Read 12 pages, 2026-09-11T04:22:36Z–04:22:49Z, every page full at 100 rows (1,200 rows, 1,200 distinct numbers, one non-monotonic stamp). 595 of the 1,200 are pull requests — 49.6%.

depth newest oldest span (d) rate/day
400 2026-09-11T04:15:19Z 2026-09-09T15:55:11Z 1.514 264.2
800 2026-09-11T04:15:19Z 2026-09-08T05:44:23Z 2.938 272.3
1200 2026-09-11T04:15:19Z 2026-09-07T04:48:52Z 3.977 301.8

⚠️ That is not the population the sweep is rated against. Replaying listRecentlyClosedIssues over those captured pages with nowMs = the read instant:

stream replayed closedPages in-window closures reach observed rate
as captured (PR-inclusive) 9 426 2026-09-07 256.2/day
card-only slice of it 5 426 2026-09-07 139.4/day
the live 01:55Z run 5 428 2026-09-07 141.3/day

The card-only replay reproduces the live run on all four numbers, to within 1.4% on the rate. The PR-inclusive one does not, and cannot: 428 in-window closures will not come out of 5 PR-inclusive pages, because only ~250 of those 500 rows are cards. The patrol runner's token declares contents: read + issues: write and no pull-request scope, and its /issues pages come back card-only; the byte-identical request from this container returns 49.6% pull requests. ⛔ The permission block is the reading that fits, not a measurement — the runner's raw response is not readable from a seat. The population difference itself is measured.

So the pinned depths are re-taken on the card-only stream (605 such rows inside the 12 pages):

depth (card-only) oldest span (d) rate/day
200 2026-09-09T19:47:53Z 1.352 147.9
400 2026-09-08T09:48:23Z 2.769 144.5
500 2026-09-07T14:11:02Z 3.586 139.4
605 2026-09-07T04:59:18Z 3.969 152.4

⚖️ Deviation from Zone 1, declared. The ruling said "pin the leading-1200-row rate … at the depth where the budget is spent, as the last re-pin did". Two of its premises measured false. (1) The previous re-pin did not pin the 1200-row rate: its own record reads window 400 rows spanning 0.964 days / rate 400 / 0.964 = ~415, and the deeper re-reads that day were ~390 and ~388 — 415.1 is the 400-row figure, and the 1200-row reading was the diagnostic that showed it was light. (2) The 1200-row depth is a container-read depth; the runner spends 5 pages, and 1,200 card-only rows are not reachable inside the 12-read budget. Zone 2(b) is the clause that decides it — "measure the SAME population … dropping whatever the sweep drops" — so the pin is the rate at the depth the budget is actually spent, on the population it is spent on: 500 card-only rows over 3.586 days, past the 3-day horizon, not a one-day sample. Pinning 301.8 instead would have left the alarm drifted at 0.47 on every future run and left H22's coverage sentence wrong by 2.2x — the card unfixed with a new number in it.

The pin, before and after

before after
MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY 415.1 139.4
..._AT '2026-09-06' '2026-09-11'
record 400 rows / 0.964 d; re-reads 800 / 2.051 d and 1200 / 3.092 d 500 card-only rows / 3.586 d; re-reads 200 / 1.352 d, 400 / 2.769 d, 605 / 3.969 d

Against the new pin the last run's own observation classifies ok: rate premise OK — observed ~141.3/day against pinned MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY = 139.4/day, measured 2026-09-11 (1d ago) (factor 1.01, band 2x). The 2026-09-09 run's ~167.5/day also reads ok (factor 1.20).

The value-derived self-test pins, re-derived with the arithmetic

The claim declared three. There are eleven; the other eight are the same kind of pin (an expected value that moves with the constant) and the self-test is red without them. Declared here rather than silently widened:

pin derivation before after
windowCoverageDays(400, PIN) 400 / 139.4 0.96 2.87
PIN / 10.7 139.4 / 10.7 38.8 13
windowCoverageDays(400, PIN) < N 2.87 no longer under 2.5 < 2.5 < 3 — still short of the 3-day horizon, which is the claim the case carries
drift factor in the message 942.76 / 139.4 'a factor of 2.27' 'a factor of 6.76'
pinned rate in the message the constant, rendered '= 415.1/day' '= 139.4/day'
the value literal ⛔ the no-self-rewrite pin 415.1 139.4
the date literal ⛔ same pin, its date '2026-09-06' '2026-09-11'
the in-band stream fixture step is relative to the pin: 0.25 gave 433.33/day (3.11x the new pin) rateStream16393(0.25), 13 pages, 433.33 rateStream16393(0.75), 5 pages, 166.67 (factor 1.20)
clause: expired case observed stated at the pin clause16393(415.1, …) clause16393(139.4, …)
clause: in-band cases (x2) same reason as the fixture clause16393(433.33) clause16393(166.67)
clause: both rates named rendered observed + pin 'observed ~433.3/day', '= 415.1/day' 'observed ~166.7/day', '= 139.4/day'

The pins that read the constant RELATIVELY are byte-identical: the date-shape case, and every h22Premise(PIN * k) band case.

The expiry, stated

RATE_PREMISE_STALE_DAYS is 30 and classifyRatePremise turns expired at ageDays > 30, so the new pin expires 2026-10-11 — measured, not computed by hand: classifyRatePremise answers ok at 2026-10-10T00:00:01Z (age 29.00) and expired at 2026-10-11T00:00:01Z (age 30.00). It is one line on the ..._AT docblock, inside the record's own shape.

The triage seat's run-time suggestion, answered in one sentence

⛔ A run-time divisor is the one thing this file rules out — CLOSED_UPDATE_RATE_PREMISE's docblock: 「⛔ The alarm REPORTS; it never writes. H8's rule — a pinned premise is CHECKED against a sweep, never overwritten by one … A self-updating divisor would make every window's stated coverage true by construction and worth nothing」 — and the fallback it asked for instead, a staleness check, already exists and is what filed this card: classifyRatePremise (band 2x, four states ok/drifted/expired/unobserved) is rendered inside the census clause on EVERY run, in every state.

The one judgement, on the four axes

实际业务需求 — the consumer is real and measured, not speculative: the census clause the loop reads to decide a sweep was complete divides a page budget by this constant, four times a day, and the 2026-09-11T01:55Z run is the instance. What the measurement changed is whose stream: the only reading the pin is ever checked against is the runner's, so a pin taken from a container serves nobody's actual read. 项目长远合理性 — a hand pin with a dated record and an alarm watching it is the sustainable shape already chosen here (no workaround, contract-first: the record states its population so the next re-measure is reproducible); a run-time divisor is the temporary patch, and its long-term cost is that no window's stated coverage can ever be wrong, hence never checked. 防 AI 写代码犯错 — the fix that makes the next agent structurally less likely to err is the one that writes the population INTO the recipe: an agent following 「400 rows spanning N days」 from a container reproduced this defect exactly once already, and a recipe that names its population refuses the wrong reading loudly instead of tolerating it; the alternative (a tolerant self-updating divisor) is precisely the silent-fallback shape that hides bulk error. 创业阶段不扩散需求 — no new mechanism, no new row, no new constant, no band change, no exit-code change: one number, one date, one record, and the self-test pins that move with them. The three-depth record is the answer to the card's sampling question, and it costs nothing to carry.

⚖️ The axes do not conflict here; the only tension is with the dispatch's Zone-1 depth, declared above and resolved by Zone 2(b)'s own population clause.

Verification

Every exit code captured before any pipe (cmd > log 2>&1; code=$?), on head 11e23caca — the final commit, with a clean working tree.

  • node scripts/pm/check-half-states.mjs --self-test — before (origin/main 0918c441, run in-tree): ✓ check-half-states self-test: 3606 cases pass. exit 0. after: ✓ check-half-states self-test: 3606 cases pass. exit 0. ⛔ No case added or removed; the count is identical because every change is an expected VALUE, never a new case.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 39 commands from the final diff (1 path, three-dot vs merge base 0918c4411). All 39 ran; all 39 exited 0. Representative verdict lines: ✓ check-half-states self-test: 3606 cases pass. · ✓ dispatch-gates self-test: 1678 cases pass. · check-nul-bytes: OK (scanned 8366 text file(s) -- 8366 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).
  • node scripts/pm/dispatch-gates.mjs --ran … --repo objectstack-ai/objectstack — ✓ dispatch-gates --ran: 39 derived famil(ies) accounted for — 39 run, 0 NOT-MEASURED (a DERIVED zero — all 39 recorded an exit code and none of them is 3). exit 0.
  • pnpm lint (repo-wide eslint . --no-inline-config) — exit 0, no output. Run whole rather than narrowed; no narrowing argument is owed.
  • node scripts/pm/check-governed-merges.mjs --branch claude/issue-17254-h22-rate-pin-remeasure — ✅ NOT governed — ordinary queue landing applies to a PR with exactly this file list. exit 0.
  • Control-byte self-scan beyond the gate: grep -naP over the changed file for the C0 set — no hits.
  • ⊘ NOT MEASURED, by the derivation's own classification: 4 families that take a value from the workflow (including check-half-states.mjs --format=markdown --provenance=…, the live sweep), 1 CI-measured-only family, 1 path-scheduled CI job's 5 steps, 11 wide-population families, 47 artifact rosters, and the 10 pending-changeset families — which do not apply, since this PR carries no changeset (below).

Changeset: none — scripts/pm/** publishes nothing from any released package (it is in no package's files[]), so skip-changeset applies, exactly as this file's predecessors this shift carried it.

Clause-②: no

Acceptance notes

  • noted, not filed: the card's acceptance item 2 — a patrol run whose census line no longer carries RATE PREMISE DRIFTED — cannot be quoted pre-merge: the scheduled sweep reads main. The classifier's answer against the last run's own observation is quoted above (factor 1.01, ok); the run itself is the seat's to quote in the landing record. Carrier: this PR's seat review.
  • filed as #17626: the population divergence itself — the patrol runner pages a card-only stream while an agent container pages a PR-inclusive one — with the three statements it makes false, all of them outside this card's region-declared surface and therefore byte-identical here: CLOSED_ISSUE_WINDOW_DAYS's ⚠️ Cost note (「46% of the rows this stream returns are PULL REQUESTS … roughly twice the pages a card-only stream would need」), the same docblock's 「the divisor below now reads 415.1」, and listRecentlyClosedIssues's 「measured over the RAW stream (400 rows / 0.964 days)」. CLOSED_ISSUE_WINDOW_PAGE_CEILING's 40 was sized on the container stream too, so its real headroom is about double the docblock's arithmetic — safe direction, wrong number.
  • noted, not filed: the claim's enumeration of the value-derived self-test pins named three of eleven. Not a surface breach — the claim's own words are "the self-test pins that hardcode a number derived from the constant's VALUE and therefore change with it", which all eleven are — but the enumeration is worth correcting in the next claim for a pin of this kind. Carrier: the skills seat's dispatch template.
  • noted, not filed: the 1200-row container reading (301.8/day) is raised by a boundary burst: rows 1,101–1,200 span 11 minutes, while rows 1–1,100 run at 277.1/day. It does not affect the pin (card-only), and every card-only depth agrees inside the band (139.4 to 165.9, max ratio 1.19). Carrier: none — it is an artefact of one read, not of the board.

维护者速读(草稿)

改了什么 — scripts/pm/check-half-states.mjs 一个文件:H22 的除数常量 MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY 从 415.1(2026-09-06)重新手工测量为 139.4(2026-09-11),连同它的测量记录(读取命令、窗口、跨度、三个深度的复读),以及十一个「期望值随该常量变动」的 self-test pin。⛔ 没有自更新除数、没有改带宽、没有改页数上限、没有新行、没有改退出码。

为什么改 — 巡逻每天四次在自己的普查句里报 RATE PREMISE DRIFTED,即它对自身窗口覆盖范围的描述错了 2.9 倍。测量过程中查明了根因:巡逻 runner 拿到的 /issues 流不含 PR,而任何座位从容器里读同一个 URL 拿到的流有 49.6% 是 PR。上一个 pin 是从容器读出来的,于是它被拿去和一个密度只有一半的总体对比——「漂移」里有一半根本不是节奏变化。新 pin 按 sweep 真正分页的那个总体(card-only)在预算实际花到的深度(5 页 / 500 行 / 3.586 天)取值。

风险与代价(含回滚) — 风险低:该常量只进两处——普查句里的一个描述性数字,和 classifyRatePremise 的对比基准;不参与分页、不参与选择、不改退出码。self-test 前后都是 3606 例全过,没有新增或删除用例。回滚 = revert 本 PR 的单个 commit,常量回到 415.1,警报回到 drifted。

席位意见 — (待填)

你要做的 — 合并后从下一次巡逻运行(half-state-patrol.yml,cron 37 1,7,13,19)的普查句确认 RATE PREMISE DRIFTED 已消失、改读 rate premise OK;并决定 #17626 的走向:是把「card-only」写进测量配方,还是给 workflow 加 pull-requests: read 让两边读同一个流(代价是 H22 的页数账单翻倍)。


Generated by Claude Code

…rement

`MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY` stood at 415.1/day, measured
2026-09-06, and every sweep since has printed `RATE PREMISE DRIFTED` —
so the coverage sentence H22 writes about itself was misdescribed by
that factor, four times a day.

Re-measured by the docblock's own recipe, and over the population the
sweep is actually rated against: the scheduled runner's `/issues` pages
come back card-only, while the byte-identical request from an agent
container returns 49.6% pull requests. The 2026-09-11T01:55Z run reports
5 pages, 428 in-window closures and ~141.3/day; replaying this pager
over a container read of the same hour reproduces 5 pages / 426 /
~139.4 on the card-only slice and 9 pages / 426 / ~256.2 on the whole
one, and 428 in-window closures cannot come out of 5 PR-inclusive pages
at all. The pin it replaces was taken PR-inclusive, so the 0.34 factor
the alarm printed was a real slowdown times a population difference.

Pinned at 139.4/day (500 card-only rows spanning 3.586 days — the five
pages a 3-day horizon costs), dated 2026-09-11, with the 200 / 400 /
605-row re-reads recorded in the same shape the old record used. The
alarm reads `ok` against the last run's own observation (factor 1.01).

The divisor stays a HAND pin: the alarm reports, it never writes.

Self-test: 3606 cases pass before and after — no case added or removed;
the pins that hardcode a number derived from the constant's value are
re-derived from it.

Claude-Session: https://claude.ai/code/session_01YKEjmbYNvYWJvWGSWx26zK
Co-authored-by: Claude <noreply@anthropic.com>
@os-litant os-litant added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 11, 2026 — with Claude
@os-litant
os-litant marked this pull request as ready for review September 11, 2026 05:27
@os-litant
os-litant enabled auto-merge September 11, 2026 05:27
@os-litant
os-litant added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit c1123cf Sep 11, 2026
50 checks passed
@os-litant
os-litant deleted the claude/issue-17254-h22-rate-pin-remeasure branch September 11, 2026 05:53
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…sured on (objectstack-ai#17793)

Fixes objectstack-ai#17626

H22 pages its closed-card window through `closedWindowPagePath` — `GET
/repos/{repo}/issues?state=closed&sort=updated&direction=desc&per_page=100&page=N`.
Two callers issue the byte-identical request and get different
POPULATIONS: the scheduled patrol runner's pages come back
**card-only**, a proxied agent container's come back **49.6% pull
requests**. Four prose sites in `scripts/pm/check-half-states.mjs`
quoted the container's stream while describing the patrol's, so the
measurement recipe they carry — followed correctly — yields the wrong
number. This is prose only: **no constant moves**
(`MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY` stays 139.4,
`CLOSED_ISSUE_WINDOW_PAGE_CEILING` stays 40), no H-row, no self-test
change.

## The boundary this card asked to survive the fix

The population difference itself is **MEASURED**, through the sweep's
own numbers. **The runner's `permissions:` block as the MECHANISM is NOT
claimed** — the runner's raw response was never read. Every new sentence
below is written that way, and the ⚠️ Cost note says it in those words.

## The four sites, before and after

**1. The ⚠️ Cost note under `CLOSED_ISSUE_WINDOW_DAYS`**

- before: «46% of the rows this stream returns are PULL REQUESTS,
filtered out after paging. The horizon is therefore reached in roughly
twice the pages a card-only stream would need.»
- after: the 46% is named as a **container** reading (49.6%, read
2026-09-11T04:22:36Z, 12 pages / 1,200 rows), the patrol's stream is
named card-only, and the runner side is quoted from the sweep's own
numbers — the 2026-09-11T01:55:32Z run (34552285557, `2f8ad091`) reports
5 pages, 428 in-window closures, reach 2026-09-07; 428 in-window
closures cannot come out of 5 PR-inclusive pages, since only ~250 of
those 500 rows would be cards. The 2026-09-09 run is the same shape at 6
pages.

**2. The same docblock's present-tense divisor sentence**

- before: «re-pin (objectstack-ai#16419: the divisor below now reads 415.1)» — present
tense against a divisor that now reads 139.4.
- after: restated as history — objectstack-ai#16419 re-pinned 415.1 on 2026-09-06 from
the container stream; objectstack-ai#17254 re-pinned on 2026-09-11 from the card-only
slice, and the divisor below now reads 139.4. The `~13 pages` this note
derives is also marked as a 2026-09-06 container reading, and explicitly
**not** comparable to today's 5 pages on population alone — the two
readings are five days AND one population apart, with the decomposition
left where PR objectstack-ai#17627 put it, in `MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY`.

**3. `CLOSED_ISSUE_WINDOW_PAGE_CEILING`'s derivation**

- before: «4,000 rows reached 12.8 days on the read above (~312/day
averaged over that depth), against a 3-day horizon: ~4.3x headroom.»
- after: the derivation stands, with a ⚠️ POPULATION note beneath it —
that read was container-taken, so only ~2,000 of its 4,000 rows were
cards. 40 pages return 4,000 rows either way, but on the runner all
4,000 are cards, so the same ceiling reaches **about twice** as far
back: ~25 days rather than 12.8, ~8x headroom rather than ~4.3x. Written
as a **note, not a change**: the direction is more headroom and not
less, 40 is unchanged, and re-deriving it properly needs a card-only
read at depth that nobody has taken.

**4. `listRecentlyClosedIssues`'s `rateRows` comment**

- before: «the pin this rate is checked against was measured over the
RAW stream (400 rows / 0.964 days)» — a description of the 188.3 pin,
two re-pins ago.
- after: the pin's population as it now stands (500 rows / 3.586 days,
read 2026-09-11), plus why the distinction lives at this line: on the
patrol runner the stream is card-only, so `rateRows` holds cards and the
observed rate is counted on the same population the pin was; the same
request from a container returns 49.6% pull requests and `rateRows`
would count those too — a rate on one population checked against a pin
on another. The `READ` vs `ADMITTED` argument (objectstack-ai#4690 with the numbers
the right way up) is unchanged.

## The re-measure recipe the file now carries

Stated once, in the ⚠️ Cost note, and pointed at from sites 3 and 4:

> A container read is NOT the patrol's stream. Filter pull requests OUT
of every page BEFORE counting pages or rating rows, and quote the
card-only slice.

On the 2026-09-11T04:22:36Z container read that slice gives **5 pages /
426 rows / ~139.4 per day**, against **9 pages / 426 / ~256.2 per day**
on the whole stream. The slice reproduces the runner's own 5 pages; the
raw stream does not. That is the operative half — a seat following the
old note from a container reproduced objectstack-ai#17254's false `RATE PREMISE
DRIFTED` alarm every time.

## Verification

All of it taken on the final commit `034c09ace`. Gate families derived
with `node scripts/pm/dispatch-gates.mjs --commands` (no paths passed —
it takes its own change set from the merge base), each command run with
its exit code captured **before any pipe**, then reconciled:

> `✓ dispatch-gates --ran: 40 derived famil(ies) accounted for — 40 run,
0 NOT-MEASURED (a DERIVED zero — all 40 recorded an exit code and none
of them is 3).`

Every one of the 40 exits 0, including `pnpm check:pm-dispatch-gates`
(detached — it runs past the foreground cap — and waited on with `tail
--pid`). Named readings:

- `pnpm check:pm-half-states` — self-test **3656 cases pass** on the
base file (`8fa3fe63d`) and **3656 cases pass** after; the count is
unchanged because no pin moved and no self-test assertion names any of
the rewritten sentences (grepped `46% of the rows`, `415.1`, `0.964`,
`312/day` across the tree — the only hits outside this file's own prose
are two historical mentions this PR does not touch).
- `pnpm check:nul-bytes` — `check-nul-bytes: OK (scanned 8451 text
file(s) ... no raw ASCII control bytes)`; plus a self-scan of the edited
file, `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'`, zero hits.
- `node scripts/check-comment-mask-corpus.mjs` — `6640 files, 0
disagree, 0 unparseable`.
- `node scripts/check-scripts-symbol-anchors.mjs` — `3186 anchors across
248 scripts resolve`.
- The remaining 36 derived commands exit 0.

Repository-wide scans (`pnpm lint` first among them) are CI's run, not
this PR's.

## Acceptance notes

- noted, not filed: `MEASURED_CLOSED_ISSUE_UPDATES_PER_DAY`'s docblock
(landed by PR objectstack-ai#17627, outside this card's declared surface) states the
runner's token scopes as part of its population paragraph. It does label
the reading «Measured, not inferred from the workflow file», so it is
not a mechanism claim, but it sits closer to one than the ⚠️ Cost note
now does. Successor: the next seat re-pinning that divisor.
- noted, not filed: the card names a second possible route — declaring
`pull-requests: read` on `half-state-patrol.yml` so the runner and a
container read the same stream. This PR takes the recipe route the
dispatch chose; the permission route is untouched and its cost (roughly
doubling H22's page bill) is unmeasured.
- `skip-changeset`: `scripts/pm/**` is not published — no package's
`files[]` ships this script.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_


---
_Generated by [Claude Code](https://claude.ai/code)_

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants