fix(env): prune orphaned lock entries + idempotent UpsertEnv#176
Conversation
The real failure mode behind the kubehz-cluster "verify never clean" report was duplicate lock env entries, not single-hash drift (so #174 couldn't heal it): b.lock had two blocks for the same repo — #main (configured in b.yaml) and #kubeone (NOT in b.yaml, a leftover from renaming the label). They shared 101 dests; for render.sh, #main recorded the correct hash (matches disk → verify ✓) while the orphaned #kubeone kept a stale hash (→ verify ✗). FindEnv/UpsertEnv key on the configured ref+label, so the orphan is never re-synced or replaced, yet `b verify` iterates ALL blocks and reports its stale hashes forever. Two fixes: - prune (repair): updateEnvs now drops lock env entries whose (ref,label) is not in b.yaml before writing the lock — using the same RefBase/RefLabel keys it writes with, so a configured env (even one not synced this run, or up-to-date) is never pruned. Runs on every `b update` (the lock is rewritten regardless), so a poisoned lock self-heals: the orphan is removed, verify goes clean. Emits a one-line notice listing what was pruned. - idempotent UpsertEnv (prevention): replace the first (ref,label) match in place and drop any further duplicates, so a same-key dup collapses to one on write instead of leaving a stale twin. Tests: TestUpdateEnvs_PrunesOrphanedLockEntries (orphan #kubeone removed while up-to-date #main is kept; fails without the prune) and TestUpsertEnv_CollapsesDuplicates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes b verify reporting persistent env drift caused by stale/duplicate b.lock env entries by (1) pruning lock env entries that are no longer configured in b.yaml during b update, and (2) making lock env upserts idempotent by collapsing duplicate (ref,label) entries.
Changes:
- Make
Lock.UpsertEnvreplace the first(ref,label)match and drop any further duplicates. - Add
UpdateOptions.pruneOrphanedEnvsto remove lock env entries not present in the current config before writingb.lock. - Add regression tests covering orphan-pruning during update and duplicate-collapse behavior in
UpsertEnv.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| pkg/lock/lock.go | Makes env upsert idempotent by collapsing duplicate (ref,label) entries. |
| pkg/lock/lock_test.go | Adds coverage for duplicate-collapse behavior in UpsertEnv. |
| pkg/cli/update.go | Prunes orphaned lock env entries (not in b.yaml) before writing the lock. |
| pkg/cli/env_test.go | Adds regression test ensuring b update prunes orphaned env lock entries even when the live env is up to date. |
Copilot: pruneOrphanedEnvs only removed entries not in config. If b.lock held two entries for the SAME configured (ref,label) and the env was up-to-date, SyncEnv skips it → UpsertEnv never runs to collapse the twin → the lock is still rewritten with both → b verify keeps flagging the stale duplicate. The normalize pass now also collapses duplicates of a configured (ref,label), keeping the first occurrence (the FindEnv-visible / up-to-date one). So a rewritten lock is idempotently clean even when nothing synced this run — orphans (not in b.yaml) and same-key twins are both dropped, with a per-reason notice. Test: TestUpdateEnvs_DedupsConfiguredLockEntries (two #infra entries, env up-to-date → collapses to one, keeps the good-hash first entry). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Review loop complete — 2 rounds (Copilot + subagent). Round 1: also dedup same-key lock entries (Copilot). Round 2 clean (Copilot 0 new comments; subagent SHIP). Prunes orphaned + duplicate lock env entries so a poisoned lock self-heals on update; verify goes clean. Regression tests fail without the fix. build/vet/gofmt/test green. Merging. |
🤖 I have created a release *beep* *boop* --- ## [4.18.4](v4.18.3...v4.18.4) (2026-06-25) ### Bug Fixes * **env:** prune orphaned lock entries + idempotent UpsertEnv ([#176](#176)) ([44eb405](44eb405)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Problem (refined diagnosis — #174 fixed the wrong mode)
b verifywas never clean for a user despite files being byte-identical to upstream. The real cause is duplicate lock env entries, not single-hash drift (which is why #174's auto-heal couldn't fix it).Their
b.lockhad two blocks for the same repo:git@github.com:kernpilot/lok8s#main— 223 files, configured inb.yamlgit@github.com:kernpilot/lok8s#kubeone— 115 files, NOT inb.yaml(a leftover from renaming the labelkubeone→main)They share 101 dests. For
render.sh,#mainrecords61d915c4…(matches disk → ✓) while the orphaned#kubeonekeeps a stale1918e425…(→ ✗). Confirmed against their lock:jq '.envs[] | .ref,.label'→ both present, only#mainin config.Why nothing healed it:
FindEnv/UpsertEnvkey on the configured(ref,label)=#main, so the orphan is never re-synced or replaced → "(up to date)".b verifyiterates all blocks → reports#kubeone's stale hashes against files#mainowns → 101 mismatches.Fixes
updateEnvsdrops lock env entries whose(ref,label)isn't inb.yaml, before writing the lock — keyed via the samegitcache.RefBase/RefLabelit writes with, so a configured env (even one filtered-out this run or up-to-date) is never pruned. The lock is rewritten on everyb update, so a poisoned lock self-heals: orphan removed,verifygoes clean. Emits a one-line notice of what was pruned.UpsertEnv(prevention): replace the first(ref,label)match in place and drop any further duplicates, so a same-key dup collapses to one on write instead of leaving a stale twin.Tests
TestUpdateEnvs_PrunesOrphanedLockEntries— orphan#kubeoneremoved while up-to-date#mainis kept; fails without the prune (reproduces the 2-entry lock).TestUpsertEnv_CollapsesDuplicates— pre-existing same-key dups collapse to one.go build,go vet ./...,gofmt, fullgo test ./...all clean.🤖 Generated with Claude Code