Skip to content

Block scheduler merges without exact-head independent approval #771

Open

Description

@seonghobae

Incident evidence

ContextualWisdomLab/inkspan#60 merged at 2026-08-05T03:30:37Z from exact head b02f1662c7118faf1aaf44b8ea1efb376cfc3a85 into merge commit 45d9bf315a626c686658b3148d3775405c878617 while its PR body still marked Exact-current-head independent approval and protected merge incomplete.

The retrievable review history contains only two dismissed OpenCode CHANGES_REQUESTED reviews, both attached to older head ab32926d84030f0e23cda4a2ca1fd87bf2035761. No approving review attached to the merged head is visible through the PR review API. Direct CI and security success does not substitute for independent approval.

This issue does not assert which actor performed the merge. The available connector does not expose the organization audit-log event, ruleset insight, bypass actor, or merged_by identity. An organization administrator must inspect those records.

Central scheduler defect

scripts/ci/pr_review_merge_scheduler.py currently treats an exact-head OpenCode approval as sufficient to enter direct merge or auto-merge paths. The direct path checks current-head OpenCode evidence, unresolved threads, failed checks, Strix evidence, and mergeability, then invokes gh pr merge --match-head-commit.

It does not require either of these before the mutation:

  • reviewDecision == APPROVED; or
  • an exact-head independent Noema/non-OpenCode approving review.

GitHub rulesets can designate users, roles, teams, or GitHub Apps as bypass actors. A scheduler credential that is accidentally or intentionally on a bypass list can therefore convert this omitted application-level gate into a real protected-merge bypass. GitHub documents both required pull-request review rules and ruleset bypass actors, including pull-request-only bypass mode.

Immediate administrative action

  1. Inspect the organization audit log and Rule Insights for the merge of inkspan#60 at 2026-08-05T03:30:37Z.
  2. Identify the merge actor and whether a repository role, organization administrator, user, team, GitHub App, or token source exercised ruleset or branch-protection bypass.
  3. Inspect all active organization and repository rulesets targeting inkspan:main, including required approval count, latest-push approval, required OpenCode/Noema workflows, conversation resolution, and bypass actors/modes.
  4. Remove merge-scheduler credentials and reviewer Apps from bypass lists unless a separately approved break-glass design requires them. A reviewer identity must not also possess an implicit path around its own gate.
  5. Preserve the audit evidence and record the root cause. Do not retroactively synthesize an approval for test: add scheduler decision contract fixtures #60.

Required code repair

Implement test-first, fail-closed merge eligibility:

  • require reviewDecision == APPROVED before direct merge and before enabling native auto-merge;
  • require an exact-current-head approving review from the configured independent reviewer identity, separate from OpenCode and separate from the last pusher;
  • reject stale, dismissed, commented, fallback-only, body/head-mismatched, or unknown-author reviews;
  • require the independent review after the current head commit and retain the current unresolved-thread, required-check, Strix, security, freshness, and --match-head-commit guards;
  • make absent/unknown review metadata a WAIT, never merge evidence;
  • refuse direct mode when the mutation credential is a configured ruleset bypass actor, or eliminate direct mode in favor of GitHub-native auto-merge after all application-level gates pass;
  • add an explicit machine-readable reason when reviewDecision or independent approval is missing;
  • add regression fixtures reproducing an OpenCode-approved, CLEAN, REVIEW_REQUIRED PR and prove merge_pr()/enable_auto_merge() are never called;
  • add a fixture with stale Noema approval and a current OpenCode approval and prove it remains blocked;
  • add a positive exact-head OpenCode + independent approval + reviewDecision=APPROVED fixture;
  • retain 100% production statement/branch coverage and complete public docstrings;
  • update docs/org-required-workflow-rollout.md and CHANGELOG.md with the incident boundary and rollback.

Acceptance

  • no scheduler path can merge or queue merge with only OpenCode approval;
  • exact-head Noema/independent approval and GitHub review policy must both be satisfied;
  • bypass credentials are removed or constrained by a documented break-glass policy that the routine scheduler cannot use;
  • exact-head central tests, CodeQL, Semgrep, Python Security, Security Scan, OSV, Scorecard, Secret Scan, SBOM, OpenCode, Noema, independent approval, and branch protection pass;
  • the repaired scheduler is replayed against a synthetic and a real target-repository PR without bypassing protections.

Official GitHub documentation

No formal GitHub conformity claim is made.

