This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Policy-as-code for the IntegratedDynamic GitHub org. All repo settings (branch protection,
labels, merge strategies, team permissions) are declared here and enforced by a GitHub Actions
workflow that runs safe-settings on every push to main.
One invariant: never touch GitHub settings directly in the UI — this repo always wins on the next sync.
# Format all files (requires mise + npm install first)
mise run fmt
# Check formatting without writing
mise run fmt:check
# Bootstrap after clone
mise install && npm install# 1. Branch off main
git checkout -b my-change
# 2. Edit config (see structure below)
# 3. Dry-run from your branch — mandatory before merge
gh workflow run safe-settings-sync.yml --repo IntegratedDynamic/admin --ref my-change -f nop=true
# 4. Check the dry-run output
gh run list --repo IntegratedDynamic/admin --limit 3
gh run view <run-id> --repo IntegratedDynamic/admin --log | grep "There are changes"
# 5. Open PR, merge — real sync fires on push to main.github/settings.yml # org-wide defaults (ALL repos)
.github/suborgs/*.yml # name-pattern groups (merged on top of settings.yml)
.github/repos/<name>.yml # single-repo overrides (highest precedence)
deployment-settings.yml # safe-settings app config (not repo settings)
Precedence (highest wins): repos/<name>.yml > suborgs/*.yml > settings.yml
Only declare what changes at each level — everything else is inherited via deep merge.
| File | Matches |
|---|---|
backend.yml |
*-api, *-service, *-worker, *-backend, *-server |
frontend.yml |
*-app, *-web, *-ui, *-frontend, *-mobile, *-extension |
infrastructure.yml |
infrastructure, gitops, terraform-*, *-infra, platform-* |
Controls the safe-settings process (not individual repos):
restrictedRepos.exclude— repos safe-settings will never touch (currently:.githubonly —adminis managed like any other repo)configvalidators— validate a single setting value (e.g. block admin collaborator permission)overridevalidators— validate when a suborg/repo overrides an org setting (e.g. block loweringrequired_approving_review_countbelow org baseline)
These are already worked around in this repo — do not undo them:
-
Suborgs directory early-exit — never add subdirectories inside
.github/suborgs/. A dir causes all alphabetically-later.ymlfiles to be silently skipped (returninstead ofcontinuebug ingetSubOrgConfigs()). Example/template files live indocs/suborg-examples/instead. -
contexts: []not a placeholder string —contexts: ["placeholder"]with a non-existent check name crashes NOP mode. Always usecontexts: []. -
bypass_pull_request_allowancesonly insettings.yml— safe-settings deep-merges arrays (concatenates, not replaces). If set in bothsettings.ymland a suborg file,nbrieusselends up listed twice and the API rejects it. Set bypass only insettings.yml. -
probot v14 full-sync break — PR #949 claimed to fix this but 2.1.19 still crashes with
TypeError: Cannot read properties of null (reading 'info') at performFullSync. The octokit.rest.*calls were fixed butcreateProbot()still initializes with a null logger. Stay on2.1.17until a release actually boots cleanly in NOP mode.
| # | Priority | Title |
|---|---|---|
| #3 | P1 | Add permissions: contents: read to sync workflow |
| #4 | P2 | Harden sync workflow (npm ci, ubuntu pin, timeout) |
| #5 | P2 | Upgrade safe-settings 2.1.17 → 2.1.19 |
| #6 | P2 | Add Renovate for automated dependency updates |
| #7 | P2 | Establish GitHub App private key rotation policy |
| #8 | P3 | SHA-pin the safe-settings checkout ref |
| #9 | P3 | Add PR template with dry-run checklist |
Safe-settings merges configs recursively. Arrays are concatenated, not replaced. This means:
- Adding a label at suborg level adds it on top of org labels (good)
- Adding
bypass_pull_request_allowancesat suborg level duplicates users already insettings.yml(bad — see bug #3 above) - Adding
teams:at suborg level is additive (fine, but teams must exist in the org first)
The GitHub Actions workflow (.github/workflows/safe-settings-sync.yml) checks out the official
github/safe-settings public repo at a pinned version tag, then runs npm run full-sync. It uses:
APP_ID+PRIVATE_KEY+GITHUB_CLIENT_ID/SECRET+WEBHOOK_SECRET— GitHub App credentials stored as repo secrets/variablesDEPLOYMENT_CONFIG_FILEpointing todeployment-settings.ymlat repo rootFULL_SYNC_NOP=truein dry-run mode (enablesLOG_LEVEL=debugautomatically)
The app code is not forked — it is fetched fresh from github/safe-settings on every run.