This document covers operational workflows for managing the zero-dot-force GitHub organization using two complementary tools: peribolos and safe-settings.
| Area | Tool | Config Location |
|---|---|---|
| Org membership (admins, members) | peribolos | org/config.yaml |
| Team creation, membership, privacy | peribolos | org/config.yaml |
| Team-to-repo permission mappings | peribolos | org/config.yaml |
| Repo description | peribolos | org/config.yaml |
| Repo has_projects | peribolos | org/config.yaml |
| Repo default_branch | peribolos | org/config.yaml |
| Repo merge strategies | safe-settings | safe-settings/settings.yml |
| Repo auto-merge, delete-branch | safe-settings | safe-settings/settings.yml |
| Repo has_wiki | safe-settings | safe-settings/settings.yml |
| Dependabot alerts and fixes | safe-settings | safe-settings/settings.yml |
| Branch protection rules | safe-settings | safe-settings/settings.yml |
| Rulesets | safe-settings | safe-settings/settings.yml |
.github repo ruleset |
manual | GitHub UI |
Why two tools? Peribolos manages org-level concerns (who is a member, what teams exist, what permissions teams have). Safe-settings manages repo-level concerns (how branches are protected, what merge strategies are allowed, what security features are enabled). This separation follows the principle of least privilege for their respective GitHub App permissions.
Boundary enforcement: Go tests in config/boundary_test.go validate
that neither tool manages fields owned by the other. These tests run on
every PR via CI.
- Edit
org/config.yaml-- add/remove the username from theadminsormemberslist (keep sorted alphabetically). - If adding, add to the appropriate team(s) as well.
- Submit a PR. CI validates the config automatically.
- After merge, peribolos applies the change (push-triggered or daily at 05:30 UTC).
- Edit
org/config.yaml-- add/modify the team under theteamssection. - Ensure team members are org members (CI validates this).
- Ensure admins are listed as
maintainers, notmembers(CI validates). - Submit a PR and merge.
- Add the repo to
org/config.yamlunder the top-levelrepossection withdescription,has_projects, anddefault_branch(peribolos-owned fields). - Add the repo to the appropriate team
reposmapping. - Add the repo to the appropriate suborg file:
safe-settings/suborgs/code-repos.ymlfor code repositoriessafe-settings/suborgs/non-code-repos.ymlfor non-code repositories
- Add the repo to the matching ruleset
repository_name.includelist insafe-settings/settings.yml. Both files must be updated -- the suborg controls settings inheritance, the ruleset controls branch protection. - Submit a PR. CI boundary tests validate consistency.
- After merge, peribolos creates the repo and sets team permissions.
- Trigger
workflow_dispatchon the "Safe Settings Sync" workflow to apply repo settings.
- Edit
safe-settings/settings.yml-- modify the ruleset underrulesets. - The
safe-settings: code reposruleset applies to code repos. - Submit a PR and merge.
- Trigger
workflow_dispatchto apply.
Use repo overrides sparingly. Only create one when a repo needs settings that differ from its suborg defaults.
- Create
safe-settings/repos/<repo-name>.yml. - Set only the fields that differ from the suborg/org defaults.
- Do NOT set peribolos-owned fields (
description,has_projects,default_branch). - Submit a PR. CI boundary tests validate the override.
Override validators in safe-settings/deployment-settings.yml enforce
a security floor:
- Approver count floor: Suborg or repo configs cannot lower
required_approving_review_countbelow the org default. Setting it higher is allowed. - No admin collaborators: The
adminpermission cannot be granted to collaborators via safe-settings. Use peribolos team membership with admin role instead.
Requesting an exception: If a legitimate use case requires bypassing
a validator, discuss with org admins. Exceptions require modifying the
validator script in deployment-settings.yml via a reviewed PR.
- Go (version in
go.mod) yamllint(for YAML validation)
# Validate all YAML (peribolos + safe-settings)
make lint
# Run all Go tests (peribolos + boundary)
make test-unit
# Validate only safe-settings YAML
make safe-settings-validate
# Full validation: format, vet, lint, tests, diff check
make sanitysafe-settings reads its config from the .github repo's default branch
via the GitHub API. Config changes must be merged to main before
safe-settings can apply them.
-
Local validation (before PR):
make test-unit # boundary tests make safe-settings-validate # YAML syntax
-
Submit PR -- CI runs boundary tests and YAML validation.
-
Merge PR -- config lands on main.
-
Dry-run against a single repo -- go to Actions > "Safe Settings Sync" > "Run workflow":
- Set
dry-runtotrue - Set
reposto a single repo (e.g.,snake-eyes) - Review the workflow output to see what would change
- Set
-
Apply to a single repo -- same workflow:
- Set
dry-runtofalse - Set
reposto the same repo
- Set
-
Apply to all repos -- same workflow:
- Set
dry-runtofalse - Leave
reposempty (applies to all managed repos)
- Set
If safe-settings applies incorrect settings:
git revertthe config change and push to main- Trigger
workflow_dispatchwithdry-run=false-- safe-settings reverts to the previous config state - Or fix settings manually via the GitHub UI (safe-settings will re-apply them on the next sync)
Go to Actions > "Peribolos: Apply" > "Run workflow". Set dry-run to
true for a preview, or false to apply.
Go to Actions > "Safe Settings Sync" > "Run workflow":
- dry-run:
trueto preview,falseto apply (defaults totrue) - repos: comma-separated list of repos to target (e.g.,
snake-eyes,reading-stone). Leave empty to apply to all managed repos.
After initial validation, the workflow can be extended with:
pushtrigger onsafe-settings/**path changes to mainscheduletrigger (daily at 06:00 UTC) for drift correction
These triggers are intentionally disabled during the initial rollout to ensure full manual control.
- Trigger
workflow_dispatchmanually -- safe-settings only runs on manual dispatch during initial rollout (no push/schedule triggers). - Check the "Safe Settings Sync" workflow run in the Actions tab.
- Look for errors in the workflow logs (credential expiry, API errors).
Boundary tests fail when:
- A repo in a suborg file does not exist in
org/config.yaml-- add it to peribolos first. - A repo appears in multiple suborg files -- each repo belongs to exactly one suborg.
- A safe-settings config sets
description,has_projects, ordefault_branch-- these are peribolos-owned fields. - A suborg repo list does not match the corresponding ruleset
repository_name.include-- update both files together.
Common causes:
- Credential expiry: The GitHub App private key may need rotation.
Update the
SAFE_SETTINGS_PRIVATE_KEYsecret. - API rate limits: The sync may fail if it hits GitHub API rate limits. Wait and re-trigger.
- Invalid YAML: The workflow validates YAML before applying. Check the yamllint output in the workflow logs.
- safe-settings version issue: If safe-settings behavior changes, check the pinned version in the workflow file.
The workflow includes a patched full-sync.js to work around a bug
in safe-settings where handleResults crashes in full-sync mode
because payload.check_suite is undefined outside the webhook flow
(the sync itself completes; only the Check Run reporting fails).
- Upstream issue: github-community-projects/safe-settings#818
- Upstream fix PR: github-community-projects/safe-settings#1018
- Search tag:
TODO(safe-settings-818)in the workflow file
Once upstream PR #1018 is merged and released, update the pinned
version and revert the patched script back to npm run full-sync.
The following repos are excluded from safe-settings management:
.github-- the admin repo (avoids circular dependency). Its ruleset is managed manually via the GitHub UI.
These are listed in safe-settings/deployment-settings.yml under
restrictedRepos and/or excluded from suborg files.