Skip to content

fix(environments): stop mutating the shared config when expanding branch policies - #1082

Open
ethanejones wants to merge 1 commit into
github-community-projects:main-enterprisefrom
ethanejones:fix-environments-shared-config
Open

ethanejones wants to merge 1 commit into
github-community-projects:main-enterprisefrom
ethanejones:fix-environments-shared-config

Conversation

@ethanejones

@ethanejones ethanejones commented Sep 23, 2026 •

Copy link
Copy Markdown

The Environments constructor rewrites custom_branch_policies on the config objects it is given. Suborg config is one object shared by every repo in the suborg, so the first repo turns ['main'] into [{ name: 'main', type: 'branch' }] and every later repo sees objects that match neither expansion branch. Those repos end up with custom branch policies switched on and an empty allowlist, which blocks every deployment with no error.

This clones the entries before normalising them. Two tests cover it: the caller's config is left untouched, and a second plugin built from the same config gets the same expanded policies as the first.

The same two tests against main-enterprise (left) and this branch (right):

jest output before and after: both tests fail upstream with an empty allowlist on the second repo, both pass with the fix

Fixes #876. The symptom there (custom policies enabled, no rules created, at org and suborg level) matches what we saw on an org with four suborgs declaring the policy.

…nch policies

The Environments constructor rewrote custom_branch_policies on the config
objects it was given. Suborg config is shared by every repo in the suborg,
so the first repo turned ['main'] into [{ name: 'main', type: 'branch' }]
and every later repo saw objects that match neither expansion branch,
ending up with custom branch policies enabled and an empty allowlist.

Clone the entries before normalising them.

Fixes github-community-projects#876
Copilot AI lite review requested due to automatic review settings September 23, 2026 12:10

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.

Copilot review overview

🟢 Approval recommended

The fix is covered by targeted regression tests and resolves the shared-config mutation.

Review effort: Lite
Findings: None

What changed in this PR

Prevents environment policy expansion from mutating shared configuration across repositories.

Changes:

  • Clones entries before normalizing branch policies.
  • Adds regression tests for config immutability and repeated plugin construction.
File Description
test/​unit/​lib/​plugins/​environments.test.js Tests shared-config behavior.
lib/​plugins/​environments.js Clones entries before normalization.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@ethanejones

Copy link
Copy Markdown
Author

@decyjphr when you next do a pass at reviews on this repo this PR is small. 🙂
Just 1 line in environments.js plus two tests and it'll close #876.
Thanks!

@nicomazzarello

nicomazzarello commented Oct 13, 2026 •

Copy link
Copy Markdown

Thanks for this. We hit the same bug on 3.0.0 and are rolling this patch out as a patched image while it waits for review. Three things we found along the way may help it land.

1. The empty allowlist fails open, not closed. Here says the result "blocks every deployment with no error". We tested it, and it's the other way round. An environment with custom branch policies enabled (custom_branch_policies: true) and zero rules, which is exactly what this bug leaves behind, lets a job from any branch deploy. So repos hit by this bug don't lose their deployments; they lose their branch restriction, silently. For anyone relying on main-only environments to gate cloud credentials (eg. an OIDC subject of repo:<org>/<repo>:environment:<name>), that's a security issue rather than a broken deploy.

2. structuredClone doesn't cover YAML anchors. A common pattern we do use is to share one policy between environments:

environments:
  - name: a
    deployment_branch_policy: &main_only
      protected_branches: false
      custom_branch_policies:
        - type: branch
          names: ["main"]
  - name: b
    deployment_branch_policy: *main_only
  - name: c
    deployment_branch_policy: *main_only

js-yaml gives a, b and c the same object, and structuredClone preserves that aliasing. The constructor normalises it once for a; b then sees {name, type} without names and drops it, and all three end up with [] in a single repo:

constructor repo 1 repo 2
3.0.0 a=[] b=[] c=[] a=[] b=[] c=[]
this PR a=[] b=[] c=[] a=[] b=[] c=[]
idempotent normalisation (below) a=main b=main c=main a=main b=main c=main

Making the normalisation idempotent fixes both the cross-repo case this PR targets and the anchor case. That means accepting an entry that's already {name, type} instead of dropping it:

} else if (typeof policy === 'object' && typeof policy.name === 'string' && !policy.names) {
  policies.push({ name: policy.name, type: policy.type || 'branch' })
} else if (typeof policy === 'object' && Array.isArray(policy.names)) {

Keeping the structuredClone as well is still worthwhile, so the plugin stops mutating its caller's config.

3. Where to look in the wild. We ran 3.0.0's own childPluginsList merge plus this constructor over our real config: one suborg declaring 10 main-only environments, 18 repos, 4 of which restate the environments in their own repo file. 3.0.0 left 140 of 180 environments with an empty allowlist. This PR alone left 140 (the suborg used an anchor). Removing the anchor alone left 130. Both together left 0. Repos whose own file restated the policy mostly came out fine, which made the bug look intermittent: live, it flapped as update() deleted every policy and re-created them on the next sync.

Happy to open a follow-up PR with the idempotent version and a test for the anchor case if that's useful.

This branch has not been deployed

No deployments
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.

Environment creation doesn't set custom branch policies

3 participants