Skip to content

defaults guard: a call owner's SHIP counts when the REST quota is spent (#32983) - #33111

Merged
lawrencecchen merged 2 commits into
feat-cmux-nextfrom
fix/defaults-guard-owner-receipts
Nov 3, 2026
Merged

lawrencecchen merged 2 commits into
feat-cmux-nextfrom
fix/defaults-guard-owner-receipts

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The defaults guard stayed red on #32983 with a valid receipt (Review (independent-subagent): SHIP at 8af92c61... by lawrencecchen). The merge service then left the PR out as "defaults guard red".

Cause: lawrencecchen's org membership is private. The workflow's token sees a private member as CONTRIBUTOR. The guard then asks the REST API (collaborators/<login>/permission) for write access. That call uses the installation's REST quota, and other jobs use the same quota. In run 41576329148 the call got API rate limit exceeded for installation. A failed lookup trusts nobody, so the guard rejected the receipt. The error message gave no reason ("needs a comment or review whose first line is ..."). It was not an old guard version: scripts/ci/defaults_guard.py is the same at the PR base c2933eb and at feat tip.

Fix:

  • A receipt from a call owner in .github/defaults-call-owners.json is trusted without the REST lookup. The guard reads that file from the trusted base, and the same file already lets these owners approve a default directly.
  • Other private members are still looked up. If a lookup fails for a reason other than 404, a new error line names the receipt that was not counted and says to re-run the check when the quota resets.

Regression command: python3 tests/test_defaults_guard.py

  • Base (6723a4e, tests only): 2 failures. test_a_call_owners_receipt_ships_with_the_rest_quota_spent reproduces Chief: import browser cookies from Chrome on request #32983 (owner receipt, CONTRIBUTOR association, rate-limited lookup) and gives the same warning and error as the CI log. test_a_receipt_whose_lookup_failed_is_named_in_the_error also fails.
  • Head: 67 tests OK.

Changes to existing tests: the private-member fixtures used austinywang, who is a call owner. They now use a non-owner login, so they still test the lookup path. The "outsider" CONTRIBUTOR receipt now uses the login outsider, not the call owner teamleaderleo.

Observed / Not run

