Bug
When updating a workos_user or workos_organization resource and removing metadata keys, the removed keys persist on the WorkOS side. This causes Terraform to report:
Error: Provider produced inconsistent result after apply
When applying changes to workos_user.test, provider produced an unexpected
new value: .metadata: new element "team" has appeared.
Root cause
The WorkOS API merges metadata on PUT updates rather than replacing it. To delete a key, the API requires sending null for that key's value (docs). The provider currently sends only the new keys using map[string]string, which cannot represent null — so removed keys are never communicated to the API.
Affected resources
workos_user — resource_user.go Update method (line ~473)
workos_organization — resource_organization.go Update method (line ~354)
Failing test
TestAccUserResource_withExternalIDAndMetadata step 3/3 — updates metadata from {department: "Engineering", team: "Platform"} to {department: "Product", role: "Lead"}. The "team" key persists because the provider never tells the API to delete it.
Note: This test was previously masked by a different failure (the IsUnknown() crash fixed in #16). The SHA-pinning commit also failed CI for the same underlying reason.
Proposed fix
1. Change update request structs to support null values
In internal/client/models.go, change the Metadata field on update request structs from map[string]string to map[string]*string:
// UserUpdateRequest
Metadata map[string]*string `json:"metadata,omitempty"`
// OrganizationUpdateRequest
Metadata map[string]*string `json:"metadata,omitempty"`
Create request structs remain map[string]string — no null values needed on create.
2. Compute key deletions in provider Update methods
In both resource_user.go and resource_organization.go Update methods, compare old vs new metadata and explicitly set removed keys to nil:
// Build update map with pointer values
updateMap := make(map[string]*string, len(newMetadata))
for k, v := range newMetadata {
v := v
updateMap[k] = &v
}
// Null out removed keys so WorkOS deletes them
if !state.Metadata.IsNull() && !state.Metadata.IsUnknown() {
oldMetadata := make(map[string]string)
state.Metadata.ElementsAs(ctx, &oldMetadata, false)
for key := range oldMetadata {
if _, exists := newMetadata[key]; !exists {
updateMap[key] = nil // serializes as JSON null
}
}
}
updateReq.Metadata = updateMap
3. Handle metadata fully removed (set → null)
Edge case: if a user removes the entire metadata block from their config, plan.Metadata is null. Currently ElementsAs on a null value won't produce a usable map, so old keys persist silently. The fix should detect this case and send all old keys as null:
if plan.Metadata.IsNull() && !state.Metadata.IsNull() {
// All metadata removed — null out every old key
oldMetadata := make(map[string]string)
state.Metadata.ElementsAs(ctx, &oldMetadata, false)
updateMap := make(map[string]*string, len(oldMetadata))
for key := range oldMetadata {
updateMap[key] = nil
}
updateReq.Metadata = updateMap
}
Files to change
| File |
Change |
internal/client/models.go |
UserUpdateRequest.Metadata and OrganizationUpdateRequest.Metadata → map[string]*string |
internal/provider/resource_user.go |
Update method: compute key deletions + handle full removal |
internal/provider/resource_organization.go |
Update method: same pattern as user resource |
Test plan
Bug
When updating a
workos_userorworkos_organizationresource and removing metadata keys, the removed keys persist on the WorkOS side. This causes Terraform to report:Root cause
The WorkOS API merges metadata on PUT updates rather than replacing it. To delete a key, the API requires sending
nullfor that key's value (docs). The provider currently sends only the new keys usingmap[string]string, which cannot representnull— so removed keys are never communicated to the API.Affected resources
workos_user—resource_user.goUpdate method (line ~473)workos_organization—resource_organization.goUpdate method (line ~354)Failing test
TestAccUserResource_withExternalIDAndMetadatastep 3/3 — updates metadata from{department: "Engineering", team: "Platform"}to{department: "Product", role: "Lead"}. The "team" key persists because the provider never tells the API to delete it.Proposed fix
1. Change update request structs to support
nullvaluesIn
internal/client/models.go, change theMetadatafield on update request structs frommap[string]stringtomap[string]*string:Create request structs remain
map[string]string— no null values needed on create.2. Compute key deletions in provider Update methods
In both
resource_user.goandresource_organization.goUpdate methods, compare old vs new metadata and explicitly set removed keys tonil:3. Handle metadata fully removed (set → null)
Edge case: if a user removes the entire
metadatablock from their config,plan.Metadatais null. CurrentlyElementsAson a null value won't produce a usable map, so old keys persist silently. The fix should detect this case and send all old keys asnull:Files to change
internal/client/models.goUserUpdateRequest.MetadataandOrganizationUpdateRequest.Metadata→map[string]*stringinternal/provider/resource_user.gointernal/provider/resource_organization.goTest plan
TestAccUserResource_withExternalIDAndMetadatapasses (step 3 — key removal)go build ./...passes