bd init under $TMPDIR silently plants the embedded store at the OS temp root's own .beads instead of the target directory
Summary
bd init --non-interactive --prefix <x> at a target directory under the OS
temp root (os.TempDir() — e.g. macOS's $TMPDIR, /var/folders/.../T/)
silently plants the new embedded Dolt store at the temp root's own
.beads/embeddeddolt/<prefix> instead of locally at
<target>/.beads/embeddeddolt/<prefix> — even on an otherwise-clean machine
with no pre-existing .beads anywhere, and even with a brand-new,
never-before-used --prefix. This makes every mktemp -d-based hermetic bd
fixture in ANY caller's test suite silently alias into one shared,
ever-growing store instead of getting its own isolated one.
Once this has happened once, it compounds: findParentConfig's ancestor
search (cmd/bd/bootstrap.go) has no ceiling at the OS temp root (only at
$HOME), so every subsequent bd -C <any-subdir-of-tmpdir> call — init,
create, count, list — finds the now-existing temp-root .beads and reuses
it, regardless of the target directory's own path or configured prefix.
Environment
bd version 1.1.0 (861fd98b9)- macOS (Darwin 25.6.0), embedded Dolt mode (
dolt_mode: embedded) - No
BEADS_DOLT_SHARED_SERVER, nodolt.shared-serverconfig — confirmedIsSharedServerMode()is false; this is not the shared-server codepath.
Reproduction
S="$(mktemp -d)" # e.g. /var/folders/xx/.../T/tmp.XXXXXXXX
mkdir -p "$S/store-a" "$S/store-b"
( cd "$S/store-a" && bd init --non-interactive --prefix rta )
( cd "$S/store-b" && bd init --non-interactive --prefix rtb )
bd -C "$S/store-a" create "fixture A" -t task -q
bd -C "$S/store-b" create "fixture B1" -t task -q
bd -C "$S/store-b" create "fixture B2" -t task -q
bd -C "$S/store-a" count --json # {"count": 3} -- expected 1
bd -C "$S/store-b" count --json # {"count": 3} -- expected 2, SAME as store-a
# Check where it actually landed:
ls -d "$S/store-a/.beads" 2>&1 # No such file or directory
ls -d "${TMPDIR%/}/.beads" 2>&1 # <- exists; dolt_database in its
# metadata.json is "rta", the FIRST
# prefix ever used, not "rtb"Even a brand-new prefix that's never been used before gets silently
discarded — creates land under whatever prefix the temp root's .beads
already has:
S2="$(mktemp -d)"
mkdir -p "$S2/solo"
( cd "$S2/solo" && bd init --non-interactive --prefix zzq9999 )
bd -C "$S2/solo" create "solo" -t task -q
bd -C "$S2/solo" list --json | jq -r '.[0].id' # rta-xxxxxx, NOT zzq9999-xxxxxxRoot cause (as far as I traced it)
- Trigger: when
bd inithas to perform its own internalgit init(i.e. no.gitexists yet at the target), something in the init flow falls back to placing the new embedded store's physical files underos.TempDir()instead of the target directory — reproducible from a fully clean state, not merely a reuse of pre-existing ancestor state. Workaround confirmed deterministic across repeated fresh-machine-state runs: pre-creating the git repo with a separategit init -qcall before invokingbd initavoids the misbehavior entirely —bd initthen correctly creates.beadslocally at the target. This points at an ordering/caching issue around git-root detection immediately afterbd's owngit initstep, though I did not locate the exact line. - Compounding factor (confirmed in source):
findParentConfigincmd/bd/bootstrap.gowalks up from a directory's parent looking for an ancestor.beads, with no ceiling at the OS temp root — only at$HOME:Once (1) has planted afor dir := start; dir != "/" && dir != "."; { candidate := filepath.Join(dir, ".beads") ... if homeDir != "" && dir == homeDir { break } ... }.beadsat the temp root once, this walk finds it from literally anymktemp -dsubdirectory forever after (until someone manually removes it), for any caller, any prefix.
Impact
Any test suite that builds "isolated" bd fixtures under mktemp -d — a very
common, otherwise-idiomatic pattern — can silently get cross-fixture
aliasing instead of isolation, with no error and no warning. We hit this for
real in a Gas Town regression test (dolt-compact-routine.selftest.sh)
whose entire job is to catch exactly this class of aliasing bug in a
downstream caller's own code — the test's own fixture harness had quietly
become subject to the identical bug it exists to detect, which meant it
could no longer be trusted to catch a real regression. Root-cause writeup
and the observed store (accumulated to 40+ stray "fixture bead" rows from
repeated affected test runs) confirmed live 2026-09-18.
Suggested direction (not prescriptive — happy to be told this is wrong)
bd initshould never place a newly created store's files outside the exact directory it was asked to initialize, regardless of git-detection state at the time it runs.- Separately,
findParentConfig's ancestor walk arguably should stop at (or never enter)os.TempDir()'s own value — a.beadssitting directly at the OS-managed temp root can never legitimately correspond to a real project someone meant to nest under.
Workaround for callers today
git init -q at the target directory before calling bd init --non-interactive avoids the misbehavior. (Also worth defensively verifying
<target>/.beads/metadata.json exists locally right after bd init, in
case some other trigger of the same class exists that this workaround
doesn't cover.)
Source: gastownhall/beads