bd update --notes "" wipes the notes field at exit 0 with a success receipt; --allow-empty-description has no --allow-empty-notes counterpart
TL;DR
bd update <id> --notes "" erases the entire notes field, prints ✓ Updated issue: ..., and exits 0. There is no flag to gate it. bd update already ships --allow-empty-description for exactly this hazard on the description field, so the guard concept exists in the CLI and is simply not wired to notes.
The common way to hit this is not typing --notes "" on purpose. It is a command substitution that collapses to the empty string:
bd update "$ID" --notes "$(cat merged.txt)" # merge script died before writing merged.txtNothing about the output distinguishes that from a successful write.
Environment
| Item | Value |
|---|---|
bd version |
1.0.5 (Homebrew) |
| OS | macOS (darwin 25.5.0), Apple silicon |
| Engine | embedded Dolt (in-process, no server) |
Reproduction
On a throwaway issue, four cases. Only the last is the bug; the other three are included because the contrast is the argument.
ID=$(bd create "throwaway" --json | ...) # any issue
bd update "$ID" --notes "SECTION_ALPHA: first section."
bd show "$ID" | grep SECTION_ALPHA # present
bd update "$ID" --append-notes "SECTION_BETA: second section."
bd show "$ID" | grep -c 'SECTION_ALPHA\|SECTION_BETA' # 2 — appends correctly
rm -f /tmp/definitely-missing.txt
bd update "$ID" --append-notes "$(cat /tmp/definitely-missing.txt 2>/dev/null)"
bd show "$ID" | grep -c 'SECTION_ALPHA\|SECTION_BETA' # 2 — safe no-op, good
bd update "$ID" --notes "$(cat /tmp/definitely-missing.txt 2>/dev/null)"
bd show "$ID" | grep -c 'SECTION_ALPHA\|SECTION_BETA' # 0 — WIPEDObserved on the last call:
✓ Updated issue: <id> — throwaway
exit 0| Command | Content | Result |
|---|---|---|
--append-notes |
text | appends with newline separator, both survive |
--append-notes |
"" from a dead $(cat) |
safe no-op, nothing lost, exit 0 |
--notes |
text | replaces, prior content gone (documented behaviour) |
--notes |
"" from a dead $(cat) |
wipes, ✓ Updated, exit 0 |
--append-notes already does the right thing on an empty payload. That is the strongest evidence the empty case is understood somewhere in the codebase and just is not applied on the --notes path.
Why this is worth a flag rather than "don't do that"
Replacement on --notes is correct and expected. The bug is that the empty replacement is indistinguishable from success, so the failure is silent and the data is unrecoverable from the CLI. In our own working notes this failure has twelve dated occurrences across three distinct root causes, all of them upstream of the --notes call rather than in it:
- a command substitution collapsing to the empty string when its input file was never written;
bd show <id> --jsonreturning a JSON list rather than an object, so a naivejson.load(...).get('notes')throws and the extraction writes a zero-byte file;bdinvoked from outside the beads repo writing an empty--jsonpayload at exit 0, indistinguishable from an empty notes field.
Each of those produces the same thing at the call site: an empty string. Any of them plus --notes is a silent wipe. The largest single loss we recorded was 5,400 characters, caught only because a per-section grep ran afterwards.
Proposed fix
Mirror the existing description behaviour:
bd update <id> --notes ""(or a substitution that resolves to empty) exits non-zero with a message naming the current notes length and pointing at the override.- Add
--allow-empty-notesto perform the erase deliberately. - Optionally extend the same guard to
--notes-file/ stdin input, which is where--allow-empty-descriptionalready applies.
A softer variant, if a hard refusal is considered breaking: keep exit 0 but make the receipt honest, e.g. ✓ Updated issue: <id> (notes cleared, 5400 chars removed). Most of the damage here comes from a success string that reads identically whether 5,400 characters survived or vanished.
Related
- #3964 —
--append-notessilently drops writes in rapid succession. Same silent-loss-behind-a-success-receipt shape on the sibling flag. Relevant here because--append-notesis otherwise the correct answer to most of what people reach for--notesto do. - #3102 —
--notes-file/--append-notes-file. If those land, the empty-payload guard should cover them from the start; a file that exists but is zero bytes is the same hazard with a different surface.
Source: gastownhall/beads