Observed:

  • Job log of run 41576329148 (Chief: import browser cookies from Chrome on request #32983): ::warning ... lawrencecchen: gh: API rate limit exceeded for installation, followed by the "needs a review" error.
  • orgs/manaflow-ai/public_members/lawrencecchen returns 404 (the membership is private).
  • I ran the fixed guard functions on Chief: import browser cookies from Chrome on request #32983's live GraphQL data, with the association changed to CONTRIBUTOR and a rate-limited lookup. The result was ('ship', 'review (independent-subagent)'), with no lookup.
  • python3 tests/test_defaults_guard.py red then green as above; scripts/cmux-next/prepush-gate.py clean.

Not run: a live pull_request_target run of this guard. The workflow checks out the guard from the PR base, so this change is first used by PRs whose base contains it.

Changelog

none


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes the defaults guard so a call owner's SHIP receipt counts even when the GitHub REST quota is spent (#32983), and errors now name the receipt when a lookup fails for any reason other than 404.

  • A call owner's receipt is trusted without a REST permission lookup, since the owners file is read from the trusted base.
  • Non-owner members are still looked up; the error output now tells the re-run to wait for the quota to reset.

Written for commit e0d7252. Summary will update on new commits.

View guided diff Turn on auto-fix

…is spent (failing, #32983)

A private org member reads as CONTRIBUTOR to the workflow's token. The
guard then asks the REST API for their write access. On #32983 that call
got "API rate limit exceeded for installation", so the guard trusted
nobody and stayed red on a valid SHIP from lawrencecchen, a call owner.

Red on the base: python3 tests/test_defaults_guard.py fails
test_a_call_owners_receipt_ships_with_the_rest_quota_spent and
test_a_receipt_whose_lookup_failed_is_named_in_the_error.
…okup (#32983)

A private org member reads as CONTRIBUTOR to the workflow's token, so the
guard asked the REST API for each receipt author's write access. That call
spends the installation's REST quota, which other jobs share. On #32983 the
quota was spent ("API rate limit exceeded for installation"), the lookup
trusted nobody, and the guard stayed red on lawrencecchen's valid SHIP; the
merge service left the PR out as "defaults guard red".

The call owners come from the trusted base's .github/defaults-call-owners.json,
the same file that lets them approve a default outright, so their receipts
now need no lookup. Any other member is still looked up; when that lookup
fails for a reason other than 404, the error now names the receipt that
was not counted and says to re-run when the quota resets.

python3 tests/test_defaults_guard.py: red on the base (2 failures:
test_a_call_owners_receipt_ships_with_the_rest_quota_spent,
test_a_receipt_whose_lookup_failed_is_named_in_the_error), green on this
commit (67 tests OK).
@lawrencecchen lawrencecchen added the risk:normal Risk tier (docs/ci/risk-tiers.md) label Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review e0d7252: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr33111 -f ref=fix/defaults-guard-owner-receipts -f platform=macos -f backend_mode=local

red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on e8b1b7d to compare; do not merge until green; see lane-rules

Default changes

This pull request changes 1 default at e0d7252e2205 (what everyone gets without configuring anything):

  • guard.scripts/ci/defaults_guard.py: (none) -> "edited"

Signed off on this commit by review (independent-subagent, correctness first).

Merge receipt: merged as 81a6951f0a67 with these defaults changed, signed off by review (independent-subagent, correctness first).

Regenerate the snapshot with python3 scripts/ci/defaults_guard.py snapshot.

Shipped in:

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Review (independent-subagent, correctness first): SHIP at e0d7252

  • Security: the owners file comes from the trusted base checkout. Owner trust applies only to a User author (a Bot with an owner's login gains nothing). Logins are compared in lower case on both sides. A BLOCK still beats a SHIP. The rule against a self-review is not changed. An owner's approving review was already trusted by login.
  • python3 tests/test_defaults_guard.py: 67 tests pass on the head. On the base guard, the 2 new tests fail and the 65 changed fixtures pass.
  • Low, follow-up: carried_ship does not pass failed. It only gives a warning when the lookup at an earlier head fails. The guard drops a receipt when its lookup fails, whatever the verdict. Thus a rate-limited BLOCK from a private non-owner can lose to an owner's SHIP. This was already so before this change, and it needs a SHIP from a trusted owner.

@lawrencecchen lawrencecchen added the ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds label Nov 3, 2026
@blacksmith-sh

blacksmith-sh Bot commented Nov 3, 2026

Copy link
Copy Markdown

Found 6 test failures on Blacksmith runners:

Failures

Test View Logs
/tmp/tmpm7gdigbs/summary-missing.json (live):/summary-missing.json (live): View Logs
/tmp/tmpxnzcwg23/summary-fail.json (live):/summary-fail.json (live): View Logs
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows/
[new_workspaces] debug.new_workspace timed 0 of 50 new workspace rows
View Logs
[new_workspaces] the window draws no frames/[new_workspaces] the window draws no frames View Logs
[tabs] debug.tab_switch timed 0 of 40 switches/
[tabs] debug.tab_switch timed 0 of 40 switches
View Logs
[tabs] the window draws no frames/[tabs] the window draws no frames View Logs

Fix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

@lawrencecchen
lawrencecchen merged commit 81a6951 into feat-cmux-next Nov 3, 2026
214 of 231 checks passed
@lawrencecchen
lawrencecchen deleted the fix/defaults-guard-owner-receipts branch November 3, 2026 06:50
lawrencecchen added a commit that referenced this pull request Nov 3, 2026
…9); an unchecked BLOCK still counts (#33161)

* test(defaults guard): a call owner's SHIP counts when the REST quota is spent (failing, #32983)

A private org member reads as CONTRIBUTOR to the workflow's token. The
guard then asks the REST API for their write access. On #32983 that call
got "API rate limit exceeded for installation", so the guard trusted
nobody and stayed red on a valid SHIP from lawrencecchen, a call owner.

Red on the base: python3 tests/test_defaults_guard.py fails
test_a_call_owners_receipt_ships_with_the_rest_quota_spent and
test_a_receipt_whose_lookup_failed_is_named_in_the_error.

* fix(defaults guard): trust a call owner's receipt without the REST lookup (#32983)

A private org member reads as CONTRIBUTOR to the workflow's token, so the
guard asked the REST API for each receipt author's write access. That call
spends the installation's REST quota, which other jobs share. On #32983 the
quota was spent ("API rate limit exceeded for installation"), the lookup
trusted nobody, and the guard stayed red on lawrencecchen's valid SHIP; the
merge service left the PR out as "defaults guard red".

The call owners come from the trusted base's .github/defaults-call-owners.json,
the same file that lets them approve a default outright, so their receipts
now need no lookup. Any other member is still looked up; when that lookup
fails for a reason other than 404, the error now names the receipt that
was not counted and says to re-run when the quota resets.

python3 tests/test_defaults_guard.py: red on the base (2 failures:
test_a_call_owners_receipt_ships_with_the_rest_quota_spent,
test_a_receipt_whose_lookup_failed_is_named_in_the_error), green on this
commit (67 tests OK).

* test(defaults guard): a shared helper name is no tunable source; an unchecked BLOCK still counts (failing)

#32983 and #32979 were flagged only because `toggle` and `make` are Tunable
helpers: any token with that name selected its declaration, and through
AppActions.bindApp every handler's `bind` (6182 tunable-source entries).

Two findings from the review of #33111: a BLOCK whose author's lookup failed
was dropped, so an owner's earlier SHIP passed; and a failed lookup at the
earlier head of a carried SHIP gave only a warning.

python3 tests/test_defaults_guard.py on this commit: 4 failures
(test_a_member_that_only_shares_a_helpers_name_is_not_a_tunable_source,
test_an_unchecked_block_after_an_owners_ship_keeps_the_guard_red,
test_an_unchecked_receipt_one_base_merge_back_is_named_in_the_error, and
test_a_receipt_whose_lookup_failed_is_named_in_the_error for its new wording).

* fix(defaults guard): match a Tunable helper by the call that reaches it; an unchecked BLOCK still counts

A Tunable helper (points, make, toggle) selected every declaration that had
a token of that name, so `registry.bind { model.toggle() }` in
AppActions.bindApp, and through it every handler's `bind`, was a
tunable-source default. That is 6182 fingerprints on feat-cmux-next. Now a
helper counts only as `Type.helper` where the helper's file declares Type, or
as a bare `helper` in a file that declares one of the helper file's types
(the same type or an extension of it). `x.helper` and `.helper` are other
members. The dependency walk does not change. On feat-cmux-next
(38369c1) the count goes from 6182 to 449, and no fingerprint is added.
On the diffs of #32983 and #32979 the old guard reports 1 and 2
tunable-source changes, and this one reports none.

Two findings from the review of #33111: a user whose write-access lookup
failed now counts for a BLOCK (never for a SHIP), so an unchecked receipt can
only keep the guard red. A failed lookup at the earlier head of a carried
SHIP is now named in the error too.

python3 tests/test_defaults_guard.py: 4 failures on the test commit, 72 tests
OK on this one. The real-default fixtures (helper, helper body, cross-file
Factory.number, symbol, external symbol, dynamic) still fail without a review.

* test(defaults guard): Self., typealias, subclass and protocol helper calls stay tunable sources (failing)

The review of #33161 found forms that reach a Tunable or its helper with
no Tunable token and no Type.helper of a type in the helper's file:
Self.points(...), a typealias of Tunable (TD.number, `: TD = .number`), a
subclass calling an inherited helper (bare or Sub.helper), and a type
calling a helper of a protocol extension it conforms to.

On this commit all 6 subtests fail. On the guard before #33161, the 4
Self, subclass and protocol subtests pass and the 2 typealias subtests
fail: that guard missed a typealias too.

* fix(defaults guard): Self., typealias, subclass and protocol helper calls reach a Tunable helper

The review of #33161 found Tunable forms the exact helper match missed.
Self.points(...) was skipped because Self is no declared type, and the
guard before #33161 caught it only by the shared-name match. Now:
- Self.helper resolves like a bare helper: the caller's own types.
- A caller's types also include the superclasses and protocols its types
  name, so a subclass reaches its base's helper and a conforming type a
  protocol extension's helper. Only the caller side takes inheritance, so a
  helper file that conforms to Sendable does not reach every Sendable type.
- A typealias of a Tunable, or an alias of such an alias, counts as a
  Tunable token (TD.number, `: TD = .number`). The guard before #33161
  missed this too.
- `class func` / `class var` no longer read as types named func / var.

feat-cmux-next tree: still 449 fingerprints, the same set, all of them in
the old 6182. #32983 and #32979 still report no tunable-source change.
python3 tests/test_defaults_guard.py: 6 subtests red on the test commit,
73 tests OK here. Not resolved: a helper reached through a generic
parameter (T.points).

---------

Co-authored-by: cmux-bot[bot] <348531757+cmux-bot[bot]@users.noreply.github.com>

This branch was previously deployed

1 inactive deployment
ci-routing-agent — e0d7252e Deployed Nov 3, 2026 by lawrencecchen via cmux-tui tree preflight #104191
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-land Reviewed at this head; next-autoland merges it into feat-cmux-next when its only reds are base reds risk:normal Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant