Skip to content
Open
2 changes: 1 addition & 1 deletion app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ default_permissions:
organization_administration: write

# Manage Actions variables.
# https://docs.github.com/en/rest/actions/variables?apiVersion=2022-11-28
# https://docs.github.com/en/rest/actions/variables?apiVersion=2026-03-10
actions_variables: write


Expand Down
4 changes: 2 additions & 2 deletions docs/github-settings/1. repository-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ repository:

>[!TIP]
>GitHub's API documentation defines these inputs and types:
>1. [Update an environment](https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#update-a-repository)
>2. [Replace all repository topics](https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#replace-all-repository-topics)
>1. [Update an environment](https://docs.github.com/en/rest/repos/repos?apiVersion=2026-03-10#update-a-repository)
>2. [Replace all repository topics](https://docs.github.com/en/rest/repos/repos?apiVersion=2026-03-10#replace-all-repository-topics)

<table>
<tr><td>
Expand Down
2 changes: 1 addition & 1 deletion docs/github-settings/2. repository-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ variables:

>[!TIP]
>GitHub's API documentation defines these inputs and types:
>1. [Update a repository variable](https://docs.github.com/en/rest/actions/variables?apiVersion=2022-11-28#update-a-repository-variable)
>1. [Update a repository variable](https://docs.github.com/en/rest/actions/variables?apiVersion=2026-03-10#update-a-repository-variable)

<table>
<tr><td>
Expand Down
4 changes: 2 additions & 2 deletions docs/github-settings/3. collaborators.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ collaborators:

>[!TIP]
>GitHub's API documentation defines these inputs and types:
>1. [Add a repository collaborator](https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#add-a-repository-collaborator)
>2. [Remove a repository collaborator](https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#remove-a-repository-collaborator)
>1. [Add a repository collaborator](https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2026-03-10#add-a-repository-collaborator)
>2. [Remove a repository collaborator](https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2026-03-10#remove-a-repository-collaborator)

<table>
<tr><td>
Expand Down
2 changes: 1 addition & 1 deletion docs/github-settings/4. teams.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ teams:

>[!TIP]
>GitHub's API documentation defines these inputs and types:
>1. [Add or update team repository permissions](https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#add-or-update-team-repository-permissions)
>1. [Add or update team repository permissions](https://docs.github.com/en/rest/teams/teams?apiVersion=2026-03-10#add-or-update-team-repository-permissions)

<table>
<tr><td>
Expand Down
2 changes: 1 addition & 1 deletion docs/github-settings/5. branch-protection.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ branches:

>[!TIP]
>GitHub's API documentation defines these inputs and types:
>1. [Update a repository variable](https://docs.github.com/en/rest/actions/variables?apiVersion=2022-11-28#update-a-repository-variable)
>1. [Update a repository variable](https://docs.github.com/en/rest/actions/variables?apiVersion=2026-03-10#update-a-repository-variable)
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

The Branch Protection docs TIP links to the Actions Variables endpoint ("Update a repository variable") instead of the Branch Protection endpoint. Update the link text + URL to point at the branch protection REST API docs for updating branch protection.

Suggested change
>1. [Update a repository variable](https://docs.github.com/en/rest/actions/variables?apiVersion=2026-03-10#update-a-repository-variable)
>1. [Update branch protection](https://docs.github.com/en/rest/branches/branch-protection?apiVersion=2026-03-10#update-branch-protection)

Copilot uses AI. Check for mistakes.

<table>
<tr><td>
Expand Down
6 changes: 3 additions & 3 deletions docs/github-settings/6. deployment-environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ environments:

>[!TIP]
>GitHub's API documentation defines these inputs and types:
>1. [Create or update an environment](https://docs.github.com/en/rest/deployments/environments?apiVersion=2022-11-28#create-or-update-an-environment)
>2. [Create a deployment branch policy](https://docs.github.com/en/rest/deployments/branch-policies?apiVersion=2022-11-28#create-a-deployment-branch-policy)
>3. [Create an environment variable](https://docs.github.com/en/rest/actions/variables?apiVersion=2022-11-28#create-an-environment-variable)
>1. [Create or update an environment](https://docs.github.com/en/rest/deployments/environments?apiVersion=2026-03-10#create-or-update-an-environment)
>2. [Create a deployment branch policy](https://docs.github.com/en/rest/deployments/branch-policies?apiVersion=2026-03-10#create-a-deployment-branch-policy)
>3. [Create an environment variable](https://docs.github.com/en/rest/actions/variables?apiVersion=2026-03-10#create-an-environment-variable)

<table>
<tr><td>
Expand Down
2 changes: 1 addition & 1 deletion docs/github-settings/7. autolinks.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ variables:

>[!TIP]
>GitHub's API documentation defines these inputs and types:
>1. [Create an autolink reference for a repository](https://docs.github.com/en/rest/repos/autolinks?apiVersion=2022-11-28#create-an-autolink-reference-for-a-repository)
>1. [Create an autolink reference for a repository](https://docs.github.com/en/rest/repos/autolinks?apiVersion=2026-03-10#create-an-autolink-reference-for-a-repository)

<table>
<tr><td>
Expand Down
6 changes: 3 additions & 3 deletions docs/github-settings/8. labels.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ labels:

>[!TIP]
>GitHub's API documentation defines these inputs and types:
>1. [Create a label](https://docs.github.com/en/rest/issues/labels?apiVersion=2022-11-28#create-a-label)
>2. [Update a label](https://docs.github.com/en/rest/issues/labels?apiVersion=2022-11-28#update-a-label)
>3. [Delete a label](https://docs.github.com/en/rest/issues/labels?apiVersion=2022-11-28#delete-a-label)
>1. [Create a label](https://docs.github.com/en/rest/issues/labels?apiVersion=2026-03-10#create-a-label)
>2. [Update a label](https://docs.github.com/en/rest/issues/labels?apiVersion=2026-03-10#update-a-label)
>3. [Delete a label](https://docs.github.com/en/rest/issues/labels?apiVersion=2026-03-10#delete-a-label)

<table>
<tr><td>
Expand Down
8 changes: 4 additions & 4 deletions docs/sample-settings/sample-deployment-settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ overridevalidators:
error: |
`Branch protection required_approving_review_count cannot be overidden to a lower value`
script: |
console.log(`baseConfig ${JSON.stringify(baseconfig)}`)
console.log(`overrideConfig ${JSON.stringify(overrideconfig)}`)
if (baseconfig.protection.required_pull_request_reviews.required_approving_review_count && overrideconfig.protection.required_pull_request_reviews.required_approving_review_count ) {
return overrideconfig.protection.required_pull_request_reviews.required_approving_review_count >= baseconfig.protection.required_pull_request_reviews.required_approving_review_count
const baseCount = baseconfig?.protection?.required_pull_request_reviews?.required_approving_review_count
const overrideCount = overrideconfig?.protection?.required_pull_request_reviews?.required_approving_review_count
if (baseCount && overrideCount) {
return overrideCount >= baseCount
}
return true
- plugin: labels
Expand Down
12 changes: 6 additions & 6 deletions docs/sample-settings/settings.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This settings file can be used to create org-level settings

# This is the settings that need to be applied to all repositories in the org
# See https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#create-an-organization-repository for all available settings for a repository
# See https://docs.github.com/en/rest/repos/repos?apiVersion=2026-03-10#create-an-organization-repository for all available settings for a repository
repository:
# A short description of the repository that will show up on GitHub
description: description of the repo
Expand Down Expand Up @@ -123,7 +123,7 @@ milestones:
state: open

# Collaborators: give specific users access to any repository.
# See https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#add-a-repository-collaborator for available options
# See https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2026-03-10#add-a-repository-collaborator for available options
collaborators:
- username: regpaco
# The permission to grant the collaborator. Can be one of:
Expand All @@ -144,7 +144,7 @@ collaborators:
- another-repo

# Teams
# See https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#create-a-team for available options
# See https://docs.github.com/en/rest/teams/teams?apiVersion=2026-03-10#create-a-team for available options
teams:
- name: core
# The permission to grant the team. Can be one of:
Expand All @@ -163,7 +163,7 @@ teams:
visibility: closed

# Branch protection rules
# See https://docs.github.com/en/rest/branches/branch-protection?apiVersion=2022-11-28#update-branch-protection for available options
# See https://docs.github.com/en/rest/branches/branch-protection?apiVersion=2026-03-10#update-branch-protection for available options
branches:
# If the name of the branch value is specified as `default`, then the app will create a branch protection rule to apply against the default branch in the repo
- name: default
Expand Down Expand Up @@ -202,7 +202,7 @@ branches:
teams: []

# Custom properties
# See https://docs.github.com/en/rest/repos/custom-properties?apiVersion=2022-11-28
# See https://docs.github.com/en/rest/repos/custom-properties?apiVersion=2026-03-10
custom_properties:
- name: test
value: test
Expand All @@ -221,7 +221,7 @@ validator:
pattern: "[a-zA-Z0-9_-]+"

# Rulesets
# See https://docs.github.com/en/rest/orgs/rules?apiVersion=2022-11-28#create-an-organization-repository-rulesetfor available options
# See https://docs.github.com/en/rest/orgs/rules?apiVersion=2026-03-10#create-an-organization-repository-rulesetfor available options
rulesets:
- name: Template
# The target of the ruleset. Can be one of:
Expand Down
8 changes: 4 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
repo: env.ADMIN_REPO,
path: oldPath,
headers: {
'X-GitHub-Api-Version': '2022-11-28'
'X-GitHub-Api-Version': '2026-03-10'
}
})
let content = Buffer.from(repofile.data.content, 'base64').toString()
Expand All @@ -418,10 +418,10 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
// Check if a config file already exists for the renamed repo name
await context.octokit.request('GET /repos/{owner}/{repo}/contents/{path}', {
owner: payload.repository.owner.login,
repo: env.ADMIN_REPO,
repo: env.ADMIN_REPO,
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

Trailing whitespace after env.ADMIN_REPO, will fail standard/eslint formatting checks. Remove the extra space.

Suggested change
repo: env.ADMIN_REPO,
repo: env.ADMIN_REPO,

Copilot uses AI. Check for mistakes.
path: newPath,
headers: {
'X-GitHub-Api-Version': '2022-11-28'
'X-GitHub-Api-Version': '2026-03-10'
}
})
} catch (error) {
Expand All @@ -436,7 +436,7 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) =>
message: `Repo Renamed and safe-settings renamed the file from ${payload.changes.repository.name.from} to ${payload.repository.name}`,
sha: repofile.data.sha,
headers: {
'X-GitHub-Api-Version': '2022-11-28'
'X-GitHub-Api-Version': '2026-03-10'
}
})
robot.log.debug(`Created a new setting file ${newPath}`)
Expand Down
2 changes: 1 addition & 1 deletion lib/plugins/autolinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = class Autolinks extends Diffable {
changed (existing, attr) {
// is_alphanumeric was added mid-2023. In order to continue to support settings yamls which dont specify this
// attribute, consider an unset is_alphanumeric as `true` (since that is the default value in the API)
// https://docs.github.com/en/rest/repos/autolinks?apiVersion=2022-11-28#create-an-autolink-reference-for-a-repository
// https://docs.github.com/en/rest/repos/autolinks?apiVersion=2026-03-10#create-an-autolink-reference-for-a-repository
const isAlphaNumericMatch = attr.is_alphanumeric === undefined
? existing.is_alphanumeric // === true, the default
: attr.is_alphanumeric === existing.is_alphanumeric
Expand Down
45 changes: 38 additions & 7 deletions lib/plugins/branches.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ const Overrides = require('./overrides')
const ignorableFields = []
const previewHeaders = { accept: 'application/vnd.github.hellcat-preview+json,application/vnd.github.luke-cage-preview+json,application/vnd.github.zzzax-preview+json' }
const overrides = {
'contexts': {
'action': 'reset',
'type': 'array'
},
contexts: {
action: 'reset',
type: 'array'
}
}

// GitHub API requires these fields to be present in updateBranchProtection calls
// See: https://docs.github.com/rest/branches/branch-protection#update-branch-protection
const requiredBranchProtectionDefaults = {
required_status_checks: null,
enforce_admins: null,
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

updateBranchProtection requires required_pull_request_reviews to be present in the payload (it can be null), but requiredBranchProtectionDefaults only sets required_status_checks, enforce_admins, and restrictions. When both the config and the existing GitHub protection omit required_pull_request_reviews, the request will be sent without it and can fail validation. Add required_pull_request_reviews: null to requiredBranchProtectionDefaults (and ensure it is included in the 404 branch as well).

Suggested change
enforce_admins: null,
enforce_admins: null,
required_pull_request_reviews: null,

Copilot uses AI. Check for mistakes.
restrictions: null
}

module.exports = class Branches extends ErrorStash {
Expand Down Expand Up @@ -56,7 +64,7 @@ module.exports = class Branches extends ErrorStash {
const params = Object.assign({}, p)
return this.github.rest.repos.getBranchProtection(params).then((result) => {
const mergeDeep = new MergeDeep(this.log, this.github, ignorableFields)
const changes = mergeDeep.compareDeep({ branch: { protection: this.reformatAndReturnBranchProtection(result.data) } }, { branch: { protection: Overrides.removeOverrides(overrides, branch.protection, result.data) } })
const changes = mergeDeep.compareDeep({ branch: { protection: this.reformatAndReturnBranchProtection(structuredClone(result.data)) } }, { branch: { protection: Overrides.removeOverrides(overrides, branch.protection, result.data) } })
const results = { msg: `Followings changes will be applied to the branch protection for ${params.branch.name} branch`, additions: changes.additions, modifications: changes.modifications, deletions: changes.deletions }
this.log.debug(`Result of compareDeep = ${results}`)

Expand All @@ -73,7 +81,7 @@ module.exports = class Branches extends ErrorStash {
resArray.push(new NopCommand(this.constructor.name, this.repo, null, results))
}

Object.assign(params, branch.protection, { headers: previewHeaders })
Object.assign(params, requiredBranchProtectionDefaults, this.reformatAndReturnBranchProtection(structuredClone(result.data)), Overrides.removeOverrides(overrides, branch.protection, result.data), { headers: previewHeaders })

if (this.nop) {
resArray.push(new NopCommand(this.constructor.name, this.repo, this.github.rest.repos.updateBranchProtection.endpoint(params), 'Add Branch Protection'))
Expand All @@ -83,7 +91,7 @@ module.exports = class Branches extends ErrorStash {
return this.github.rest.repos.updateBranchProtection(params).then(res => this.log.debug(`Branch protection applied successfully ${JSON.stringify(res.url)}`)).catch(e => { this.logError(`Error applying branch protection ${JSON.stringify(e)}`); return [] })
}).catch((e) => {
if (e.status === 404) {
Object.assign(params, Overrides.removeOverrides(overrides, branch.protection, {}), { headers: previewHeaders })
Object.assign(params, requiredBranchProtectionDefaults, Overrides.removeOverrides(overrides, branch.protection, {}), { headers: previewHeaders })
if (this.nop) {
resArray.push(new NopCommand(this.constructor.name, this.repo, this.github.rest.repos.updateBranchProtection.endpoint(params), 'Add Branch Protection'))
return Promise.resolve(resArray)
Expand Down Expand Up @@ -123,6 +131,29 @@ module.exports = class Branches extends ErrorStash {
protection.required_linear_history = protection.required_linear_history && protection.required_linear_history.enabled
protection.enforce_admins = protection.enforce_admins && protection.enforce_admins.enabled
protection.required_signatures = protection.required_signatures && protection.required_signatures.enabled
protection.allow_force_pushes = protection.allow_force_pushes && protection.allow_force_pushes.enabled
protection.block_creations = protection.block_creations && protection.block_creations.enabled
protection.lock_branch = protection.lock_branch && protection.lock_branch.enabled
protection.allow_fork_syncing = protection.allow_fork_syncing && protection.allow_fork_syncing.enabled
if (protection.restrictions) {
delete protection.restrictions.url
protection.restrictions.users = Array.isArray(protection.restrictions.users)
? protection.restrictions.users.map(user => user.login || user)
: []
protection.restrictions.teams = Array.isArray(protection.restrictions.teams)
? protection.restrictions.teams.map(team => team.slug || team)
: []
protection.restrictions.apps = Array.isArray(protection.restrictions.apps)
? protection.restrictions.apps.map(app => app.slug || app)
: []
}
if (protection.required_status_checks) {
delete protection.required_status_checks.url
delete protection.required_status_checks.contexts_url
if (Array.isArray(protection.required_status_checks.contexts) && protection.required_status_checks.contexts.length === 0) {
delete protection.required_status_checks.contexts
}
Comment on lines +153 to +155
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

reformatAndReturnBranchProtection deletes required_status_checks.contexts when it is an empty array. The Branch Protection API schema typically requires contexts to be present whenever required_status_checks is non-null, and removing it can cause 422 errors when the repo uses status checks but has no contexts configured. Consider keeping contexts: [] (or only deleting it when checks is present and the API explicitly allows omitting contexts).

Suggested change
if (Array.isArray(protection.required_status_checks.contexts) && protection.required_status_checks.contexts.length === 0) {
delete protection.required_status_checks.contexts
}

Copilot uses AI. Check for mistakes.
}
if (protection.required_pull_request_reviews && !protection.required_pull_request_reviews.bypass_pull_request_allowances) {
protection.required_pull_request_reviews.bypass_pull_request_allowances = { apps: [], teams: [], users: [] }
}
Expand Down
2 changes: 1 addition & 1 deletion lib/plugins/collaborators.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = class Collaborators extends Diffable {
}

find () {
// https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28
// https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2026-03-10
// 'outside' means all outside collaborators of an organization-owned repository.
// 'direct' means all collaborators with permissions to an organization-owned repository, regardless of organization membership status. (includes outside collaborators)
// 'all' means all collaborators the authenticated user can see.
Expand Down
20 changes: 12 additions & 8 deletions lib/plugins/custom_properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ module.exports = class CustomProperties extends Diffable {

// Force all names to lowercase to avoid comparison issues.
normalizeEntries () {
this.entries = this.entries.map(({ name, value }) => ({
name: name.toLowerCase(),
value
}))
this.entries = this.entries
.filter(({ name }) => name != null)
.map(({ name, value }) => ({
name: name.toLowerCase(),
value
}))
Comment on lines 13 to +20
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

normalizeEntries() now silently drops custom property entries with name == null. This avoids a crash but can hide invalid config and make debugging harder. Consider logging/stashing a validation error when filtering out invalid entries so users know their input was ignored.

Copilot uses AI. Check for mistakes.
}

async find () {
Expand All @@ -38,10 +40,12 @@ module.exports = class CustomProperties extends Diffable {

// Force all names to lowercase to avoid comparison issues.
normalize (properties) {
return properties.map(({ property_name: propertyName, value }) => ({
name: propertyName.toLowerCase(),
value
}))
return properties
.filter(({ property_name: propertyName }) => propertyName != null)
.map(({ property_name: propertyName, value }) => ({
name: propertyName.toLowerCase(),
value
}))
Comment on lines 41 to +48
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

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

normalize() now silently drops properties with property_name == null. This prevents a crash but can hide malformed GitHub responses/config data. Consider emitting a debug/error log (or error stash entry) when such entries are encountered so the behavior is observable.

Copilot uses AI. Check for mistakes.
}

comparator (existing, attrs) {
Expand Down
4 changes: 2 additions & 2 deletions lib/plugins/rulesets.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ const overrides = {
'required_status_checks': {
'action': 'delete',
'parents': 3,
'type': 'dict'
'type': 'array'
},
}

const version = {
'X-GitHub-Api-Version': '2022-11-28'
'X-GitHub-Api-Version': '2026-03-10'
}
module.exports = class Rulesets extends Diffable {
constructor (nop, github, repo, entries, log, errors, scope) {
Expand Down
Loading