Uh oh!
There was an error while loading. Please reload this page.
Route the PM seat's high-frequency GitHub reads to REST in the permission set - #11186
Merged
Conversation
…e permission set The seat's GraphQL bucket is measured at the ceiling (~4965/5000 used in one hour) while REST core sits nearly empty (~50/15000). The MCP list/read family runs on GraphQL — `list_issues` returns Connection cursors, and a squeeze measurement moved graphql used by +7/+4 across MCP calls while core barely moved. Every read the loop repeats therefore spends the scarce bucket. Pre-approve the REST equivalents so the seat can take them without a prompt, spelled exactly like the additive label POST added by #11112 (method, host and path shape pinned; URL immediately after `-X`; the trailing `*` absorbs the Bearer header and any payload). Nine path shapes per repo, objectstack and objectui twins: issue list, issue get, issue comments, issue labels read, PR get, PR files, PR commits, commit check-runs, and the PR body PATCH. Plus `GET /rate_limit`, which is free and is what a quota check reads. Query-carrying paths get the wildcard against the path itself (`comments* *`) so one entry covers both the bare and the `?per_page=…&page=N` spellings without widening the path shape. No bare `curl *` entry is added. The routing-guidance prose is NOT in this PR: `platform-readings.md` is at 134/134 and SKILL.md at 682/682, both zero headroom, and no honest deletion was available — see the PR body. Fixes#11181 Co-Authored-By: Claude <noreply@anthropic.com>
os-zhuang
commented
Aug 23, 2026
ContributorAuthor
ACCEPT — session 逐条读完 diff(1 文件,+20/−1,head
等维护者合并。合并后席位即可免弹窗走 REST 高频读,GraphQL 桶只留给 mutation-only 三件套。 Generated by Claude Code |
os-zhuang
marked this pull request as ready for review
August 23, 2026 01:38
os-zhuang
enabled auto-merge
August 23, 2026 01:38
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#11181
Why — the measured quota split (cited from the card, not re-measured)
The card carries the seat's measurement from 2026-08-23 01:1x UTC, taken through the free
GET /rate_limitendpoint. Quoted, not repeated:Two structural findings on the card, both independent of concurrent-seat noise:
list_issuesreturnspageInfo.startCursor/endCursor— GraphQL Connectioncursors. The issue list runs on GraphQL.
graphqlused by +7 and +4 whilecorebarely moved.
So the loop's high-frequency reads spend the bucket that is at its ceiling, while the one
with 99.7% headroom goes unused. The card's routing table names the REST equivalent for
each of those reads; this PR makes those calls prompt-free for the seat.
What changed
One file,
.claude/settings.json,permissions.allow: 19 entries added.Shape is copied from the additive label POST that #11112 added, changing only method and
path — the URL sits immediately after the
-Xmethod flag, and the trailing*absorbs theAuthorization: Bearerheader and any payload, which is why that convention puts headersafter the URL. #11112's own commit message states the intent this follows: "pre-approve
exactly the … spelling that os-dev.md now prescribes". Every entry pins method + host +
path shape. There is no bare
curl *entry, and nothing here widens the two POST entriesalready present.
Nine path shapes, each as an objectstack/objectui twin:
GET /repos/objectstack-ai/{repo}/issues?* *GET /repos/objectstack-ai/{repo}/issues/* *GET /repos/objectstack-ai/{repo}/issues/*/comments* *GET /repos/objectstack-ai/{repo}/issues/*/labels* *GET /repos/objectstack-ai/{repo}/pulls/* *GET /repos/objectstack-ai/{repo}/pulls/*/files* *GET /repos/objectstack-ai/{repo}/pulls/*/commits* *GET /repos/objectstack-ai/{repo}/commits/*/check-runs* *PATCH /repos/objectstack-ai/{repo}/pulls/* *Plus one repo-agnostic entry,
GET https://api.github.com/rate_limit *— the freeendpoint a quota check reads.
On the two wildcard spellings: paths that can carry a query string take the wildcard
against the path token itself (
comments* *, notcomments *), so one entry covers both…/commentsand…/comments?per_page=100&page=2without loosening the path shape. Pathsthat never carry one (
/issues/{n},/pulls/{n}) keep #11112's exact… /* *spelling.Under either reading of the matcher's
*— single-segment or any-character — the twoadjacent wildcards match the intended command and nothing broader in path shape.
The label write stays on the two POST entries #11112 added; only the read half of the
read-modify-write moves to REST here.
Routing-guidance prose — deferred, and why
Deferred to the #11086 whole-text restructuring round, per the card's own escape
hatch. Both candidate homes are at zero headroom, confirmed by the gate on this commit:
.claude/skills/pm-dispatch/references/platform-readings.md— 134 lines, ceiling 134,headroom 0
.claude/skills/pm-dispatch/SKILL.md— 682 lines, ceiling 682, headroom 0No ceiling was raised — that is maintainer-only. I looked for genuine deletion (真删) to
fund the row and did not find any I can defend as duplication. What the file does hold
is supersession, which is a different thing and not mine to land in this PR:
platform-readings.md§ API 配额 currently opens with a dated 2026-08-22 reading thatthe seats run under different GitHub accounts, with one shared identity holding only
within a seat. The card's 2026-08-23 measurement reads the opposite — one identity
across seats, GraphQL as a commons already at the ceiling. Both are dated measurements
and they contradict each other.
gate, concluding that a pure-MCP session meeting a drained pool has no list channel
until reset. The card overturns the premise for this seat: it used
GET /rate_limit,GET /commits/{sha}/check-runsandPATCH /pulls/{n}on 2026-08-23 — the very callsthis allowlist covers.
Deleting either would be resolving a conflict between two measured readings, not paying a
line budget, and it would land inside the allowlist PR where no reviewer is looking for
it. Both are flagged in the dev report for PM triage; the whole-text round is where they
belong. The allowlist stands on its own without the prose — it removes permission
prompts, it does not instruct anyone to route differently.
Gates — run on the final commit
99f2db40e2Families derived with
node scripts/pm/dispatch-gates.mjs(no hand-fed paths; it read thechange set from the merge base itself and reported
1 path(s)—.claude/settings.json).It named four families; all four were run, plus the two the dispatch mandates and the
NUL-byte gate. Every verdict below is the line the gate itself printed; exit codes were
captured before any pipe.
Hook self-tests — this PR does not touch
.claude/hooks/, but the card asks for them:JSON validity:
require('./.claude/settings.json')parses; 47 allow entries, 21 of themBash(curl …).Repo-wide ESLint — a proven narrowing, not a skipped run
pnpm lint(eslint . --no-inline-config) was not run locally; CI runs the farm on everyPR. The narrowing is measured rather than assumed, on all three counts:
eslint.config.mjsmatches**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}and the fivenarrower
files:blocks are all.ts/.tsxsubsets. No JSON plugin is configured andno
files:pattern names.json..claude/settings.json. Itsintersection with that population is zero files.
permission allowlist — it defines no ESLint rule, plugin, parser or
files:pattern,so it cannot move the verdict on any file ESLint does lint.
Landing — governed surface, human merge only
node scripts/pm/check-governed-merges.mjs --test .claude/settings.json→ EXIT=3,verbatim:
This PR therefore stays a draft. No seat marks it ready, enqueues it, or arms
auto-merge. A human merges it.
No changeset: the diff is
.claude/**-only and publishes nothing.dispatch-gates.mjsplaced the five changeset families in its "once a changeset exists" section, i.e. none of
them is triggered by the paths as they stand. The
skip-changesetlabel is appliedseparately and read back.
content/docs/releases/is untouched.Generated by Claude Code
Generated by Claude Code