integrations / integrations/terraform-provider-github
[BUG]: github_organization_settings silently drifts repo-creation booleans due to partial PATCH × deprecated legacy field
- Dominant language
- Go
- Stars
- 1.2k
- Forks
- 1k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 8
Description
### Expected Behavior
When `terraform apply` updates any of the four repo-creation boolean fields on `github_organization_settings`:
- `members_can_create_repositories`
- `members_can_create_public_repositories`
- `members_can_create_private_repositories`
- `members_can_create_internal_repositories`
…the resulting server-side state should match the Terraform config exactly, regardless of which subset of those four actually changed in the diff.
### Actual Behavior
Provider silently leaves the org in a state that contradicts the Terraform config, with no warning at apply time. The next `terraform plan` may report no drift because state is refreshed from the (now-wrong) server values.
Real-world example (Enterprise Cloud org, 2026-05-14):
- Config: `create=true, public=false, private=true, internal=false`
- State before apply: `create=false, private=false` (drift from UI), `public=false, internal=false` (matched)
- `terraform apply` sent a PATCH containing only the two `HasChange` fields (`members_can_create_repositories=true`, `members_can_create_private_repositories=true`) per [resource_github_organization_settings.go L183](https://github.com/integrations/terraform-provider-github/blob/v6.11.1/github/resource_github_organization_settings.go#L183)
- Server response: `create=true, public=true, private=true, internal=true`, `members_allowed_repository_creation_type=all`
The GitHub Org API, when receiving a PATCH that sets `members_can_create_repositories=true` without also specifying the granular booleans, resets the deprecated `members_allowed_repository_creation_type` to `all`, which then silently overrides the unspecified granular booleans server-side. We had to manually reconcile via `gh api -X PATCH` after every apply that touches these fields.
### Terraform Version
```
Terraform v1.14.8
+ provider registry.terraform.io/integrations/github v6.11.1
```
### Affected Resource(s)
- `github_organization_settings`
### Steps to Reproduce
1. Real org with Enterprise plan and config:
```hcl
resource "github_organization_settings" "this" {
billing_email = "x@example.com"
members_can_create_repositories = true
members_can_create_public_repositories = false
members_can_create_private_repositories = true
members_can_create_internal_repositories = false
}
```
2. Have server state diverge such that *some but not all* of the four booleans match config (easiest: toggle "members can create repositories" off in the UI, leaving the others at their existing values).
3. `terraform apply`.
4. `gh api orgs/ --jq '{members_can_create_repositories, members_can_create_public_repositories, members_can_create_private_repositories, members_can_create_internal_repositories, members_allowed_repository_creation_type}'` — observe `members_allowed_repository_creation_type=all` and the unspecified booleans flipped to `true`.
### Root cause
`buildOrganizationSettings` determines what to PATCH via `d.HasChange()` per field, independently. This is correct in isolation but the four repo-creation booleans are mutually entangled at the API layer through the deprecated `members_allowed_repository_creation_type` field. Sending a partial update for the entangled set causes the API to recompute the legacy field from the partial input, which then resets the omitted fields server-side.
The go-github type explicitly documents this entanglement:
> Deprecated: Use MembersCanCreatePublicRepos, MembersCanCreatePrivateRepos, MembersCanCreateInternalRepos instead. The new fields overrides the existing MembersAllowedRepositoryCreationType during 'edit' operation and does not consider 'internal' repositories during 'get' operation
### Proposed Fix
Treat the four repo-creation booleans as an atomic group in `buildOrganizationSettings`: if any has changed, send all four (or three on non-Enterprise plans):
```go
anyRepoCreateChange :=
d.HasChange("members_can_create_repositories") ||
d.HasChange("members_can_create_public_repositories") ||
d.HasChange("members_can_create_private_repositories") ||
(isEnterprise && d.HasChange("members_can_create_internal_repositories"))
if anyRepoCreateChange {
settings.MembersCanCreateRepos = github.Ptr(d.Get("members_can_create_repositories").(bool))
settings.MembersCanCreatePublicRepos = github.Ptr(d.Get("members_can_create_public_repositories").(bool))
settings.MembersCanCreatePrivateRepos = github.Ptr(d.Get("members_can_create_private_repositories").(bool))
if isEnterprise {
settings.MembersCanCreateInternalRepos = github.Ptr(d.Get("members_can_create_internal_repositories").(bool))
}
}
```
This approach:
- Fixes the silent drift for all existing users without schema changes (no breaking change)
- Aligns with go-github's deprecation guidance (use the new fields, not the legacy one)
- Avoids exposing a deprecated field via schema (which would be the alternative)
### Relation to #3388
This issue and #3388 attack the same underlying problem from different angles:
- **#3388**: expose the deprecated legacy field as a schema attribute so users can set it explicitly.
- **This issue**: fix the partial PATCH so the legacy field never gets reset in the first place.
Implementing the fix here makes #3388's schema exposure optional rather than necessary, and avoids the awkwardness of a `Deprecated:` schema field. The two approaches are not mutually exclusive.
If maintainers prefer the fix proposed here, I'm willing to put up a PR.
### Code of Conduct
- [X] I agree to follow this project's Code of Conduct
Contributor guide
Assessment
This issue has not been assessed yet.