Skip to content

fix: full-sync NOP results without check run + idempotent ruleset create - #1047

Open
decyjphr wants to merge 2 commits into
yadhav/fix-recent-issuesfrom
decyjphr-incorporate-pr-1018-nop-results
Open

fix: full-sync NOP results without check run + idempotent ruleset create#1047
decyjphr wants to merge 2 commits into
yadhav/fix-recent-issuesfrom
decyjphr-incorporate-pr-1018-nop-results

Conversation

@decyjphr

@decyjphr decyjphr commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Why

Two related robustness bugs surfaced during full-sync and repo-creation flows:

  1. Full-sync NOP runs crashed. A NOP run triggered outside the webhook flow (e.g. the full-sync entrypoint) builds a minimal payload that has no check_run or repository. handleResults unconditionally dereferenced payload.check_run.id and payload.repository.owner.login, so a scheduled or CLI dry-run died after doing all its work instead of reporting a plan. (Incorporates PR fix(settings): handle nop mode results when check run is missing #1018.)

  2. Ruleset creation failed with 422 "Name must be unique". POST /rulesets is not idempotent. When Octokit's retry/auth-app layer re-sends a create that already succeeded (observed with retryCount: 1), or a repo is processed by two overlapping syncs (a full sync racing with the repository.created webhook), the second create hits a duplicate-name 422 even though the ruleset now exists, failing the run.

Approach

lib/settings.js (NOP full-sync guard): When the payload lacks check_run or repository, write the deduplicated dry-run results to the log and return before any check-run/PR-comment reporting. The full diff can contain sensitive config values (e.g. Actions variables), so it goes to debug (passed as an object so it is only serialized when debug is emitted) while info gets a value-free summary. The webhook flow is untouched.

lib/plugins/rulesets.js (idempotent create): On a 422 "Name must be unique" from add(), reconcile instead of failing: re-fetch existing rulesets via find(), match by name via comparator, and update() the existing ruleset in place. Any other error (including a non-uniqueness 422) still surfaces normally. Applies to both repo and org scope.

Notes for reviewers

  • The reconcile path relies on update() preferring the existing ruleset's numeric id, so a config that carries no id (the normal case) is updated against the real ruleset.
  • Only the exact "Name must be unique" 422 triggers reconciliation; other validation errors are not masked.

Tests

  • Added handleResults coverage: full-sync dry runs without a check run / without a repository log a value-free summary and never call the checks API.
  • Added rulesets coverage: repo and org reconcile-by-update on duplicate name, plus a non-uniqueness 422 that still surfaces as an error.
  • Full unit suite passes (338 passed); changed lib files lint clean.

decyjphr and others added 2 commits August 5, 2026 19:13
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A ruleset POST is not idempotent. When Octokit retries a create that already succeeded, or a repo is processed by two overlapping syncs (full sync racing with repository.created), the second create fails with 422 'Name must be unique'. Reconcile by looking the existing ruleset up by name and updating it in place.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Improves robustness of dry-run full-sync execution and ruleset creation by preventing crashes when webhook-only payload fields are missing and by making ruleset creation effectively idempotent under retries/concurrency.

Changes:

  • Guarded NOP/dry-run result handling to avoid dereferencing payload.check_run / payload.repository and to log a value-free summary instead of attempting check-run reporting.
  • Added reconciliation logic for POST /rulesets duplicate-name 422 responses by re-fetching the existing ruleset and updating it in place.
  • Added unit tests covering both the full-sync dry-run guard and the ruleset duplicate-name reconciliation behavior.
Show a summary per file
File Description
test/unit/lib/settings.test.js Adds coverage ensuring full-sync dry runs without check_run/repository log a value-free summary and do not call the Checks API.
test/unit/lib/plugins/rulesets.test.js Adds coverage for reconcile-by-update on duplicate-name 422s for both repo/org rulesets, and verifies other 422s still surface.
lib/settings.js Adds an early-return guard for NOP full-sync runs missing webhook payload fields, logging summary at info and full results at debug.
lib/plugins/rulesets.js Adds duplicate-name detection and reconciliation path to make ruleset creation resilient to retried/concurrent POSTs.

Review details

Tip

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

  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread lib/plugins/rulesets.js
if (!match) {
return this.handleError(e)
}
return this.update(match, attrs)
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.

2 participants