Expected Behavior
When security_and_analysis.code_security.status is set to "enabled" (and the repository already has Code Security enabled on GitHub), terraform plan should show no changes. The provider should read the code_security status back from the GitHub API into state and send it on create/update, exactly as it already does for secret_scanning and secret_scanning_push_protection.
Actual Behavior
terraform plan reports a permanent diff on every run, trying to add the code_security block even though the GitHub API already returns code_security.status = "enabled":
~ resource "github_repository" "this" {
id = "<REPO>"
~ security_and_analysis {
+ code_security {
+ status = "enabled"
}
# (2 unchanged blocks hidden) # secret_scanning / secret_scanning_push_protection
}
}
Plan: 0 to add, 1 to change, 0 to destroy.
apply does not converge: the next plan shows the same diff again.
Root cause
The code_security block is declared in the schema, but it is wired into neither the read nor the write path:
- flattenSecurityAndAnalysis() (READ) only sets advanced_security, secret_scanning, secret_scanning_push_protection — it never sets code_security, so the API value (enabled) is dropped and never stored in state.
- calculateSecurityAndAnalysis() (WRITE) only handles those same three fields — it never sends code_security to the API.
Because config always renders code_security {} but state never contains it, Terraform plans to add it forever, and apply never actually writes it.
Proposed fix
|
func flattenSecurityAndAnalysis(securityAndAnalysis *github.SecurityAndAnalysis) []any { |
|
func calculateSecurityAndAnalysis(d *schema.ResourceData) *github.SecurityAndAnalysis { |
// calculateSecurityAndAnalysis() — WRITE
if ok, status := tryGetSecurityAndAnalysisSettingStatus(lookup, "code_security"); ok {
securityAndAnalysis.CodeSecurity = &github.CodeSecurity{
Status: github.Ptr(status), // on main: new(status)
}
}
// flattenSecurityAndAnalysis() — READ (conditional, like advanced_security)
codeSecurity := securityAndAnalysis.GetCodeSecurity()
if codeSecurity != nil {
securityAndAnalysisMap["code_security"] = []any{map[string]any{
"status": codeSecurity.GetStatus(),
}}
}
Terraform Version
Terraform v1.15.2
on linux_amd64
+ provider registry.terraform.io/integrations/github v6.12.1
GitHub Installation Type
Affected Resource(s)
Terraform Configuration Files
resource "github_repository" "test" {
name = "code-security-repro"
description = "repro for code_security perpetual diff"
visibility = "private"
security_and_analysis {
code_security {
status = "enabled"
}
secret_scanning {
status = "enabled"
}
secret_scanning_push_protection {
status = "enabled"
}
}
}
Precondition: the repository already has Code Security enabled on GitHub (e.g. enabled by an org-level security configuration). Confirm with:
gh api /repos/<OWNER>/<REPO> --jq '.security_and_analysis.code_security'
{"status":"enabled"}
Steps to Reproduce
- Ensure the repo has code_security enabled on GitHub (verify with the gh api command above → "enabled").
- Apply the configuration above (apply reports success).
- Run
terraform plan again.
=> Plan is NOT empty; it shows + code_security { status = "enabled" }.
- Run
terraform apply then terraform plan repeatedly.
=> The same diff reappears every time; it never converges.
Debug Output
# terraform plan (TF_LOG=DEBUG recommended; relevant excerpt)
~ resource "github_repository" "this" {
id = "<REPO>"
~ security_and_analysis {
+ code_security {
+ status = "enabled"
}
# (2 unchanged blocks hidden)
}
}
Plan: 0 to add, 1 to change, 0 to destroy.
# Evidence the API already has it enabled (so this is a provider read/write gap, not API state):
$ gh api /repos/<OWNER>/<REPO> --jq '.security_and_analysis'
{
"code_security": { "status": "enabled" },
"secret_scanning": { "status": "enabled" },
"secret_scanning_push_protection": { "status": "enabled" },
...
}
Code of Conduct
Expected Behavior
When security_and_analysis.code_security.status is set to "enabled" (and the repository already has Code Security enabled on GitHub), terraform plan should show no changes. The provider should read the code_security status back from the GitHub API into state and send it on create/update, exactly as it already does for secret_scanning and secret_scanning_push_protection.
Actual Behavior
terraform plan reports a permanent diff on every run, trying to add the code_security block even though the GitHub API already returns code_security.status = "enabled":
apply does not converge: the next plan shows the same diff again.
Root cause
The code_security block is declared in the schema, but it is wired into neither the read nor the write path:
Because config always renders code_security {} but state never contains it, Terraform plans to add it forever, and apply never actually writes it.
Proposed fix
terraform-provider-github/github/resource_github_repository.go
Line 1190 in f37a4b6
terraform-provider-github/github/resource_github_repository.go
Line 553 in f37a4b6
Terraform Version
GitHub Installation Type
Affected Resource(s)
Terraform Configuration Files
Steps to Reproduce
terraform planagain.=> Plan is NOT empty; it shows
+ code_security { status = "enabled" }.terraform applythenterraform planrepeatedly.=> The same diff reappears every time; it never converges.
Debug Output
Code of Conduct