How Safe Settings works
Safe Settings is a GitHub App built on the Probot framework that enforces repository settings as code. This guide explains the internal architecture, webhook processing, and configuration management.Architecture overview
Safe Settings operates as a webhook-driven application with optional scheduled execution:Core components
1. Webhook listener (index.js)
The main application file (index.js) registers webhook event handlers:
2. Settings manager (lib/settings.js)
TheSettings class orchestrates configuration loading, merging, and application:
3. Plugin system
Each setting type is managed by a dedicated plugin:- Fetches current GitHub settings
- Compares with desired configuration
- Calculates additions, modifications, and deletions
- Applies changes via GitHub API
- Returns results for reporting
Webhook events
Safe Settings responds to the following webhook events:Configuration change events
push - Configuration updates
push - Configuration updates
When: Push to default branch of admin repositoryBehavior:
- If
.github/settings.ymlmodified → sync all repositories - If
.github/repos/*.ymlmodified → sync specific repositories - If
.github/suborgs/*.ymlmodified → sync suborg repositories - Ignores non-default branches (validated in dry-run mode)
repository.created - New repository
repository.created - New repository
When: New repository created in the organizationBehavior:
- Loads org-level settings
- Checks for suborg membership (by name pattern, team, or properties)
- Checks for repo-specific config file
- Applies merged configuration
Drift prevention events
branch_protection_rule - Protection modified
branch_protection_rule - Protection modified
When: Branch protection rule modified or deleted via UIBehavior:
- Ignores changes made by bots (including Safe Settings)
- Reverts changes made by humans
- Re-applies settings from admin repository
repository.edited - Repository settings changed
repository.edited - Repository settings changed
When: Repository settings modified (description, topics, features)Behavior:
- Ignores bot changes
- Syncs settings for human changes
- Handles default branch renames
repository.renamed - Repository renamed
repository.renamed - Repository renamed
When: Repository name changedBehavior (if
BLOCK_REPO_RENAME_BY_HUMAN=true):- If renamed by human → reverts to original name
- If renamed by bot → copies config file to new name
- Safe Settings can rename repos by renaming the
.ymlconfig file
repository_ruleset - Rulesets modified
repository_ruleset - Rulesets modified
When: Repository or organization ruleset modifiedBehavior:
- Org-level rulesets → syncs all repositories
- Repo-level rulesets → syncs specific repository
- Ignores bot changes
member_change_events - Access modified
member_change_events - Access modified
When: Member, team, or collaborator access changedEvents:
member, team.added_to_repository, team.removed_from_repository, team.editedBehavior:- Ignores bot changes
- Syncs teams and collaborators for human changes
custom_property_values - Properties updated
custom_property_values - Properties updated
When: Custom property values set on repositoryBehavior:
- Checks if property defines suborg membership
- Applies appropriate suborg configuration
team: frontend property applies frontend-team suborg configPull request validation events
pull_request.opened/reopened - PR created
pull_request.opened/reopened - PR created
When: Pull request opened or reopened in admin repositoryBehavior:
- Runs Safe Settings in nop (no-operation) mode
- Validates configuration changes
- Runs custom validators
- Creates check run with dry-run results
- Posts comment with change summary (if
ENABLE_PR_COMMENT=true)
check_run.created - Validation triggered
check_run.created - Validation triggered
When: Check run created for pull requestBehavior:
- Identifies changed configuration files
- Runs validation in dry-run mode
- Updates check run with results
- Shows errors without applying changes
Configuration hierarchy
Safe Settings uses a three-tier hierarchy with intelligent merging:Configuration resolution
When applying settings to a repository:-
Load organization config (
.github/settings.yml) -
Check suborg membership
Repositories can belong to suborgs via:
Name patterns:
Team membership:Custom properties:
-
Merge suborg config (if repository matches)
-
Merge repo-specific config (if exists)
Deep merging: Safe Settings performs deep merging of objects and arrays. More specific configurations override broader ones at every level of nesting.
Suborg configuration methods
- Name Patterns
- Team Membership
- Custom Properties
Match repositories by name using glob patterns:Use case: Organizing repositories by functional area or team
Request flow details
Normal operation (push to default branch)
- Webhook received: GitHub sends
pushevent - Event validation: Check if admin repo and default branch
- File detection: Identify which config files changed
- Scope determination:
.github/settings.ymlchanged → sync all repos.github/suborgs/*.ymlchanged → sync suborg repos.github/repos/*.ymlchanged → sync specific repos
- Configuration loading: Fetch relevant config files
- Repository iteration: Process each affected repository
- Configuration merging: Org → Suborg → Repo
- Change detection: Compare with current GitHub settings
- API calls: Apply only actual changes
- Check run creation: Report success/failure
Dry-run mode (pull request)
- Webhook received: GitHub sends
pull_request.openedevent - Check run creation: Safe Settings creates pending check
- File detection: Identify changed config files in PR
- Configuration loading: Load configs from PR branch
- Validation:
- YAML syntax validation
- Custom
configvalidatorsexecution - Custom
overridevalidatorsexecution
- Simulation: Process settings without applying
- Change calculation: Determine what would change
- Check run update: Report validation results
- PR comment: Post summary of changes (optional)
Performance optimizations
When managing thousands of repositories, Safe Settings implements several optimizations:1. Selective file loading
2. Change detection
Before making API calls, Safe Settings compares desired vs. current state:3. Rate limit handling
Probot automatically handles GitHub rate limits:- Respects
X-RateLimit-Remainingheaders - Implements exponential backoff
- Queues requests when limits approached
4. Caching
Safe Settings caches configuration files with ETags:Custom validators
Safe Settings supports two types of custom validators:Config validators
Validate settings independently:baseconfig: The setting being appliedgithubContext: Octokit instance for API calls
Override validators
Validate overrides against base configuration:baseconfig: Base (org or suborg) configurationoverrideconfig: Override (suborg or repo) configurationgithubContext: Octokit instance for API calls
Validators run during dry-run mode and can prevent invalid configurations from being merged.
Scheduled sync (drift prevention)
Configure periodic reconciliation to prevent drift:- Fetches all configuration files
- Syncs all repositories
- Reverts any manual changes
- Creates check run with results
Scheduled sync runs even if no webhooks were triggered, protecting against webhook delivery failures.
Restricted repositories
Control which repositories Safe Settings manages:excludeonly → Manage all except excludedincludeonly → Manage only included- Both → Manage included except excluded
- Neither → Exclude
admin,.github,safe-settingsby default
Next steps
Configuration Reference
Explore all available settings and configuration options
Deployment Guide
Learn about different deployment options and best practices