-
Notifications
You must be signed in to change notification settings - Fork 224
Hi!
We're running safe-settings via npm run full-sync in a GitHub Actions workflow (version 2.1.18). During a full sync with multiple repositories, every PATCH request to the GitHub API ends up with the same "name" field — one specific repo's name applied to all — causing 422 "Validation Failed: name already exists on this account" errors across the board.
For context, in our case the name being sent for every repo was kbrgnhwcx8, which happened to be an existing repository in our org. We're not sure whether this is coincidental or relevant to the error.
We might be missing something obvious in how safe-settings is meant to be configured, so wanted to ask here before assuming it's a bug.
What we observed in lib/settings.js, updateRepos():
repoConfig = Object.assign(repoConfig, { name: repo.repo, org: repo.owner })
Our reading is that since repoConfig references the shared this.config.repository object, and Promise.all runs all repos concurrently (all synchronous setup runs before any await), the last repo to execute Object.assign overwrites name on the shared object — so all repos end up PATCHing with that last repo's name. But we could easily be misreading the code (not my main language).
We worked around it by adding a catch-all suborg config (suborgs/all.yml with suborgrepos: ["*"] and repository: {}), which triggers the mergeDeep({}, repoConfig, suborg) path and creates a fresh object per repo. That seems to fix it, but it feels like a workaround rather than an intended usage pattern.
Are we setting something up incorrectly, or is this a known limitation? Happy to provide more details if helpful.
Also, the version in package.json does not match the released version so the log output is incorrect. See: https://github.com/github/safe-settings/blob/main-enterprise/package.json#L3
safe-settings.yml.txt
all.yml.txt
deployment-settings.yml.txt
settings.yml.txt
All reactions
Replies: 3 comments
Hey! Quick sanity checks that usually cause this:\n\n1) In each repo’s settings file, confirm name is the actual repo name (not the org) and it matches the repo slug exactly.\n2) Make sure you don’t have a shared defaults or template file that sets name: and gets merged into every repo config.\n3) If you’re using include/inherit, verify the merge order so repo‐specific name isn’t being overwritten by a later include.\n4) If you’re generating the YAML, log the final per‐repo object right before sync to see where name flips.\n\nIf it helps, paste one repo entry (redacted) and the resulting "final" config you see at runtime — happy to take a look.
All reactions
Hey! Quick sanity checks that usually cause this:
- In each repo settings file, confirm
nameis the actual repo name (not the org) and it matches the repo slug exactly. - Make sure you do not have a shared
defaultsortemplatefile that setsname:and gets merged into every repo config. - If you are using
include/inherit, verify the merge order so repo-specificnameis not overwritten by a later include. - If you are generating the YAML, log the final per-repo object right before sync to see where
nameflips.
If it helps, paste one repo entry (redacted) and the resulting "final" config you see at runtime — happy to take a look.
All reactions
Thanks for the suggestions! We went through each of them:
- No per-repo settings files in our setup — just a global
settings.yml, so no per-reponame:to check. settings.ymldoes not setname:at all — safe-settings injects it in code, so no shared template to blame.- Not using
include/inherit. - We did not log the final per-repo object, but we did read through the source code instead.
Since none of the checks pointed anywhere, we dug into lib/settings.js and found what we think is the cause — though we could be misreading it. Our best guess: Object.assign(repoConfig, { name: repo.repo, org: repo.owner }) mutates the shared this.config.repository object. Since all the synchronous setup in Promise.all runs before any await, the last repo's name overwrites the shared object and ends up in every PATCH request.
The workaround that fixed it for us was adding a catch-all suborg config (suborgs/all.yml):
suborgrepos: - "*" # Workaround for safe-settings shared-object mutation bug: # Without a suborg entry, all repos share the same repoConfig reference via # Object.assign() mutation. Adding a suborg causes mergeDeep({}, ...) which # creates a fresh object per repo, giving each repo its correct name. repository: {}
When a suborg entry is present, safe-settings takes the mergeDeep({}, repoConfig, suborg) path which creates a fresh object per repo rather than mutating the shared one. That's been running reliably across ~17 repos for us.
Happy to be corrected if we've misdiagnosed it!