Version: br 0.5.7
Summary
br doctor --repair rebuilds the database from the JSONL export. The export carries
issue state only, so every append-only history table is emptied by the repair and the
resulting database reports integrity_check: ok — the operation looks like a success.
Lost on every repair:
| table |
role |
events |
who changed what, and when |
gate_result_history |
which gate passed/failed, by which reviewer, at which revision |
gate_results |
current gate state |
close_metadata |
who closed an issue, whether policy was bypassed, which gates fired |
capacity_occupancy |
capacity/exemption records |
Preserved (because the export carries them): issues, comments, labels,
dependencies, export_hashes, child_counters, blocked_issues_cache.
No issue content is lost. What is lost is the entire provenance layer — the record of how
issues reached their current state.
Reproduction
mkdir -p /tmp/br-repro && cd /tmp/br-repro
br init --prefix repro
br q "first" # -> repro-1lc
br q "second" # -> repro-nbm
br update repro-1lc --status=in_progress --actor a
br update repro-1lc --status=in_review --actor a
# before
sqlite3 .beads/*.db 'select count(*) from events;' # 4
sqlite3 .beads/*.db 'select count(*) from capacity_occupancy;' # 2
# induce a hard integrity fault (the trigger condition)
python3 - <<'EOF'
import glob
p = glob.glob('.beads/*.db')[0]
with open(p, 'r+b') as f:
f.seek(4096); f.write(b'\xde\xad\xbe\xef' * 512)
EOF
br doctor --repair
Observed output:
ERROR sqlite.integrity_check: database disk image is malformed:
index `sqlite_autoindex_issues_1` root: page 2 header invalid
ERROR db.write_probe: Failed to select probe issue
Repairing: rebuilding DB from JSONL...
Repair complete: imported 1, skipped 0
After:
integrity_check ok
events 0 (was 4)
capacity_occupancy 0 (was 2)
issues 1 (was 2)
Two distinct problems
1. History tables are silently dropped. The JSONL export schema contains 31 issue-level
keys and no history tables — verified exhaustively across a 2,713-record export. Nothing in
the repair output says history was discarded; the run reports Repair complete and the
database then passes integrity_check.
2. The JSONL is treated as authoritative for issues too. In the repro the database held 2
issues and the export held 1, and the rebuild produced 1. Any issue present in the database
but not yet exported is destroyed by a repair.
Trigger conditions (narrowed by experiment)
| state |
result |
clean database + --repair |
No errors detected; nothing to repair — no rebuild |
| database/JSONL count mismatch |
WARN counts.db_vs_jsonl — no rebuild |
| corrupt database |
ERROR sqlite.integrity_check → rebuild fires |
So this only triggers on a hard integrity failure — but that is exactly the situation where a
user runs --repair, and exactly when the history is least replaceable.
Where it happens
rebuild_database_family() in src/config/mod.rs calls
import_from_jsonl_snapshot_into_fresh_replacement(). The subsequent
VACUUM / REINDEX / VACUUM INTO steps are cosmetic (free-space accounting, per the
comments referencing issues #237, #246, #248) and are not the cause of the loss.
br doctor --help does state --repair "rebuilds DB from JSONL", so the mechanism is
documented — but the consequence for history tables is not, and the success output does not
mention it.
Impact observed in practice
On one active tracker, successive repairs reduced the event history to zero. Recovery
snapshots left in .br_recovery/ show each repair starting a fresh window:
snapshot A 20,506 events 616 gate results 354 close records (5 months of history)
snapshot B 5,441 events 256 87
snapshot C 750 events 8 2
live 0 events 0 0
Each window begins within minutes of the previous one ending — i.e. each repair zeroes the
table and it refills from that instant. The apparent decay is not progressive loss; every
repair has always discarded everything.
Across a fleet of ~39 trackers, 11 currently show the signature (near-zero events with recent
oldest-event timestamps). Eight of those have no recovery snapshot retaining the history.
Suggested directions
- Warn loudly. If the repair will discard non-exported tables, say so before and after,
with row counts. A silent success is the core problem.
- Carry history in the export, or write a sidecar during repair so it can be reattached.
- Preserve the pre-repair database under a name that is not rotated, and say where it is.
.br_recovery/ snapshots exist but are reused, so a second repair can age out the only
copy of the history.
- Consider a narrower recovery for integrity faults that does not require a full
re-import — analogous to the existing --repair-indexes.
This is the third defect of the same shape in br doctor
Two earlier reports, both since closed, describe the same underlying problem — the
diagnostic's output does not track reality, in both directions:
br doctor reported OK schema.tables while three tables were missing. A migration set
PRAGMA user_version = 17 without creating capacity_occupancy, capacity_exemptions,
capacity_exemption_history and five indexes; doctor (0.2.19) reported OK throughout
because it trusted the version pragma rather than checking the tables. Reports OK when
something is wrong.
br doctor --repair reported a false failure after a successful rebuild. It rebuilt the
database from issues.jsonl, then exited non-zero on ERROR db.sidecars: WAL sidecar exists without a matching SHM sidecar, though the rebuilt database was fine. Reports failure when
nothing is wrong.
- This report:
--repair reports success while destroying five tables. Reports success
when something is very wrong.
Note the first of those concerns capacity_occupancy — one of the tables this report shows
being silently emptied. The two issues meet on the same table from opposite directions.
The narrow fix is a warning on the repair path. The broader one is that doctor's verdicts
are derived from proxies — a version pragma, a sidecar file, an exit code — rather than from
the state they claim to describe. Each individual fix leaves that intact.
Version: br 0.5.7
Summary
br doctor --repairrebuilds the database from the JSONL export. The export carriesissue state only, so every append-only history table is emptied by the repair and the
resulting database reports
integrity_check: ok— the operation looks like a success.Lost on every repair:
eventsgate_result_historygate_resultsclose_metadatacapacity_occupancyPreserved (because the export carries them):
issues,comments,labels,dependencies,export_hashes,child_counters,blocked_issues_cache.No issue content is lost. What is lost is the entire provenance layer — the record of how
issues reached their current state.
Reproduction
Observed output:
After:
Two distinct problems
1. History tables are silently dropped. The JSONL export schema contains 31 issue-level
keys and no history tables — verified exhaustively across a 2,713-record export. Nothing in
the repair output says history was discarded; the run reports
Repair completeand thedatabase then passes
integrity_check.2. The JSONL is treated as authoritative for issues too. In the repro the database held 2
issues and the export held 1, and the rebuild produced 1. Any issue present in the database
but not yet exported is destroyed by a repair.
Trigger conditions (narrowed by experiment)
--repairNo errors detected; nothing to repair— no rebuildWARN counts.db_vs_jsonl— no rebuildERROR sqlite.integrity_check→ rebuild firesSo this only triggers on a hard integrity failure — but that is exactly the situation where a
user runs
--repair, and exactly when the history is least replaceable.Where it happens
rebuild_database_family()insrc/config/mod.rscallsimport_from_jsonl_snapshot_into_fresh_replacement(). The subsequentVACUUM/REINDEX/VACUUM INTOsteps are cosmetic (free-space accounting, per thecomments referencing issues #237, #246, #248) and are not the cause of the loss.
br doctor --helpdoes state--repair"rebuilds DB from JSONL", so the mechanism isdocumented — but the consequence for history tables is not, and the success output does not
mention it.
Impact observed in practice
On one active tracker, successive repairs reduced the event history to zero. Recovery
snapshots left in
.br_recovery/show each repair starting a fresh window:Each window begins within minutes of the previous one ending — i.e. each repair zeroes the
table and it refills from that instant. The apparent decay is not progressive loss; every
repair has always discarded everything.
Across a fleet of ~39 trackers, 11 currently show the signature (near-zero events with recent
oldest-event timestamps). Eight of those have no recovery snapshot retaining the history.
Suggested directions
with row counts. A silent success is the core problem.
.br_recovery/snapshots exist but are reused, so a second repair can age out the onlycopy of the history.
re-import — analogous to the existing
--repair-indexes.This is the third defect of the same shape in
br doctorTwo earlier reports, both since closed, describe the same underlying problem — the
diagnostic's output does not track reality, in both directions:
br doctorreportedOK schema.tableswhile three tables were missing. A migration setPRAGMA user_version = 17without creatingcapacity_occupancy,capacity_exemptions,capacity_exemption_historyand five indexes;doctor(0.2.19) reported OK throughoutbecause it trusted the version pragma rather than checking the tables. Reports OK when
something is wrong.
br doctor --repairreported a false failure after a successful rebuild. It rebuilt thedatabase from
issues.jsonl, then exited non-zero onERROR db.sidecars: WAL sidecar exists without a matching SHM sidecar, though the rebuilt database was fine. Reports failure whennothing is wrong.
--repairreports success while destroying five tables. Reports successwhen something is very wrong.
Note the first of those concerns
capacity_occupancy— one of the tables this report showsbeing silently emptied. The two issues meet on the same table from opposite directions.
The narrow fix is a warning on the repair path. The broader one is that
doctor's verdictsare derived from proxies — a version pragma, a sidecar file, an exit code — rather than from
the state they claim to describe. Each individual fix leaves that intact.