Community Note
- Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request.
- Please do not leave +1 or me too comments, they generate extra noise for issue followers and do not help prioritize the request.
- If you are interested in working on this issue or have submitted a pull request, please leave a comment.
Terraform Version & Okta Provider Version(s)
Terraform v1.12.1
on darwin_arm64
- provider registry.terraform.io/okta/okta v6.9.0
The Delete implementation is byte-for-byte identical from v6.6.0 through v6.13.0 and current master, so the defect is present on all of those versions (verified by diffing the function across tags).
Affected Resource(s)
Can this be done in the Admin UI?
Yes
Can this be done in the actual API call?
Yes (deactivate and delete both work individually; the bug is in how the provider handles a failure between them)
Customer Information
Organization Name: (redacted — large paid enterprise org, happy to share privately)
Paid Customer: yes
Terraform Configuration
resource "okta_push_group" "example" {
app_id = "0oaXXXXXXXXXXXXXXXXX" # app ID
source_group_id = "00gXXXXXXXXXXXXXXXXX" # source group ID
status = "ACTIVE"
delete_target_group_on_destroy = false
}
Debug Output
Debug logs are not available from the CI run where this occurred. The failure is fully explained by code inspection (see Actual Behavior); the destroy failed with the provider error:
Error: failed to delete push group mapping:
with okta_push_group.example,
on ... line ..., in resource "okta_push_group" "example":
... giving up after N attempt(s)
Expected Behavior
Delete performs two API calls: it deactivates the mapping, then deletes it. If the deactivation succeeds and the delete then fails, the provider should persist the fact that the mapping is now INACTIVE to state before returning the error, so that state reflects reality.
Per the plugin framework resource delete documentation, state set in the Delete response is preserved when error diagnostics are returned — the framework only removes the resource from state when Delete reports no errors. That mechanism exists precisely for partially-completed deletes like this one.
At an absolute minimum, the error message should tell the operator that the mapping was already deactivated and has stopped pushing group membership downstream, rather than the current bare "failed to delete push group mapping".
Actual Behavior
Delete in okta/services/idaas/resource_okta_push_group.go (lines 280–307 on master; identical code at lines 271–298 in v6.9.0) is a two-phase sequence with no intermediate state write:
if state.Status.ValueString() != "INACTIVE" {
_, _, err := r.config.OktaIDaaSClient.OktaSDKClientV6().GroupPushMappingAPI.UpdateGroupPushMapping(ctx, state.AppId.ValueString(), state.ID.ValueString()).Body(v6okta.UpdateGroupPushMappingRequest{
Status: "INACTIVE",
}).Execute()
if err != nil {
resp.Diagnostics.AddError("failed to delete push group mapping: ", err.Error())
return
}
}
_, err := r.config.OktaIDaaSClient.OktaSDKClientV6().GroupPushMappingAPI.DeleteGroupPushMapping(ctx, state.AppId.ValueString(), state.ID.ValueString()).DeleteTargetGroup(state.DeleteTargetGroupOnDestroy.ValueBool()).Execute()
if err != nil {
resp.Diagnostics.AddError("failed to delete push group mapping: ", err.Error())
return
}
}
- Lines 292–300 (
master): if the mapping is not already INACTIVE, UpdateGroupPushMapping is called with Status: "INACTIVE". The Okta API requires a mapping to be deactivated before it can be deleted — the provider's own guard message at line 288 says as much ("To delete a group push mapping, the status must be INACTIVE").
- Lines 302–306:
DeleteGroupPushMapping.
If step 1 succeeds and step 2 fails — for example the provider's HTTP transport exhausts its 429 retries in a rate-limited org (giving up after N attempt(s)), or the API returns a 5xx — Delete returns an error and no state is written. The framework preserves the prior state, which still records status = "ACTIVE".
The divergence is a silent functional change, not just a cosmetic state mismatch:
- Reality in Okta: the mapping is
INACTIVE. It has stopped pushing group membership to the downstream application, so membership changes in the Okta source group no longer propagate.
- Terraform state and the next plan:
status = "ACTIVE".
- What the operator sees:
failed to delete push group mapping: ..., with no indication that the mapping was already deactivated and is no longer syncing members downstream.
It does self-heal on the next run: the next refresh reads status = "INACTIVE", and Delete then skips step 1 and retries the DELETE. But between the failed apply and the next apply, downstream group membership has silently stopped syncing while state claims the mapping is ACTIVE. In a scheduled or CI-driven pipeline that gap can be hours or days, and nothing in the plan output flags it.
This is the same class of defect as #2899 / #2900 in okta_request_condition ("fix(request_condition): save state before post-create calls"), and the same principle applies: persist what the provider already knows before making a follow-up API call.
Suggested fix
After the deactivation at step 1 succeeds, write the observed status back to resp.State before attempting the DELETE:
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("status"), "INACTIVE")...)
(or set the whole model via resp.State.Set with state.Status updated). The framework nuance is worth stating explicitly, because it is the opposite of the intuition for Create: in internal/fwserver/server_deleteresource.go the response state is seeded from prior state and RemoveResource is only called when deleteResp.Diagnostics.HasError() is false, while resp.NewState = &deleteResp.State is assigned unconditionally. So an attribute written in Delete survives on the error path and is discarded on the success path — exactly the behaviour needed here. path is already imported in this file, and Delete already reads prior state from resp.State at line 282, so the change is small.
Offered as a suggestion rather than a prescription — a maintainer may prefer to re-set the full model. Even if the state write is rejected, please consider making the error message distinguish "could not deactivate" from "deactivated but could not delete", so the operator knows group push has stopped.
Steps to reproduce
- Apply the configuration above so an
ACTIVE push group mapping exists and is in state.
- Remove the resource from the configuration (or run
terraform destroy -target=okta_push_group.example).
- Arrange for the DELETE to fail after the deactivation PATCH (
PATCH /api/v1/apps/{appId}/group-push/mappings/{mappingId} with {"status":"INACTIVE"}) succeeds. Deterministic repro: intercept DELETE /api/v1/apps/{appId}/group-push/mappings/{mappingId} with a proxy and return HTTP 429 (or any 5xx) repeatedly until the transport gives up. In the real world this happens organically in orgs under API rate-limit pressure.
terraform apply / terraform destroy fails with failed to delete push group mapping.
- Inspect the mapping in Okta (
GET /api/v1/apps/{appId}/group-push/mappings/{mappingId}) — status is INACTIVE, and membership is no longer being pushed to the downstream app.
- Inspect
terraform state show okta_push_group.example — status = "ACTIVE". terraform plan also shows nothing about the status change; the only signal is the destroy error from the previous run.
Important Factoids
- Nothing atypical about authentication: standard service-account (API token) auth.
- The same shape exists in
Update (single call, so no window) and in Create (a single POST, so no window) — this report is specific to Delete, which is the only method in this resource making two mutating calls.
- The window is not hypothetical for large orgs: 429-driven exhaustion of the transport's retry budget is the common trigger, and it hits the second call more often than the first because the first call has already consumed part of the rate-limit budget for that endpoint.
References
Community Note
Terraform Version & Okta Provider Version(s)
Terraform v1.12.1
on darwin_arm64
The
Deleteimplementation is byte-for-byte identical from v6.6.0 through v6.13.0 and currentmaster, so the defect is present on all of those versions (verified by diffing the function across tags).Affected Resource(s)
okta_push_groupCan this be done in the Admin UI?
Yes
Can this be done in the actual API call?
Yes (deactivate and delete both work individually; the bug is in how the provider handles a failure between them)
Customer Information
Organization Name: (redacted — large paid enterprise org, happy to share privately)
Paid Customer: yes
Terraform Configuration
Debug Output
Debug logs are not available from the CI run where this occurred. The failure is fully explained by code inspection (see Actual Behavior); the destroy failed with the provider error:
Expected Behavior
Deleteperforms two API calls: it deactivates the mapping, then deletes it. If the deactivation succeeds and the delete then fails, the provider should persist the fact that the mapping is nowINACTIVEto state before returning the error, so that state reflects reality.Per the plugin framework resource delete documentation, state set in the
Deleteresponse is preserved when error diagnostics are returned — the framework only removes the resource from state whenDeletereports no errors. That mechanism exists precisely for partially-completed deletes like this one.At an absolute minimum, the error message should tell the operator that the mapping was already deactivated and has stopped pushing group membership downstream, rather than the current bare "failed to delete push group mapping".
Actual Behavior
Deleteinokta/services/idaas/resource_okta_push_group.go(lines 280–307 onmaster; identical code at lines 271–298 in v6.9.0) is a two-phase sequence with no intermediate state write:master): if the mapping is not alreadyINACTIVE,UpdateGroupPushMappingis called withStatus: "INACTIVE". The Okta API requires a mapping to be deactivated before it can be deleted — the provider's own guard message at line 288 says as much ("To delete a group push mapping, the status must be INACTIVE").DeleteGroupPushMapping.If step 1 succeeds and step 2 fails — for example the provider's HTTP transport exhausts its 429 retries in a rate-limited org (
giving up after N attempt(s)), or the API returns a 5xx —Deletereturns an error and no state is written. The framework preserves the prior state, which still recordsstatus = "ACTIVE".The divergence is a silent functional change, not just a cosmetic state mismatch:
INACTIVE. It has stopped pushing group membership to the downstream application, so membership changes in the Okta source group no longer propagate.status = "ACTIVE".failed to delete push group mapping: ..., with no indication that the mapping was already deactivated and is no longer syncing members downstream.It does self-heal on the next run: the next refresh reads
status = "INACTIVE", andDeletethen skips step 1 and retries the DELETE. But between the failed apply and the next apply, downstream group membership has silently stopped syncing while state claims the mapping is ACTIVE. In a scheduled or CI-driven pipeline that gap can be hours or days, and nothing in the plan output flags it.This is the same class of defect as #2899 / #2900 in
okta_request_condition("fix(request_condition): save state before post-create calls"), and the same principle applies: persist what the provider already knows before making a follow-up API call.Suggested fix
After the deactivation at step 1 succeeds, write the observed status back to
resp.Statebefore attempting the DELETE:(or set the whole model via
resp.State.Setwithstate.Statusupdated). The framework nuance is worth stating explicitly, because it is the opposite of the intuition forCreate: ininternal/fwserver/server_deleteresource.gothe response state is seeded from prior state andRemoveResourceis only called whendeleteResp.Diagnostics.HasError()is false, whileresp.NewState = &deleteResp.Stateis assigned unconditionally. So an attribute written inDeletesurvives on the error path and is discarded on the success path — exactly the behaviour needed here.pathis already imported in this file, andDeletealready reads prior state fromresp.Stateat line 282, so the change is small.Offered as a suggestion rather than a prescription — a maintainer may prefer to re-set the full model. Even if the state write is rejected, please consider making the error message distinguish "could not deactivate" from "deactivated but could not delete", so the operator knows group push has stopped.
Steps to reproduce
ACTIVEpush group mapping exists and is in state.terraform destroy -target=okta_push_group.example).PATCH /api/v1/apps/{appId}/group-push/mappings/{mappingId}with{"status":"INACTIVE"}) succeeds. Deterministic repro: interceptDELETE /api/v1/apps/{appId}/group-push/mappings/{mappingId}with a proxy and return HTTP 429 (or any 5xx) repeatedly until the transport gives up. In the real world this happens organically in orgs under API rate-limit pressure.terraform apply/terraform destroyfails withfailed to delete push group mapping.GET /api/v1/apps/{appId}/group-push/mappings/{mappingId}) — status isINACTIVE, and membership is no longer being pushed to the downstream app.terraform state show okta_push_group.example—status = "ACTIVE".terraform planalso shows nothing about the status change; the only signal is the destroy error from the previous run.Important Factoids
Update(single call, so no window) and inCreate(a single POST, so no window) — this report is specific toDelete, which is the only method in this resource making two mutating calls.References
okta_request_conditionRead/Update/Deleteofokta_push_group. It touches the exact lines inDeletedescribed above, but it does not address this divergence: it only adds early returns when the API responds 404 (mapping already gone). On a 429/5xx from the DELETE, the behaviour after that PR is unchanged — prior state withstatus = "ACTIVE"is still preserved while the mapping isINACTIVEin Okta. The two changes are complementary and would need to be reconciled in whichever merges second.Delete: https://developer.hashicorp.com/terraform/plugin/framework/resources/delete