Skip to content

fix(ci): the Gate's own status post could pick a token that cannot write statuses - #81

Merged
stranske merged 1 commit into
mainfrom
claude/gate-status-token
Aug 23, 2026
Merged

fix(ci): the Gate's own status post could pick a token that cannot write statuses#81
stranske merged 1 commit into
mainfrom
claude/gate-status-token

Conversation

@stranske

Copy link
Copy Markdown
Owner

What broke

A fully green Gate run left a red Gate / gate commit status on #54, and nothing could clear it. The summary job computed the right answer and was then refused the write:

Token registry initialized with 7 tokens
Selected token: WORKFLOWS_APP (4645 remaining, 92.9% capacity)
POST /repos/stranske/Orchestrator/statuses/bfb6fb1... - 403
##[warning]Gate commit status update blocked by permissions; leaving existing status untouched.

createTokenAwareRetry defaults to env: process.env, so it collected every App and PAT secret the job exposes and chose by remaining rate-limit capacity. POST /statuses/{sha} needs the statuses scope — GITHUB_TOKEN has it here (the job declares statuses: write; the runner printed Statuses: write for both runs), the WORKFLOWS App installation does not.

Why it is a latched gate

Maint 71 will not merge a sync PR without Gate / gate = success. The only writer of that status is this step. The step was refused the write. So the stale failure outlived its evidence and waiting could not clear it — and it failed toward silence: a warning buried in a run whose 18 jobs were all green.

It looked intermittent because token choice is capacity-based: run 32659615306 posted fine at 18:56, 32661355234 was refused at 19:28 and again at 19:34 on a re-run, with identical declared permissions.

The fix

env: {} pins this one call to the workflow token. Retries still apply; only the token source is fixed.

