integrations / integrations/terraform-provider-github

[BUG]: github_organization_settings silently drifts repo-creation booleans due to partial PATCH × deprecated legacy field

Open
#3,429 3 comments 0 reactions 0 assignees View on GitHub
r/organization_settings Type: Bug vNext
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.