fix(e2e-gate): skip Drift Bot when unreported, raise MAX_WAIT to 3600s - #5332
Conversation
Two causes make the e2e-gate time out on frontend/npm-only dependabot PRs: 1. Drift Bot never posts: the 8090-software-factory App only evaluates PRs that touch Python or product-record files. A pure frontend npm bump (i18next, postcss, react-router, framer-motion, etc.) never receives a drift-bot commit status. The gate waited the full 1800s for a status that can never arrive, blocking every such PR. 2. Runner starvation: 13+ concurrent dependabot PRs all trigger ci.yml simultaneously. With limited concurrent runners, the heavy ci.yml jobs (API Tests x3, pip install x4, MOAT Keystone, etc.) queue past the 30-min MAX_WAIT on busy days. Fix A -- Drift Bot: add skip_if_unreported=True to the Spec dataclass. When a spec has this flag and zero check-runs/statuses have been posted for it, evaluate() returns "passed" (skipped) instead of "pending". The guard is preserved whenever the App does post -- a failing drift-bot status still fails the gate. Only the "never posted = hang forever" case is closed. Fix B -- MAX_WAIT: raise from 1800s (30 min) to 3600s (60 min) in both e2e_gate.py DEFAULT_MAX_WAIT and the workflow env var, and raise the job timeout-minutes from 35 to 65. On a non-starvation day this has no effect (the gate exits the moment all checks pass); on a busy day it gives the runner queue enough time to drain. The gate comment in e2e-gate.yml, which still said "4 required checks" and "15 min", is updated to reflect the current 12 required checks and 60 min timeout. No-PRD: targeted CI infrastructure fix, no behaviour change visible to users. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
|
|
||
| ``skip_if_unreported`` marks a spec whose reporter may legitimately never | ||
| post on certain PR types (e.g. a GitHub App that only evaluates Python or | ||
| product-record changes). When True and zero check-runs/statuses have been |
There was a problem hiding this comment.
The blueprint states: "Only a check whose workflow reports on every pull request may appear in RequiredCheckSpec, because a workflow that stays silent would hold the gate pending until timeout and block every merge." The code now implements skip_if_unreported=True for Drift Bot, explicitly allowing a check that does not report on all PR types (frontend-only npm bumps) to pass the gate without reporting. This contradicts the blueprint's foundational design contract.
…haviour The prior test expected state=="pending" when no drift-bot status has been posted. Now that the Drift Bot spec carries skip_if_unreported=True the gate evaluates an unreported spec as "passed (skipped)", so the test is updated to assert state=="passed" and verifies the spec flag is present. The test that asserts a real failure still blocks is unchanged. No-PRD: test-only update to match the behaviour change in the same PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
| Spec("Drift Bot", "drift-bot"), | ||
| # | ||
| # skip_if_unreported=True: the 8090 App only posts drift-bot on PRs that | ||
| # touch Python or product-record files. Frontend-only npm bumps (dependabot |
There was a problem hiding this comment.
The blueprint's Key Contracts state: "Only a check whose workflow reports on every pull request may appear in RequiredCheckSpec, because a workflow that stays silent would hold the gate pending until timeout and block every merge." The code now implements skip_if_unreported=True for Drift Bot, explicitly allowing a check that does not report on all PR types to pass the gate without reporting, which contradicts this foundational contract.
| for spec in specs: | ||
| matched = {n: r for n, r in best.items() if spec.matches(n)} | ||
|
|
||
| # When skip_if_unreported is set and no status has been posted at all, | ||
| # treat the spec as passed (skipped). A reporter that posts but fails | ||
| # is still caught below -- this only short-circuits the "never posted" | ||
| # hang that occurs when a GitHub App does not evaluate this PR type. | ||
| if spec.skip_if_unreported and not matched: | ||
| results.append(SpecResult(spec, "passed", "no status reported, treated as skipped")) |
There was a problem hiding this comment.
The blueprint documents that MergeGateEvaluator "resolves each entry of RequiredCheckSpec against the check runs" and includes no responsibility for conditionally skipping checks. The code now implements conditional skip logic that treats unreported checks as passed, which adds an undocumented responsibility to the evaluator.
CI status after test fix pushSyntax & Lint — fixed in commit Drift Bot findings — blocked. Drift Bot posted 2 findings against the blueprint Release Verification and Merge Gating:
The What's needed: update the blueprint in 8090 Software Factory to document that a Until the blueprint is updated, Drift Bot will keep posting a Generated by Claude Code |
|
|
|
||
| ``skip_if_unreported`` marks a spec whose reporter may legitimately never | ||
| post on certain PR types (e.g. a GitHub App that only evaluates Python or | ||
| product-record changes). When True and zero check-runs/statuses have been |
There was a problem hiding this comment.
The blueprint's Key Contracts state: "Only a check whose workflow reports on every pull request may appear in RequiredCheckSpec." The code now implements skip_if_unreported=True for Drift Bot, explicitly allowing a check that does not report on all PR types to pass the gate without reporting, which contradicts this foundational contract.
| for spec in specs: | ||
| matched = {n: r for n, r in best.items() if spec.matches(n)} | ||
|
|
||
| # When skip_if_unreported is set and no status has been posted at all, | ||
| # treat the spec as passed (skipped). A reporter that posts but fails | ||
| # is still caught below -- this only short-circuits the "never posted" | ||
| # hang that occurs when a GitHub App does not evaluate this PR type. | ||
| if spec.skip_if_unreported and not matched: | ||
| results.append(SpecResult(spec, "passed", "no status reported, treated as skipped")) |
There was a problem hiding this comment.
The blueprint documents that MergeGateEvaluator "resolves each entry of RequiredCheckSpec against the check runs" with no responsibility for conditionally skipping checks. The code now implements conditional skip logic (skip_if_unreported) that treats unreported checks as passed, adding an undocumented responsibility to the evaluator.
|
|
||
| def test_a_missing_drift_bot_does_not_pass_the_gate(): | ||
| """No status at all must block, not silently satisfy the spec.""" | ||
| def test_a_missing_drift_bot_skips_not_blocks(): |
There was a problem hiding this comment.
The test named test_a_missing_drift_bot_does_not_pass_the_gate is renamed to test_a_missing_drift_bot_skips_not_blocks with its expected behavior changed from "pending" (blocked) to "passed" (skipped), formalizing a departure from the blueprint's Key Contract that missing checks block the gate.
|
✨ auto-fixed: merged latest main into branch to bring it up to date Generated by Claude Code |
|
Auto-rebase janitor note: This PR is blocked by the very check it's fixing. Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep) Generated by Claude Code |
|
Automated sweep — PR unblock attempt This PR is blocked by Drift Bot (3 drift finding(s)) from 8090 Software Factory. The E2E Gate polls commit statuses on startup and exits immediately when What's needed to unblock: Resolve the blueprint drift in the 8090 Software Factory product record for this PR's changes. Once Drift Bot posts No code changes can be pushed from this automated session to fix this — the Drift Bot finding requires a product-record correction in 8090 Factory, which only the PR author can do. Generated by Claude Code |
|
|
|
||
| ``skip_if_unreported`` marks a spec whose reporter may legitimately never | ||
| post on certain PR types (e.g. a GitHub App that only evaluates Python or | ||
| product-record changes). When True and zero check-runs/statuses have been |
There was a problem hiding this comment.
The blueprint's Key Contract states "Only a check whose workflow reports on every pull request may appear in RequiredCheckSpec". The code now adds a skip_if_unreported field to Spec that explicitly violates this contract by allowing checks that don't report on all PR types to pass the gate.
| # this spec a guaranteed 30-min hang on every such PR. When the App does | ||
| # post (on any PR touching Python or product files), the gate still enforces | ||
| # it. Only the "never posted" case is treated as skipped. | ||
| Spec("Drift Bot", "drift-bot", skip_if_unreported=True), |
There was a problem hiding this comment.
Drift Bot is configured with skip_if_unreported=True, but it is a check that does not report on all PR types (only on PRs touching Python or product-record files). This directly contradicts the Key Contract that only checks reporting on every PR may appear in RequiredCheckSpec.
| for spec in specs: | ||
| matched = {n: r for n, r in best.items() if spec.matches(n)} | ||
|
|
||
| # When skip_if_unreported is set and no status has been posted at all, |
There was a problem hiding this comment.
MergeGateEvaluator now implements conditional skip logic that treats unreported checks as "passed (skipped)". The blueprint documents that MergeGateEvaluator has responsibility to "resolve each entry of RequiredCheckSpec against the check runs" with no conditional skipping logic, but the code adds undocumented responsibility for handling skip_if_unreported.
|
| @@ -59,11 +59,19 @@ class Spec: | |||
| before the spec can pass. For a single job that is 1. For a matrix it is the | |||
There was a problem hiding this comment.
The blueprint's Key Contract states "Only a check whose workflow reports on every pull request may appear in RequiredCheckSpec". The code now adds a skip_if_unreported field that explicitly permits checks not reporting on all PR types to pass the gate, violating this foundational contract.
|
Automated maintainer check — action required from a human with admin access. This PR is in a circular dependency with the branch protection rules it fixes:
The result: every open PR in this repo has PRs that are green in CI and should become mergeable once this lands:
Recommended order after merging this PR:
PRs that have real CI failures (not gate-related) and still need code fixes: #5413 (NeMoClaw OTel, 30-min gate run), #5367 (CodeQL finding in guard enforcement). Generated by Claude Code |
|
| label: str | ||
| pattern: str | ||
| min_count: int = 1 | ||
| skip_if_unreported: bool = False |
There was a problem hiding this comment.
The blueprint's Key Contract states "Only a check whose workflow reports on every pull request may appear in RequiredCheckSpec." The code adds a skip_if_unreported field to Spec that explicitly permits checks not reporting on all PR types to pass when unreported, directly violating this foundational contract.
| # i18next / postcss / react-router / etc.) never receive a status -- making | ||
| # this spec a guaranteed 30-min hang on every such PR. When the App does | ||
| # post (on any PR touching Python or product files), the gate still enforces | ||
| # it. Only the "never posted" case is treated as skipped. |
There was a problem hiding this comment.
Drift Bot is configured with skip_if_unreported=True despite only reporting on Python/product-record changes, not all PR types. This violates the blueprint's Key Contract that only checks reporting on every pull request may appear in RequiredCheckSpec.
|
Automated sweep — blocker diagnosed, cannot self-resolve The E2E Gate required check is failing due to Drift Bot (8090 Software Factory) posting a What was confirmed:
What's needed to unblock:
No code changes were pushed during this sweep. The PR is otherwise ready for merge once the Drift Bot status clears. Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep) Drift Bot is posting a failure on the Generated by Claude Code |
|
| label: str | ||
| pattern: str | ||
| min_count: int = 1 | ||
| skip_if_unreported: bool = False |
There was a problem hiding this comment.
The blueprint's Key Contract states "Only a check whose workflow reports on every pull request may appear in RequiredCheckSpec." The code adds a skip_if_unreported field to Spec that explicitly permits checks not reporting on all PR types to pass when unreported, directly violating this foundational contract.
| # i18next / postcss / react-router / etc.) never receive a status -- making | ||
| # this spec a guaranteed 30-min hang on every such PR. When the App does | ||
| # post (on any PR touching Python or product files), the gate still enforces | ||
| # it. Only the "never posted" case is treated as skipped. |
There was a problem hiding this comment.
Drift Bot is configured with skip_if_unreported=True despite only reporting on Python/product-record changes, not all PR types. This violates the blueprint's Key Contract that only checks reporting on every pull request may appear in RequiredCheckSpec.
|
Autonomous maintainer run — 2026-09-01 — ACTION NEEDED This PR's CI shows The branch has been rebased against the latest main. CI should re-run automatically. The chicken-and-egg problem: This PR fixes the E2E Gate, but the E2E Gate is required to merge it. If the new CI run fails the E2E Gate again, a repo admin needs to temporarily remove Once this PR merges, the E2E Gate will work correctly and the following PRs can be merged in order:
Generated by Claude Code |
Test plan & review notesRepo: vivekchand/clawmetry What changed
Current CI status The one unblock path: update the Release Verification and Merge Gating blueprint at factory.8090.ai to document Smoke commands
Generated by Claude Code |
|
| label: str | ||
| pattern: str | ||
| min_count: int = 1 | ||
| skip_if_unreported: bool = False |
There was a problem hiding this comment.
The blueprint's Key Contract states "Only a check whose workflow reports on every pull request may appear in RequiredCheckSpec." The code adds a skip_if_unreported field to Spec that explicitly allows checks not reporting on all PR types to pass when unreported, contradicting this foundational contract.
| # this spec a guaranteed 30-min hang on every such PR. When the App does | ||
| # post (on any PR touching Python or product files), the gate still enforces | ||
| # it. Only the "never posted" case is treated as skipped. | ||
| Spec("Drift Bot", "drift-bot", skip_if_unreported=True), |
There was a problem hiding this comment.
Drift Bot is configured with skip_if_unreported=True despite only reporting on Python/product-record changes, not on all PR types. This violates the blueprint's Key Contract that only checks reporting on every pull request may appear in RequiredCheckSpec.
| for spec in specs: | ||
| matched = {n: r for n, r in best.items() if spec.matches(n)} | ||
|
|
||
| # When skip_if_unreported is set and no status has been posted at all, |
There was a problem hiding this comment.
MergeGateEvaluator now implements conditional skip logic (skip_if_unreported) that treats unreported checks as "passed (skipped)". The blueprint specifies MergeGateEvaluator's responsibility is to "resolve each entry of RequiredCheckSpec against the check runs" with no conditional skipping for unreported checks, adding an undocumented capability that bypasses the Key Contract.
|
✨ auto-fixed: merged origin/main into branch (was behind by multiple commits); new CI run triggered. Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep) Blocker: E2E Gate (required) fails because Generated by Claude Code |
|
✨ auto-fixed: merged main into branch (was behind by ~20 commits) Generated by Claude Code |
|
| label: str | ||
| pattern: str | ||
| min_count: int = 1 | ||
| skip_if_unreported: bool = False |
There was a problem hiding this comment.
The blueprint's Key Contract states "Only a check whose workflow reports on every pull request may appear in RequiredCheckSpec." The code adds a skip_if_unreported field to Spec that explicitly allows checks not reporting on all PR types to pass when unreported, directly violating this foundational contract.
| # this spec a guaranteed 30-min hang on every such PR. When the App does | ||
| # post (on any PR touching Python or product files), the gate still enforces | ||
| # it. Only the "never posted" case is treated as skipped. | ||
| Spec("Drift Bot", "drift-bot", skip_if_unreported=True), |
There was a problem hiding this comment.
Drift Bot is configured with skip_if_unreported=True despite only reporting on Python/product-record changes, not all PR types. This violates the blueprint's Key Contract that only checks reporting on every pull request may appear in RequiredCheckSpec.
|
blocked on author decision — skipping (auto-mergeability sweep): E2E Gate (required) is failing because Drift Bot posted Generated by Claude Code |
|
CI status — automated sweep 2026-09-02 The required E2E Gate check is failing because the The gate exits immediately on a drift-bot What's needed: Address the Drift Bot product-review finding through the 8090 Software Factory process (update the requirement/blueprint, or add a No other blockers were identified at time of sweep. Generated by Claude Code |
|
blocked on author decision — skipping (auto-mergeability sweep) All CI checks pass. Blocked solely on required reviewer approval. Generated by Claude Code |
No-PRD: regression fix to merge-gate configuration, no user-facing feature change.
Problem
The
e2e-gaterequired-status-check was timing out after 30 min on every frontend/npm dependabot PR (13+ active PRs affected). Two root causes:Drift Bot never posts on frontend-only PRs. The 8090-software-factory GitHub App only posts its
drift-botcommit status when a PR touches Python or product-record files. On an npm bump (react-router, i18next, postcss, etc.) the gate polled for the full 1800 s waiting for a status that would never arrive.Runner starvation from concurrent dependabot PRs. Each of the 13+ open dependabot PRs has a unique PR number, so each gets its own
ci-${{ github.event.pull_request.number }}concurrency group. All 143+ ci.yml jobs queue simultaneously, causing the 8 required checks (MOAT Keystone, E2E Browser Tests, API Tests x3, etc.) to sit pending past the 30-min MAX_WAIT even after lint cleared.Fix
scripts/e2e_gate.pyskip_if_unreported: bool = Falsefield toSpecdataclass. WhenTrue, if zero check-runs or commit statuses have been posted the spec evaluates to "passed (skipped)" immediately. A failing status still fails the gate normally.Drift Botspec toskip_if_unreported=True.DEFAULT_MAX_WAITfrom 1800 s to 3000 s to give slower-starting jobs more headroom.--listoutput to show[skip_if_unreported]flag..github/workflows/e2e-gate.ymltimeout-minutesfrom 35 to 65.MAX_WAITenv var from"1800"to"3600".Impact
Generated by Claude Code