fix(diff): compare non-secret env vars against fresh value, not stale real_value (#78) #79
No reviewers
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:apply
scope:capture
scope:coolify-api
scope:fleet
scope:manifest
scope:secrets
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/cast#79
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/env-diff-real-value"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #78.
What
cast diffre-proposed env vars that were updated in place and are genuinely correct on the box. During the incubator prod cutover (2026-07-16), five flags flipped false→true, were applied, and redeployed — yet every subsequentcast diff --env prodstill listed all five asenv … : change. Vars that were only ever created (NODE_ENV, the derived base-URLs) did not re-propose. A false drift that never clears also masks real drift.Root cause
fetchEnvcollapsed each live var toreal_value ?? value. Coolify leaves the storedreal_valuestale after an in-place PATCH ofvalue(a redeploy does not refresh it), so the diff compared the stale"false"against the manifest's"true". Created-once vars have a freshreal_value, which is exactly why only the flipped-in-place vars re-proposed.Fix (cast-side, per-var — issue's preferred direction)
The
real_value ?? valuepreference is deliberate for secrets (valueis masked to a plain token, soreal_valueis the only plaintext). So the choice is made per var, not blanket-switched:fetchEnvnow carries both forms through asLiveEnvVar { value, realValue }instead of collapsing at read time.diffEnvpicks per the desired side'ssecretflag it already has: non-secret →value(always fresh), secret →realValue ?? value(unchanged).captureanddraft, which want the decrypted plaintext and compare against no manifest literal, keep the old flattening via a smallflattenEnvhelper — behavior unchanged.Tests
realValuereads clean (the exact prod-cutover reproduction).valueis masked still diffs viarealValue, so a genuine rotation is still caught.npm run checkclean · 511 tests pass.🤖 Generated with Claude Code