fix(rulesets): resolve {{EXTERNALLY_DEFINED}} before diffing (incorporate PR #1023) - #1048
Merged
decyjphr merged 1 commit intoAug 9, 2026
Conversation
…rate PR #1023) Diffable.sync() now calls an optional resolveOverrides(existingRecords, filteredEntries) hook after find() and before the comparison. The rulesets plugin implements it: each config entry is matched to its live record (via comparator) and passed through Overrides.removeOverrides, so {{EXTERNALLY_DEFINED}} placeholders resolve to the live values before changed() ever sees them. This mirrors how branches.js resolves overrides inside compareDeep. Behavior change (intentional, fixes #1022): a ruleset whose only difference from GitHub is the placeholder no longer reports "Update Ruleset" in dry runs and no longer issues a redundant PUT in apply mode. Rulesets with real differences behave exactly as before. Entries are cloned (structuredClone) before resolution since removeOverrides mutates its input. Tests updated to the new behavior and nop-mode coverage added. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes false-positive ruleset diffs when config uses the {{EXTERNALLY_DEFINED}} placeholder (notably in required_status_checks) by resolving overrides against live GitHub rulesets before diffing, preventing redundant “Update Ruleset” plans and unnecessary PUTs.
Changes:
- Add an opt-in
resolveOverrides(existingRecords, filteredEntries)hook toDiffable.sync()that runs afterfind()and before comparison. - Implement
resolveOverridesin the rulesets plugin to replace{{EXTERNALLY_DEFINED}}with live values prior tochanged()/diffing. - Update and extend unit tests to cover the new “placeholder-only == no change” behavior, including nop-mode planning.
Show a summary per file
| File | Description |
|---|---|
| lib/plugins/diffable.js | Adds an opt-in pre-diff hook to let plugins resolve placeholders against live records. |
| lib/plugins/rulesets.js | Implements the hook for rulesets by resolving {{EXTERNALLY_DEFINED}} via Overrides.removeOverrides before diffing. |
| test/unit/lib/plugins/rulesets.test.js | Updates expectations and adds nop-mode coverage for placeholder-only vs genuine diffs. |
Review details
Tip
Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Rulesets that use the
{{EXTERNALLY_DEFINED}}placeholder (e.g. inrequired_status_checks) were falsely reported as changed on every run. This branch already resolved the placeholder insideupdate()/add(), but only on the apply (non-nop) path, so the comparison still saw the literal placeholder string. The result: dry runs reported a redundant "Update Ruleset" and apply mode issued a redundant PUT even when nothing had actually changed (fixes the behavior in #1022).This incorporates the fix from upstream PR #1023 into the comprehensive
yadhav/fix-recent-issuesline, where it was not yet handled.Approach
Diffable.sync()now exposes an optionalresolveOverrides(existingRecords, filteredEntries)hook, called afterfind()and before the comparison. Plugins can opt in to resolve config placeholders against the live records so placeholders never register as differences.The rulesets plugin implements the hook: each config entry is matched to its live record (via the existing
comparator) and passed throughOverrides.removeOverrides, so{{EXTERNALLY_DEFINED}}resolves to the live values beforechanged()ever runs. This mirrors howbranches.jsalready resolves overrides inside itscompareDeepcall. The hook is opt-in per plugin, so no other diffable plugin changes behavior.Notes for reviewers
update()would have used.structuredClonebefore resolution becauseremoveOverridesmutates its input, so the loaded config is never modified.overridesobject usesaction: 'delete'forrequired_status_checks, which short-circuits before thetypecheck, so thearrayvsdicttype difference from PR fix(rulesets): resolve{{EXTERNALLY_DEFINED}}before diffing so unchanged rulesets don't plan updates #1023 is a no-op here and was left as-is.Testing
{{EXTERNALLY_DEFINED}}tests to the new behavior and added nop-mode coverage (placeholder-only match plans no update; a genuine diff still plans an update).ghCLI scopes, and the enterprise app_installations phase).