Skip to content

feat(compliance-audit): detect stale required-check names in rulesets#96

Merged
don-petry merged 3 commits intomainfrom
feat/compliance-audit-ruleset-drift
Apr 8, 2026
Merged

feat(compliance-audit): detect stale required-check names in rulesets#96
don-petry merged 3 commits intomainfrom
feat/compliance-audit-ruleset-drift

Conversation

@don-petry
Copy link
Copy Markdown
Contributor

@don-petry don-petry commented Apr 8, 2026

Closes #92.

Adds check_centralized_check_names to compliance-audit.sh. Inspects both the new ruleset system and classic branch protection, then flags:

  1. Stale pre-centralization names (claude, claude-issue, AgentShield, Detect ecosystems) — emits stale-required-check-<old> with the canonical replacement in the message.
  2. claude-code / claude as required — emits required-claude-code-check-broken because that check is structurally incompatible with workflow-modifying PRs (claude-code-action's app-token validation refuses to mint a token whenever the PR diff includes a workflow file). This was the exact root cause of the markets/bmad-bgreat-suite stuck PRs from the feat(workflows): centralize standards via reusable workflows #87 sweep.

Test plan

  • bash -n and shellcheck clean
  • Hand-fixture unit test in /tmp covering all four scenarios:
    • stale claude → flagged
    • stale AgentShield → flagged
    • claude-code / claude listed as required → flagged with broken-deadlock message
    • clean (agent-shield / AgentShield only) → no finding
  • CI on this branch
  • After merge: next scheduled audit run will surface real findings on any repo whose ruleset still has stale names

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Added an audit check that validates required status-check names across repositories, reporting stale legacy check names (with suggested renames) and detecting forbidden Claude-related checks.
    • Skips repository metadata-only cases and tolerates unavailable protection endpoints to avoid noisy failures.

Copilot AI review requested due to automatic review settings April 8, 2026 16:48
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

Warning

Rate limit exceeded

@don-petry has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 54 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 54 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 452694fb-3bf7-49bf-b94f-833519c35004

📥 Commits

Reviewing files that changed from the base of the PR and between 39faea0 and 13c8024.

📒 Files selected for processing (1)
  • scripts/compliance-audit.sh
📝 Walkthrough

Walkthrough

Adds a new compliance check that fetches required-status-check contexts from branch rulesets and classic branch protection for each audited repo, detects stale pre-centralization check names (with suggested renames), and flags forbidden Claude-related required checks.

Changes

Cohort / File(s) Summary
Compliance audit script
scripts/compliance-audit.sh
Added check_centralized_check_names() to gather required-status-check contexts from rulesets (repos/<repo>/rules/branches/main) and classic protection (repos/<repo>/branches/main/protection), detect stale check names (e.g., claude, AgentShield, Detect ecosystems) with mapped renames, flag broken claude-code / claude, skip .github, and integrated call into main() after check_centralized_workflow_stubs. Handles absent API endpoints by optionally suppressing errors and returns early when no contexts found.

Sequence Diagram(s)

sequenceDiagram
    participant Auditor as Compliance Script
    participant GHRules as GitHub Rulesets API
    participant GHBranch as GitHub Branch Protection API
    participant Emitter as Findings Emitter

    Auditor->>GHRules: GET required-status-checks for branches/main
    GHRules-->>Auditor: ruleset contexts (or 404)
    Auditor->>GHBranch: GET branch protection required status checks
    GHBranch-->>Auditor: classic contexts (or 404)
    Auditor->>Auditor: merge contexts, skip .github, return if none
    Auditor->>Auditor: detect stale names & forbidden Claude checks
    Auditor->>Emitter: emit findings for each detected issue
    Emitter-->>Auditor: acknowledgment
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding detection of stale required-check names in rulesets to the compliance-audit script.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #92: detects stale check names with rename mappings [#92], flags forbidden claude variants [#92], and handles both ruleset and classic branch protection APIs [#92].
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the check_centralized_check_names function and integrating it into main() as required by issue #92; no extraneous modifications present.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/compliance-audit-ruleset-drift

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new compliance-audit check to detect required-status-check configuration drift after workflow centralization, preventing merge gates from referencing nonexistent (or structurally problematic) check contexts.

Changes:

  • Introduces check_centralized_check_names() to scan both rulesets and classic branch protection for stale required-check names and suggest canonical replacements.
  • Flags claude-code / claude being required as a broken configuration that can deadlock workflow-modifying PRs.
  • Wires the new check into the audit main() loop.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/compliance-audit.sh`:
- Around line 581-585: The script currently instructs maintainers to rename the
deprecated "claude" check to "claude-code / claude" (string "claude:claude-code
/ claude") while elsewhere flags "claude-code / claude" as a forbidden/broken
required check, causing contradictory remediation; create a dedicated finding
and message for the "claude" case that tells maintainers to stop requiring the
Claude review check (remove it) instead of suggesting a rename, remove or
replace the "claude:claude-code / claude" entry and any user-facing suggestions
that propose "claude-code / claude", and update the ruleset-application logic
that currently recreates banned checks so it will not re-add the Claude-required
check when enforcing rules.
- Around line 578-612: The script currently marks entries from renames as stale
based only on ruleset/classic contexts (variables
ruleset_contexts/classic_contexts and contexts) in the for-loop that checks each
entry and calls add_finding; change this to compute the repo's actual published
canonical check names using the same logic as scripts/apply-rulesets.sh (the
workflow name/job-name derivation around lines 46-136) or at minimum verify the
centralized stub exists before flagging: for each rename in renames, fetch the
repo workflows and derive the current check context name(s), compare those
derived canonical names to old/new, and only call add_finding if the repo is
publishing the centralized stub (or the derived canonical name equals the
centralized name) rather than relying solely on contexts from rulesets/classic
branch protection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a5a681b2-6163-4a2d-904e-ce61519cbd36

📥 Commits

Reviewing files that changed from the base of the PR and between 67cb057 and 8cf6bc4.

📒 Files selected for processing (1)
  • scripts/compliance-audit.sh

Comment thread scripts/compliance-audit.sh Outdated
Comment thread scripts/compliance-audit.sh Outdated
don-petry pushed a commit that referenced this pull request Apr 8, 2026
…claude

CodeRabbit on #96: the rename map said `claude` → `claude-code / claude`,
but a separate check correctly flags `claude-code / claude` as a forbidden
required check (it deadlocks workflow PRs). Following the rename
recommendation would have moved a repo from one broken state to another.

Fix: split the logic into two distinct sets.

1. `renames[]` — only contains checks where the new name is safe to
   require (AgentShield, Detect ecosystems). These get a "rename to X"
   message.

2. `forbidden_required[]` — contains every claude variant (legacy and
   post-centralization). Any of them as a required check emits a
   stable per-name finding telling the maintainer to REMOVE it from
   required checks, not rename it.

The Claude review check still runs and surfaces feedback on normal PRs
without being a merge gate; only the required-status-checks pin is
removed.

Each forbidden_required entry maps to a stable check id so findings
don't churn across audit runs from slashes in canonical names.

Verified locally with stub fixtures for all five cases:
  stale `claude`              -> required-claude-check-broken
  stale `claude-issue`        -> required-claude-issue-check-broken
  `claude-code / claude`      -> required-claude-code-check-broken
  stale `AgentShield`         -> stale-required-check-AgentShield (rename)
  clean ruleset               -> 0 findings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
scripts/compliance-audit.sh (2)

578-596: ⚠️ Potential issue | 🟠 Major

Derive the replacement check name before calling a required check “stale.”

This logic assumes every repo now publishes agent-shield / AgentShield or dependency-audit / Detect ecosystems, but it never verifies that. Repos still on the inline pre-centralization workflow — or stubs whose workflow name: has drifted — can still legitimately publish the old context, so this will raise a false stale-required-check-* finding and tell maintainers to rename to a check that does not exist yet. Please reuse the same context-derivation logic as the ruleset generator, or at minimum gate this finding on the corresponding centralized stub already being in place.

Also applies to: 628-633

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/compliance-audit.sh` around lines 578 - 596, The current
stale-required-check detection uses the static renames list (local renames) and
marks a required-check as "stale" without verifying the replacement actually
exists; update the logic so that before emitting a stale-required-check finding
you derive the replacement context the same way the ruleset generator does
(reuse its context-derivation helper) or at minimum check that the corresponding
centralized stub/workflow is present in the repo; specifically modify the code
that iterates over renames (the renames array and the stale-check detection
branch) to call the ruleset generator's derivation function or perform a
presence check of the centralized stub and only emit the rename remediation when
the derived replacement is confirmed present.

654-656: ⚠️ Potential issue | 🟠 Major

Don’t point remediation at apply-rulesets.sh until it stops emitting Claude required checks.

This message sends maintainers to scripts/apply-rulesets.sh as the canonical required-check list, but scripts/apply-rulesets.sh:55-64 still adds claude or ${workflow_name} / claude. Using that remediation path will put repos straight back into a state this audit marks broken, so the finding will churn forever. Please land the generator change in the same PR, or remove this reference until the canonical ruleset list no longer writes Claude review checks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/compliance-audit.sh` around lines 654 - 656, The remediation link in
the add_finding call points maintainers to scripts/apply-rulesets.sh even though
that script still injects `claude`/`${workflow_name} / claude` required checks;
update the add_finding message to stop referencing scripts/apply-rulesets.sh (or
replace it with guidance to land the generator change in the same PR) until
scripts/apply-rulesets.sh is changed to stop writing Claude checks, and ensure
the message explicitly mentions `claude` / `${workflow_name} / claude` so
maintainers know not to re-add those checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/compliance-audit.sh`:
- Around line 601-606: The current forbidden_required exact-match entries only
block a few hardcoded workflow prefixes; update the checks to reject any
required context that ends with " / claude" or " / claude-issue". Replace the
explicit entries in the forbidden_required array (and the other similar array
around the 647-651 block) with suffix patterns like "* / claude" and "* /
claude-issue" (or keep just " / claude" and " / claude-issue" and change the
comparison to use suffix matching like [[ "$context" == *" / claude" ]] / *" /
claude-issue" ]), so the script matches by suffix rather than exact
workflow-name prefixes. Ensure both arrays and all comparison locations use the
same suffix-matching logic.

---

Duplicate comments:
In `@scripts/compliance-audit.sh`:
- Around line 578-596: The current stale-required-check detection uses the
static renames list (local renames) and marks a required-check as "stale"
without verifying the replacement actually exists; update the logic so that
before emitting a stale-required-check finding you derive the replacement
context the same way the ruleset generator does (reuse its context-derivation
helper) or at minimum check that the corresponding centralized stub/workflow is
present in the repo; specifically modify the code that iterates over renames
(the renames array and the stale-check detection branch) to call the ruleset
generator's derivation function or perform a presence check of the centralized
stub and only emit the rename remediation when the derived replacement is
confirmed present.
- Around line 654-656: The remediation link in the add_finding call points
maintainers to scripts/apply-rulesets.sh even though that script still injects
`claude`/`${workflow_name} / claude` required checks; update the add_finding
message to stop referencing scripts/apply-rulesets.sh (or replace it with
guidance to land the generator change in the same PR) until
scripts/apply-rulesets.sh is changed to stop writing Claude checks, and ensure
the message explicitly mentions `claude` / `${workflow_name} / claude` so
maintainers know not to re-add those checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1ba30ca6-5404-4f34-b4a4-99e5cf653027

📥 Commits

Reviewing files that changed from the base of the PR and between 8cf6bc4 and 39faea0.

📒 Files selected for processing (1)
  • scripts/compliance-audit.sh

Comment thread scripts/compliance-audit.sh Outdated
DJ and others added 3 commits April 8, 2026 10:14
Closes #92.

Adds `check_centralized_check_names` to compliance-audit.sh. For every
non-`.github` repo in the org, fetches the active required-status-check
contexts from BOTH the new ruleset system (gh api .../rules/branches/main)
and classic branch protection (gh api .../branches/main/protection),
then flags two distinct problems:

1. Stale pre-centralization names (`claude`, `claude-issue`,
   `AgentShield`, `Detect ecosystems`) — emits
   `stale-required-check-<old-name>` with the canonical replacement in
   the message.

2. `claude-code / claude` listed as required — emits
   `required-claude-code-check-broken` because that check is
   structurally incompatible with workflow-modifying PRs:
   claude-code-action's GitHub App refuses to mint a token whenever
   the PR diff includes a workflow file, so the check fails on every
   workflow PR and the merge gate becomes a deadlock. This was the
   exact root cause of the markets/bmad-bgreat-suite stuck PRs from
   #87 sweep.

Tested locally with stub fixtures for all four cases (stale claude,
stale AgentShield, broken claude-code/claude, clean ruleset).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…claude

CodeRabbit on #96: the rename map said `claude` → `claude-code / claude`,
but a separate check correctly flags `claude-code / claude` as a forbidden
required check (it deadlocks workflow PRs). Following the rename
recommendation would have moved a repo from one broken state to another.

Fix: split the logic into two distinct sets.

1. `renames[]` — only contains checks where the new name is safe to
   require (AgentShield, Detect ecosystems). These get a "rename to X"
   message.

2. `forbidden_required[]` — contains every claude variant (legacy and
   post-centralization). Any of them as a required check emits a
   stable per-name finding telling the maintainer to REMOVE it from
   required checks, not rename it.

The Claude review check still runs and surfaces feedback on normal PRs
without being a merge gate; only the required-status-checks pin is
removed.

Each forbidden_required entry maps to a stable check id so findings
don't churn across audit runs from slashes in canonical names.

Verified locally with stub fixtures for all five cases:
  stale `claude`              -> required-claude-check-broken
  stale `claude-issue`        -> required-claude-issue-check-broken
  `claude-code / claude`      -> required-claude-code-check-broken
  stale `AgentShield`         -> stale-required-check-AgentShield (rename)
  clean ruleset               -> 0 findings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address remaining CodeRabbit feedback on #96: the previous exact-match
list (`claude-code / claude`, `claude-code / claude-issue`) only caught
the canonical caller-job-id. Repos with a custom caller — e.g. a
ruleset that pins `Claude Code / claude` (workflow display name) or
`review-claude / claude` (custom job id) — would slip past the audit
even though they're equally broken.

Fix: classify each context line by suffix:

  - bare `claude` / `claude-issue`            → match
  - `<anything> / claude`                     → match
  - `<anything> / claude-issue`               → match

Then map to a stable check id (claude vs claude-issue) so findings
don't churn across audit runs from prefix variation. The full
forbidden context string is still echoed in the finding message so
maintainers see exactly what to remove.

Verified locally with stub fixtures for 8 cases:
  bare claude / claude-issue
  canonical claude-code / claude{,-issue}
  custom-prefix Claude Code / claude
  weird-prefix review-claude / claude
  stale AgentShield (rename, unaffected)
  clean ruleset (no findings)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@don-petry don-petry force-pushed the feat/compliance-audit-ruleset-drift branch from 39faea0 to 13c8024 Compare April 8, 2026 17:16
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 8, 2026

@don-petry don-petry merged commit 99bd596 into main Apr 8, 2026
18 checks passed
@don-petry don-petry deleted the feat/compliance-audit-ruleset-drift branch April 8, 2026 17:21
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.

compliance-audit: detect stale required-check names in rulesets

2 participants