fix: Stale pointer bug in env generation for SOPS variables#448
Conversation
…ration `Generate` captured `targetApp` as a range-copy before calling `ResolveForEnvironment`. Resolution writes resolved SOPS values back into `appDef.Apps[i].Env.Production` (the actual slice element), but `targetApp` still pointed to the pre-resolution copy where all SOPS `Value` fields were nil. `cast.ToString(nil)` then produced empty strings, so every variable declared in `env.default` with `"source": "sops"` was written as `KEY=""` in the generated .env file. Fix: store the app index during the lookup, then access `appDef.Apps[targetAppIdx]` after resolution so the merged env reflects the fully resolved values.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Review summary
Correct, minimal fix for a classic Go range-loop pointer bug. The production code change is clean and well-reasoned. One warning about a dead-code block in the new test; two minor suggestions on test structure. Critical issues 🔴None Warnings 🟡Dead code in The Detail// These nine lines are never read after line 106:
apps := []appdef.App{
{Name: "cms", Env: appdef.Environment{...}},
}
apps[0].Env.Production["SECRET_KEY"] = appdef.EnvValue{...}
// Test then starts over with origApps:
origApps := []appdef.App{...}The dead block should be removed. If the intent was to show the contrast (resolved vs. unresolved), that contrast is already provided by the two separate sub-tests, so this setup adds noise rather than clarity. Suggestions 🟢1. Consider asserting the key exists before checking its value ( assert.Nil(t, staleVars["SECRET_KEY"].Value, ...)If 2. The regression test simulates the resolution step manually rather than exercising This is a reasonable trade-off for a unit test, and the comment at the top of the function explains the reasoning well. If an integration-level test harness already exists (e.g. one with a mock |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #448 +/- ##
==========================================
+ Coverage 64.59% 70.26% +5.67%
==========================================
Files 154 187 +33
Lines 6064 7439 +1375
==========================================
+ Hits 3917 5227 +1310
+ Misses 2064 2012 -52
- Partials 83 200 +117 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
Fixed a bug where SOPS environment variables were appearing as empty strings in generated
.envfiles. The issue was caused by capturing a pointer to a range-loop variable before SOPS resolution occurred, resulting in stale data being used.Key Changes
targetApp = &app) to storing the index (targetAppIdx = i) during the app lookup loop. This ensures that after SOPS resolution modifiesappDef.Apps[i].Env, we read from the updated slice element rather than a stale copy.appDef.Apps[targetAppIdx]directly instead of thetargetApppointer, ensuring resolved SOPS values are visible.TestGenerateDefaultSOPSResolutionwith two sub-tests:Implementation Details
The root cause was that Go range loops create copies of values, so
targetApp = &appcaptured a pointer to a temporary copy. WhenResolveForEnvironmentlater modified the actual slice elementappDef.Apps[i].Env, those changes were invisible to code using the stale pointer. By storing the index instead and accessingappDef.Apps[targetAppIdx]after resolution, we ensure we're always reading from the authoritative slice element.https://claude.ai/code/session_01Q9rtBre5uvt5TuyubMF8Lx