Summary
bmo import reads only 2 of the 6 collections in an ExportBundle, and does no
ID remapping. As a result bmo export → bmo import is not a round trip: all
dependency links are silently lost, and importing into a non-empty database
attaches parents and comments to unrelated issues with no error or warning.
Both behaviours are long-standing — import_from_bmo is unchanged — but they are
now more prominent, because the docket importer removed in #15 was the only path
that built an old-id → new-id map and imported relations.
1. Relations, activity, and top-level labels/files are dropped
ExportBundle (src/model/export.rs) declares six collections. import_from_bmo
(src/cli/import.rs) consumes bundle.issues and bundle.comments only:
| Bundle field |
Imported? |
issues |
yes |
comments |
yes (but see §2) |
relations |
no — silently dropped |
activity |
no — silently dropped |
labels (top-level) |
no (per-issue issue.labels do survive) |
files (top-level) |
no (per-issue issue.files do survive) |
Reproduction
bmo init
bmo create --title Parent # BMO-1
bmo create --title Child --parent BMO-1
bmo create --title Blocker # BMO-3
bmo link add BMO-3 blocks BMO-1
bmo export > exp.json # relations: [{from_id: 3, to_id: 1, kind: blocks}]
cd ../fresh && bmo init
bmo import ../exp.json # "Imported 3 issue(s) and 1 comment(s)"
bmo link list BMO-1 # => "No relations." <-- edge lost
2. No ID remapping: parents and comments reattach to the wrong issues
repo.create_issue assigns fresh autoincrement IDs, but parent_id and
comment.issue_id are written straight from the exported IDs. On an empty
database with a contiguous export this coincidentally lines up. On a non-empty
database it silently misattaches.
Reproduction
bmo init
bmo create --title "Pre-existing A" # BMO-1
bmo create --title "Pre-existing B" # BMO-2
bmo import exp.json # export's ids are 1,2,3
bmo list --all
Observed:
BMO-1 parent=None Pre-existing A
BMO-2 parent=None Pre-existing B
BMO-3 parent=None Parent <- the real parent landed here
BMO-4 parent=1 Child <- but parent_id=1 points at "Pre-existing A"
BMO-5 parent=None Blocker
Child's parent is now an unrelated pre-existing issue. Likewise the comment
exported against issue_id: 1 ("Parent") attached to "Pre-existing A":
$ bmo show BMO-1
Comments (1):
[2026-09-03] unknown: a comment
Exit code is 0 and the message reads Imported 3 issue(s) and 1 comment(s).
Nothing signals that anything went wrong.
Impact
bmo export is not a usable backup: restoring it loses the entire dependency
graph, which is what bmo plan, bmo next, and bmo graph are built on.
- Importing into a non-empty database corrupts it silently — wrong parents, and
comments on unrelated issues. There is no guard against this.
Existing test gives false confidence
export_import_roundtrip in tests/import_export.rs passes today, but asserts
only issue count and titles. It never checks relations, parent_id, or
comment attachment — precisely the parts that are broken.
Suggested fix
- Build an
old_id -> new_id: HashMap<i64, i64> as issues are created.
- Resolve
parent_id through it. Because a child can be created before its
parent, either import parents first (topologically) or do a second pass that
sets parent_id once every issue exists.
- Resolve
comment.issue_id through it, and skip comments whose issue is absent.
- Import
bundle.relations, mapping both endpoints through it and skipping any
edge with an unresolvable endpoint. The removed docket importer did exactly
this — see import_from_docket at 746d209:src/cli/import.rs for a working
reference implementation.
- Decide explicitly whether
bundle.activity should replay or stay excluded,
and document it either way.
- Report skipped rows via the
warnings field the --json envelope already
carries (currently always empty).
- Strengthen
export_import_roundtrip to assert relations, parent links, and
comment attachment, and add a case that imports into a non-empty database.
Environment
bmo 0.7.1, macOS (darwin 25.6.0), SQLite backend.
Summary
bmo importreads only 2 of the 6 collections in anExportBundle, and does noID remapping. As a result
bmo export→bmo importis not a round trip: alldependency links are silently lost, and importing into a non-empty database
attaches parents and comments to unrelated issues with no error or warning.
Both behaviours are long-standing —
import_from_bmois unchanged — but they arenow more prominent, because the docket importer removed in #15 was the only path
that built an old-id → new-id map and imported relations.
1. Relations, activity, and top-level labels/files are dropped
ExportBundle(src/model/export.rs) declares six collections.import_from_bmo(
src/cli/import.rs) consumesbundle.issuesandbundle.commentsonly:issuescommentsrelationsactivitylabels(top-level)issue.labelsdo survive)files(top-level)issue.filesdo survive)Reproduction
2. No ID remapping: parents and comments reattach to the wrong issues
repo.create_issueassigns fresh autoincrement IDs, butparent_idandcomment.issue_idare written straight from the exported IDs. On an emptydatabase with a contiguous export this coincidentally lines up. On a non-empty
database it silently misattaches.
Reproduction
Observed:
Child's parent is now an unrelated pre-existing issue. Likewise the commentexported against
issue_id: 1("Parent") attached to "Pre-existing A":Exit code is 0 and the message reads
Imported 3 issue(s) and 1 comment(s).Nothing signals that anything went wrong.
Impact
bmo exportis not a usable backup: restoring it loses the entire dependencygraph, which is what
bmo plan,bmo next, andbmo graphare built on.comments on unrelated issues. There is no guard against this.
Existing test gives false confidence
export_import_roundtripintests/import_export.rspasses today, but assertsonly issue count and titles. It never checks relations,
parent_id, orcomment attachment — precisely the parts that are broken.
Suggested fix
old_id -> new_id: HashMap<i64, i64>as issues are created.parent_idthrough it. Because a child can be created before itsparent, either import parents first (topologically) or do a second pass that
sets
parent_idonce every issue exists.comment.issue_idthrough it, and skip comments whose issue is absent.bundle.relations, mapping both endpoints through it and skipping anyedge with an unresolvable endpoint. The removed docket importer did exactly
this — see
import_from_docketat746d209:src/cli/import.rsfor a workingreference implementation.
bundle.activityshould replay or stay excluded,and document it either way.
warningsfield the--jsonenvelope alreadycarries (currently always empty).
export_import_roundtripto assert relations, parent links, andcomment attachment, and add a case that imports into a non-empty database.
Environment
bmo
0.7.1, macOS (darwin 25.6.0), SQLite backend.