fix(backend): eliminate race condition in feature flag override writes#839
Conversation
Replace read-modify-write pattern with merge patches for ConfigMap updates in setFlagOverride and DeleteFeatureFlagOverride. The old pattern (GET → modify → Update) caused 500 errors when the frontend batch-saved multiple flag overrides in parallel, because concurrent requests would read the same resourceVersion and the second Update would fail with a conflict. Now uses MergePatchType which atomically sets/removes a single key without reading the full ConfigMap first. Concurrent patches to different keys never conflict. Permission checks changed from "update"/"create" to "patch" to match the new write method. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Claude Code Review - PR 839 |
Claude Code ReviewSummary This PR replaces the error-prone read-modify-write pattern in setFlagOverride and DeleteFeatureFlagOverride with atomic JSON Merge Patches, eliminating the 409 Conflict race condition that caused HTTP 500s during concurrent batch flag saves. The approach is correct and all error-handling paths are covered. RBAC manifests already grant the patch verb for project admins so the permission verb change is safe. Issues by Severity Blocker Issues: None Critical Issues: None Major Issues: None Minor Issues: M1 - Use encoding/json instead of fmt.Sprintf to build patch bodies File: components/backend/handlers/featureflags_admin.go (both setFlagOverride and DeleteFeatureFlagOverride) Go's %q verb uses non-JSON hex notation for invalid UTF-8 sequences. Flag names and bool-string values never contain invalid UTF-8 in practice so this is not a production risk, but json.Marshal is the robust alternative that guarantees valid JSON output. Ref: error-handling.md -- prefer type-safe operations over manual string construction. M2 - Concurrent-create fallback is correct but partially documented File: components/backend/handlers/featureflags_admin.go, inner IsAlreadyExists branch The logic is correct: non-AlreadyExists create errors propagate to the outer error guard. A one-line comment to that effect would help the next reader verify the error flow without tracing every path manually. Positive Highlights
Recommendations
Overall: clean, well-scoped fix. Safe to merge after considering item 1 above. |
Summary
setFlagOverride: now usesMergePatchTypeto atomically set a single key; falls back toCreate+ retry if the ConfigMap doesn't exist yetDeleteFeatureFlagOverride: now usesMergePatchTypewithnullvalue to atomically remove a key"update"/"create"to"patch"to match the new write methodProblem
When workspace admins saved multiple feature flag overrides via the batch save UI, the frontend sent parallel PUT requests. The old
setFlagOverrideused a GET → modify → Update pattern. All concurrent requests read the sameresourceVersion, and every request after the first failed with a Kubernetes 409 Conflict (surfaced as HTTP 500).Test plan
Fixes: RHOAIENG-52262
🤖 Generated with Claude Code