Declaring capabilities: ['statuses:write'] would not have worked — token_load_balancer aliases that to the generic write-repo, which all three token types claim, so the App stays eligible. That alias is the upstream half of the defect and is untouched here (it lives in stranske/Workflows, and every consumer's Gate carries the same call).

Second, unrelated correction

The mypy figure was stale in six places and disagreed with itself. Measured on 24cb115 with python3 scripts/ci_lint_baseline.py under the pinned mypy 2.3.1: 608 errors across 89 of 189 files, 19 codes, top 15 covering 603. Recorded was 604/90 in the toggle, and both 604/89 and 601/89 in the baseline doc.

It read 601 → 604 → 607 → 608 during 2026-08-23 alone and nothing couples it to a measurement, so the illustrative prose that restated it ("a bare 604 errors reads as be-patient") is now generic — that phrasing was manufacturing the next stale figure every time the count moved. The drift is recorded where the number lives.

Verification

  • test_ci_gate_config.py — 12 passed
  • ruff check and black --check -l 100 — clean over 196 files
  • pr-00-gate.yml parses, 11 jobs
  • collection unchanged at 416 = the recorded floor, so no floor edit

No behaviour change outside the token source and the prose.

🤖 Generated with Claude Code

…ite statuses

A green Gate run left a RED `Gate / gate` status on PR #54, and nothing could clear it.
The summary job computed `STATE: success`, then:

    Token registry initialized with 7 tokens
    Selected token: WORKFLOWS_APP (4645 remaining, 92.9% capacity)
    POST /repos/stranske/Orchestrator/statuses/bfb6fb1... - 403
    ##[warning]Gate commit status update blocked by permissions; leaving existing status untouched.

`createTokenAwareRetry` defaults to `env: process.env`, so it collected every App and PAT
secret this job exposes and picked by remaining CAPACITY. `POST /statuses/{sha}` needs the
`statuses` scope: `GITHUB_TOKEN` has it here (the job declares `statuses: write`, and the
runner printed `Statuses: write` for both runs), the WORKFLOWS App installation does not.
So the token that posts the Gate's status was chosen by rate-limit headroom, and when the
App won, the post 403'd and `isIntegrationPermissionError` swallowed it as best-effort.

That is a latched gate. Maint 71 will not merge a sync PR without `Gate / gate = success`;
the only writer of that status is this step; and the step was refused the write — so the
stale failure outlived its evidence and waiting could not fix it. It failed toward SILENCE:
a warning inside a run whose 18 jobs were all green.

Non-deterministic by construction, which is why it looked intermittent: run 32659615306
posted fine at 18:56 and 32661355234 was refused at 19:28/19:34 with identical declared
permissions, because the balancer picked differently. Other PRs' statuses posted normally
all day.

`env: {}` pins this one call to the workflow token. Retries still apply; only the token
source is fixed. Declaring `capabilities: ['statuses:write']` would NOT have worked:
token_load_balancer aliases that to the generic `write-repo`, which all three token types
claim, so the App stays eligible. That alias is the upstream half of this defect and is
untouched here.

Also: the mypy figure was stale in six places and disagreed with itself. Measured on 24cb115
with `python3 scripts/ci_lint_baseline.py` (pinned mypy 2.3.1): 608 errors across 89 of 189
files, 19 codes, top 15 covering 603. Recorded was 604/90 in the toggle and both 604/89 and
601/89 in the baseline doc. It read 601 -> 604 -> 607 -> 608 during 2026-08-23 alone, and
nothing couples it to a measurement, so the illustrative prose that restated it ("a bare
'604 errors' reads as be-patient") is now generic — that phrasing was manufacturing the next
stale figure every time the count moved. The drift itself is recorded where the number is.

Verified: test_ci_gate_config.py 12 passed; ruff and black -l 100 clean over 196 files;
pr-00-gate.yml parses (11 jobs); collection unchanged at 416 = the recorded floor, so no
floor edit. No behaviour change outside the token source and the prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 17 seconds.

View limit details

Limit details: You’ve used the included review currently available. Your 73 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a0ae0d11-7bf3-4730-8e89-0e1202109c0a

📥 Commits

Reviewing files that changed from the base of the PR and between 6f8ec25 and 4580cb0.

📒 Files selected for processing (3)
  • .github/workflows/pr-00-gate.yml
  • docs/CI_LINT_BASELINE.md
  • test_ci_gate_config.py

Comment @coderabbitai help to get the list of available commands.

@agents-workflows-bot

Copy link
Copy Markdown
Contributor

Workflow source needed

PR #81 needs either a linked GitHub issue or one valid non-issue Workflow Source before PR metadata automation can manage it safely.

Please do one of:

  • Add <!-- meta:issue:123 --> or a normal Closes #123 / Related to #123 line.
  • Check one Workflow Source option in the PR body.
  • Add a hidden marker such as <!-- workflow-source:local_request -->, <!-- workflow-source:manual_remote -->, <!-- workflow-source:review_followup -->, <!-- workflow-source:sync_campaign -->, or <!-- workflow-source:dependabot -->.
  • Add a workflow source label such as workflow:source-direct-pr, workflow:source-local-request, workflow:source-review-followup, workflow:source-sync, or workflow:no-automation.

Once a valid source is present, this warning will not be reposted.

@agents-workflows-bot

Copy link
Copy Markdown
Contributor

Automated Status Summary

Head SHA: 8d76a30
Latest Runs: ⏳ pending — Gate
Required: core tests (3.12): ⏳ pending, core tests (3.13): ⏳ pending, docker smoke: ⏳ pending, gate: ⏳ pending

Workflow / Job Result Logs
(no jobs reported) ⏳ pending

Coverage Overview

  • Coverage history entries: 1

Coverage Trend

Metric Value
Current 48.24%
Baseline 0.00%
Delta +48.24%
Minimum 70.00%
Status ❌ Below minimum

Top Coverage Hotspots (lowest coverage)

File Coverage Missing
outcomes.py 9.0% 243
watch.py 9.6% 431
capability_recurrence_check.py 10.4% 421
durability_sweep.py 12.2% 339
keepalive_shadow.py 13.0% 282
capability_outcome_bridge.py 13.5% 295
capability_advisor.py 13.7% 957
keepalive_outcomes.py 14.0% 339
adversarial.py 14.1% 164
langsmith_fetch.py 14.3% 409
gh_capacity.py 14.6% 228
runtime_ac_panel.py 14.7% 290
redirect_shadow.py 16.9% 476
cross_repo_lane.py 17.3% 268
experiment_recovery.py 18.4% 164

Low Coverage Files (<50.0%)

File Coverage Missing
outcomes.py 9.0% 243
watch.py 9.6% 431
capability_recurrence_check.py 10.4% 421
durability_sweep.py 12.2% 339
keepalive_shadow.py 13.0% 282
capability_outcome_bridge.py 13.5% 295
capability_advisor.py 13.7% 957
keepalive_outcomes.py 14.0% 339
adversarial.py 14.1% 164
langsmith_fetch.py 14.3% 409
gh_capacity.py 14.6% 228
runtime_ac_panel.py 14.7% 290
redirect_shadow.py 16.9% 476
cross_repo_lane.py 17.3% 268
experiment_recovery.py 18.4% 164

Updated automatically; will refresh on subsequent CI/Docker completions.


Keepalive checklist

Scope

No scope information available

Tasks

  • No tasks defined

Acceptance criteria

  • No acceptance criteria defined

@github-actions

Copy link
Copy Markdown
Contributor

Workflow state fingerprint for Agents Gate Followups. Do not edit.

@stranske
stranske merged commit 7fd3b0b into main Aug 23, 2026
34 checks passed
@stranske
stranske deleted the claude/gate-status-token branch August 23, 2026 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant