bd config set deletes a same-named flat dotted key, and bd's own readers then disagree (bd-zj95 mirrored)
#6578 fixed bd-zj95 by making bd config set <dotted.key> write a nested mapping. Where a flat top-level key of the same literal name already exists, it now deletes that entry and re-emits the value nested:
// internal/config/yaml_config.go, updateNestedYamlKey
// pre-#6578:
if findMappingChild(mapping, key) != -1 { return "", false, nil } // -> flat writer updated in place
// post-#6578:
if idx := findMappingChild(mapping, key); idx != -1 {
mapping.Content = append(mapping.Content[:idx], mapping.Content[idx+2:]...) // drops the flat entry
}That migration is deliberate and is what makes bd's own round trip hold, because GetStringFromDir (internal/config/config.go:693) reads nested only and was not changed by #6578. But it has two consequences worth fixing.
1. bd destroys a spelling another writer chose
.beads/config.yaml is not exclusively bd's. An external orchestrator can legitimately write and read flat dotted keys in it — gascity's EnsureCanonicalConfig does exactly that for dolt.host/port/user/mode/auto-start, export.auto, backup.enabled, and its reader (findValue) is a literal string compare over root-level keys only, so a nested value is invisible to it.
Verified empirically against binaries built from 87c7375d5 (pre) and fa9543900 (post):
| key written | pre-#6578 | post-#6578 |
|---|---|---|
dolt.host |
flat updated in place, external reader found=true |
flat deleted, external reader found=false |
dolt.port, dolt.auto-start, dolt.mode, export.auto, backup.enabled |
same | same |
Non-colliding keys are unaffected — everything bd writes unattended (dolt.shared-server, dolt.debug, sync.remote, agents.file) leaves external keys byte-intact.
2. bd's own two readers disagree when both spellings are present
Once an external writer re-adds its flat key, the file carries both. bd then contradicts itself:
PROBE key="dolt.host" GetStringFromDir="10.0.0.1" viper="127.0.0.1"
PROBE key="dolt.auto-start" GetStringFromDir="true" viper="false"bd config get dolt.host prints the flat value while GetStringFromDir returns the nested one. This is bd-zj95 reappearing mirrored — same split-brain, opposite spelling. It matters at runtime: internal/storage/dolt/open.go:43 reads config.GetString (viper → flat) first and only falls back to GetStringFromDir, so an operator's bd config set dolt.auto-start true can be silently ignored in favour of a stale flat false.
The two writers also ping-pong rather than converge: each bd write deletes the flat key, each external converge re-adds it.
bd doctor reports nothing about a dual-spelling file (78 passed / 7 warnings / 2 errors, identical pre and post — there is no duplicate-key check).
Suggested fix (~10 lines, no knowledge of any external tool)
updateNestedYamlKey: when the literal flat key exists, update it in place rather than deleting it. Shape-preserving writes — bd stops destroying a spelling it did not choose.GetStringFromDir: also try the literal flat key alongside the nested descent. This keeps bd-zj95 fixed without relying on migration-on-write, and makes bd's two readers agree.bd doctor: warn when a key is present in both flat and nested spelling.
(1) and (2) are complementary: with (2) in place, (1) costs nothing.
Scope, and why this is not a 1.3.1 blocker
It requires a human to run bd config set on one of seven specific keys inside an externally managed workspace. No gascity automation does so (BdStore.ConfigSet has zero production callers), bd's unattended writes do not collide, and bd init's export.auto write is behind an interactive prompt. In the one shape where the damage matters — a scope whose endpoint coordinates come from the file — it fails loudly (canonical … config requires both dolt.host and dolt.port) rather than silently.
Reverting #6578 is not the answer: it also fixed a silent no-op unset of nested keys, corruption of prose inside notes: | block scalars, and trailing-newline loss.
Not yet verified
Live-Dolt behaviour; bd config list rendering of a dual-shape file (it needs a database and errored on the DB-less fixtures); and the user-global ~/.beads/config.yaml writer (SetUserYamlConfig/UnsetUserYamlConfig), which took the same normalizeYamlKey removal.
Artifacts and fixture generator under /home/ubuntu/.cache/gascity-bd/wf6578/ and verify6578/.
Source: gastownhall/beads