Activity

  1. seonghobae commented on Aug 7, 2026

    @seonghobae
    ContributorAuthor

    Test-first implementation is now tracked in draft PR #828. Exact current branch head 5735b3427bf6a4210e0eba152f793af0b2fba1ce descends directly from protected main 1131b1bbafb24e455fc8619cdf316813e8721861.

    Permanent read-only Scheduler Independent Approval Quality CI run 31205949241 checked out that exact head and reproduced the scheduler defect deterministically: 4 failed, 1 passed. The four intended RED cases prove the current production scheduler still returns merge when (1) GitHub reports REVIEW_REQUIRED, (2) GitHub reports APPROVED but no independent exact-head approval exists, (3) the independent approval is bound to a predecessor head, or (4) the only additional approval is from the PR author. The positive exact-head OpenCode + independent approval + reviewDecision=APPROVED fixture passes.

    Keep #828 Draft/RED until the bounded production gate is implemented, full scheduler/central suites and 100% production statement/branch/public-docstring evidence pass, current-head security/supply-chain and automated review gates pass, and normal repository policy/branch protection plus a qualifying independent non-author approval permit integration without bypass.

  2. added
    area: apiAPI, protocol, event, or external contract
    area: authAuthentication, authorization, identity, or tenant isolation
    area: ci-cdCI, GitHub Actions, checks, release, or supply chain
    area: securitySecurity boundary, hardening, or vulnerability prevention
    status: blockedBlocked by conflict, dependency, or required prerequisite
    type: featureNew or expanded product capability
    on Aug 22, 2026
  3. seonghobae commented on Aug 26, 2026

    @seonghobae
    ContributorAuthor

    The Inkspan canary now shows three protected-main admission recurrences under the same governance defect class: #362, #230, and #384. The two newest merges occurred 35 seconds apart on 2026-08-26, and neither has a qualifying formal approval in the complete Reviews API.

    #362 — prior recurrence, known scheduler run excluded

    • contributor head: 9b8c5e689968d2c41d0fdb2ce7160f2e8526702f;
    • protected merge commit: 057762e003fadad418c38ee748bc3e5b20e711eb;
    • merged at 2026-08-25T23:52:48Z, merged_by=seonghobae;
    • complete Reviews API: 0 APPROVED; OpenCode CHANGES_REQUESTED plus COMMENTED-only reviews;
    • final PR body explicitly said the head was not policy-mergeable and retained an unresolved source-ownership dependency.

    Exact-head scheduler run 32859091481 used trusted central scheduler source .github@d2c554dbbc04854db6215970fabb70cef1ceb690, DRY_RUN=false, ENABLE_AUTO_MERGE=true, MERGE_MODE=direct_or_auto, UPDATE_BRANCHES=true, and an opencode-app mutation token, but its actual decision at 2026-08-25T15:01:22Z was WAIT because same-head Strix was still running. It completed at 15:01:26Z; the merge happened at 23:52:48Z, and no later exact-head scheduler run appears in the exact-head inventory. That run did not execute the observed merge.

    #230 — new recurrence, draft-era scheduler run excluded

    • contributor head: 59d82f8b65cfad5230fa9e11afb97867293a43f5;
    • protected merge commit: 1bdd1c4c9d891fcd62d0da549c9cede934ccd41b;
    • merged at 2026-08-26T00:21:01Z, merged_by=seonghobae;
    • complete Reviews API after merge: one Devin COMMENTED, 0 APPROVED;
    • immediately before merge the PR body explicitly recorded the Ready head as not policy-mergeable, with no formal approval and stale/live-base reconciliation plus regenerated exact-head/live-base gates still required.

    The exact-head Actions inventory contains scheduler run 32322068927 from 2026-08-20. Its trusted job ran with merge mutation capability but observed #230 as draft=true and terminated with PR #230: skip: draft PR / contract decision NO_ACTION. No later exact-head scheduler run appears in that inventory. Therefore that known scheduler run did not execute the 2026-08-26T00:21:01Z merge.

    #384 — new recurrence, same-head scheduler run explicitly WAITed

    • contributor head: 78d3f447315f85830b26c4bf81bdb5c376f1fcdf;
    • protected merge commit: 128a239f8b71ca16add9e15e21752d1ad63ff0;
    • merged at 2026-08-26T00:21:36Z, merged_by=seonghobae;
    • complete Reviews API immediately before merge: Devin/CodeRabbit COMMENTED-only, 0 APPROVED;
    • an explicit exact-head review-only request on fix(ci): least-privilege top-level GITHUB_TOKEN permissions #384 stated that it conferred no merge, auto-merge, branch-write, release, ruleset-bypass, or evidence-transfer authority and that COMMENTED/status/model output was not approval.

    The exact-head Actions inventory contains exactly eight required/repository runs. Required PR Review Merge Scheduler run 32875279638 used immutable central scheduler source .github@95b05b393c75931044b405ae491079d336d89293 and PULL_REQUEST_NUMBER=384, DRY_RUN=false, ENABLE_AUTO_MERGE=true, MERGE_MODE=direct_or_auto, UPDATE_BRANCHES=true, with SCHEDULER_MUTATION_TOKEN_SOURCE=opencode-app. Its actual decision at 2026-08-25T17:10:52Z was explicitly WAIT: current-head Strix was complete, but Required OpenCode Review had not materialized and the job lacked a cross-repository dispatch credential. The job completed hours before the 00:21:36Z merge, and no later exact-head scheduler run exists in the eight-run inventory. Run 32875279638 therefore did not execute the observed merge.

    Current falsifiable RCA boundary

    These three incidents no longer support attributing the protected merges to the known exact-head scheduler invocations: #362 logged WAIT, #230 logged NO_ACTION because Draft, and #384 logged WAIT, all before their eventual merges. The remaining live hypotheses are materially distinct:

    1. a later or non-head-indexed routine merge entry point used a credential/identity capable of bypassing required-review policy; or
    2. a human/admin merge used an always-available ruleset/branch-protection bypass.

    merged_by=seonghobae on all three identifies the GitHub merge actor but does not resolve authentication/token provenance or whether GitHub recorded a ruleset bypass. Audit log, Rule Insights, and merge-endpoint/authentication telemetry are therefore the first unresolved causal boundary. Do not retroactively synthesize approval and do not blindly revert product patches merely to repair governance evidence.

    Required owner-path action

    The previous test-first repair PR #828 is closed unmerged, so its RED/GREEN lineage is not protected-main remediation. Continue this existing owner path rather than creating a duplicate:

    1. reconstruct the exact feat(cloudflare): DNS-as-code + reusable Pages deploy for 6 org zones #362/Show changed files in conflict guidance #230/fix(ci): least-privilege top-level GITHUB_TOKEN permissions #384 merge decisions from organization audit log + Rule Insights + merge endpoint/authentication identity; explicitly exclude the known scheduler runs above unless audit evidence contradicts their logged WAIT/NO_ACTION outcomes;
    2. prove whether a qualifying formal approval existed at each decision time; absent such evidence, preserve each as a bypass incident rather than retroactively approving it;
    3. restore the fail-closed contract from fix(scheduler): require independent exact-head approval #828 or a successor across every routine merge entry point, not just the scheduler: reviewDecision == APPROVED, exact-current-head independent non-author approval, stale/predecessor/comment/status/model evidence rejected, missing review metadata => WAIT, no direct/auto-merge mutation otherwise;
    4. ensure routine scheduler/automation/reviewer credentials cannot exercise admin/ruleset bypass; coordinate with the existing central administrator-bypass owner rather than weakening repository rules;
    5. add Show changed files in conflict guidance #230 and fix(ci): least-privilege top-level GITHUB_TOKEN permissions #384 regression fixtures alongside feat(cloudflare): DNS-as-code + reusable Pages deploy for 6 org zones #362: technical gates may be green and threads may be resolved, but zero formal approval must make merge mutation unreachable; Draft or explicit non-mergeable owner/dependency state must also remain fail-closed;
    6. require full current central GREEN and replay a real Inkspan canary under normal policy before closing Block scheduler merges without exact-head independent approval #771.

    Inkspan-side revalidation after central repair: no remaining Inkspan PR may merge unless its then-current exact head/live base has qualifying formal approval, zero unresolved threads, and every applicable current-head gate. Do not transfer #362/#230/#384 evidence to another head.

  4. seonghobae commented on Aug 26, 2026

    @seonghobae
    ContributorAuthor

    Fresh Inkspan policy evidence narrows the live bypass hypothesis without proving which path executed the merges.

    For ContextualWisdomLab/inkspan, active organization ruleset 18156473 (CWL Central required workflows) currently targets the default branch and requires 1 approving review, stale-review dismissal on push, review-thread resolution, and the seven central required workflows (close-empty-pr, OpenCode, merge scheduler, Security Scan, Strix, SAST, Noema). The same live ruleset exposes bypass_actors=[{actor_type: OrganizationAdmin, bypass_mode: always}], and the connected maintainer identity reports current_user_can_bypass=always. The ruleset was updated 2026-08-25T22:02:57.831Z, before the #230/#384 merges.

    This is capability evidence, not proof that the bypass was exercised. It is nevertheless consistent with the remaining hypothesis in the preceding #771 evidence: #384 contributor head 78d3f447315f85830b26c4bf81bdb5c376f1fcdf became protected merge 128a239f8b71ca16add4b9e15e21752d1ad63ff0 with 0 formal APPROVED reviews even though the live policy requires one, while exact-head scheduler run 32875279638 had already logged WAIT and did not perform the observed merge.

    The first unresolved causal boundary therefore remains organization Audit Log + Rule Insights + merge-endpoint/authentication provenance for #362/#230/#384. The owner repair should fail closed across every routine merge entry point and ensure routine automation/reviewer credentials cannot consume the always-admin bypass. Do not remove the review requirement, synthesize approval, or treat the existence of bypass capability as proof of the actor/path. Inkspan release issue #118 should remain blocked on this admission incident until the central repair is protected-main integrated and an Inkspan canary is revalidated under ordinary non-bypass policy.

  5. added
    bugSomething isn't working
    type: bugDefect or incorrect behavior
    enhancementNew feature or request
    on Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: apiAPI, protocol, event, or external contractarea: authAuthentication, authorization, identity, or tenant isolationarea: ci-cdCI, GitHub Actions, checks, release, or supply chainarea: securitySecurity boundary, hardening, or vulnerability preventionbugSomething isn't workingpriority: mediumNormal-priority or P2 workstatus: blockedBlocked by conflict, dependency, or required prerequisitetype: bugDefect or incorrect behaviortype: featureNew or expanded product capability

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions