fix(diff): ignore preview env rows so they cannot shadow production (#85) #86
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#86
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/env-preview-shadow"
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 #85. Root cause of the symptom #78 chased.
The bug, in one line
cast declares production env on every write, and read production + preview. That asymmetry is the whole defect.
syncEnvsendsis_preview: falseon every bulk upsert. ButGET /applications/{uuid}/envsdoes not return one row per key — it merges two parallel sets into one flat array:The relations are complements split on
is_preview, with a unique index per(key, resource, is_preview)— so the same key legitimately arrives twice.fetchEnvkeyed bykeyalone andObject.fromEntrieskeeps the last, so cast diffed the manifest against whichever row Coolify happened to serialize last.Confirmed on prod
cast read the trailing
"false"twin and re-proposed achangethat could never clear. The production rows aretrue— prod was correct all along; the diff was the broken thing.Why this masqueraded as a stale read (#78)
Both rows are born equal — Coolify seeds a preview twin — and
syncEnvonly ever PATCHes the production row. So the two diverge for exactly the vars updated in place. Five prod flags flippedfalse→truere-proposed forever, while created-once vars (NODE_ENV,REPORTING_TZ, the${domain:…}base-URLs) stayed clean because their twins still agreed — through the identical code path.#78 spotted that pattern and reached for
real_value. Butreal_valueis anAttributeaccessor recomputed fromvalueon every read, so it cannot go stale — and the probe showsreal_value == valueon every row. Nothing was stale. cast was reading another deployment's value.The fix
fetchEnvdropsis_preview: truerows. A preview var is another deployment's value for the same name: not cast's to compare, and not cast's to write. Services and databases map a single set (their controllers don't merge), so it's a no-op for them.Tests
is_previewfield (services/databases) are kept — absent must mean "keep", never "drop".npm run checkclean · 524 tests pass (npm run build && npm test, as CI runs).Note on #79
Keep it. Comparing a manifest literal against
real_value(an escaped/quoted rendering —is_literal⇒'true'≠true) is wrong regardless, and it closes a latent secret-rotation hazard. It simply was never what was biting these five; its stated motivation should be re-pointed at this issue.🤖 Generated with Claude Code