jj-vcs/jj · critical

Conflicting factory definitions for '{}' factory

Error message

Conflicting factory definitions for '{}' factory

What it means

This panic is thrown by merge_factories_map in lib/src/repo.rs when merging two StoreFactories maps (base and ext) and an extension tries to register a store factory under a name that already exists in the base map. Jujutsu (jj) uses per-backend store factories keyed by backend name, and each name must be unique so the correct backend can be resolved. A duplicate registration means two extensions (or an extension plus a built-in) claim the same backend name, which would make resolution ambiguous, so the library aborts immediately rather than picking one silently.

Source

Thrown at lib/src/repo.rs:428

    Box<dyn Fn(&UserSettings, &Path) -> Result<Box<dyn Backend>, BackendLoadError>>;
type OpStoreFactory = Box<
    dyn Fn(&UserSettings, &Path, RootOperationData) -> Result<Box<dyn OpStore>, BackendLoadError>,
>;
type OpHeadsStoreFactory =
    Box<dyn Fn(&UserSettings, &Path) -> Result<Box<dyn OpHeadsStore>, BackendLoadError>>;
type IndexStoreFactory =
    Box<dyn Fn(&UserSettings, &Path) -> Result<Box<dyn IndexStore>, BackendLoadError>>;
type SubmoduleStoreFactory =
    Box<dyn Fn(&UserSettings, &Path) -> Result<Box<dyn SubmoduleStore>, BackendLoadError>>;

pub fn merge_factories_map<F>(base: &mut HashMap<String, F>, ext: HashMap<String, F>) {
    for (name, factory) in ext {
        match base.entry(name) {
            Entry::Vacant(v) => {
                v.insert(factory);
            }
            Entry::Occupied(o) => {
                panic!("Conflicting factory definitions for '{}' factory", o.key())
            }
        }
    }
}

pub struct StoreFactories {
    backend_factories: HashMap<String, BackendFactory>,
    op_store_factories: HashMap<String, OpStoreFactory>,
    op_heads_store_factories: HashMap<String, OpHeadsStoreFactory>,
    index_store_factories: HashMap<String, IndexStoreFactory>,
    submodule_store_factories: HashMap<String, SubmoduleStoreFactory>,
}

#[derive(Debug, Error)]
pub enum StoreLoadError {
    #[error("Unsupported {store} backend type '{store_type}'")]
    UnsupportedType {
        store: &'static str,

View on GitHub (pinned to 6631dbd4a8)

Solutions

  1. Find and remove the duplicate registration: check whether the factory name is already present before adding (e.g. build StoreFactories::default() and inspect its keys instead of re-adding built-ins).
  2. If you merged two factory maps with merge/merge_factories_map, ensure each map is populated from disjoint sources, or filter ext entries to only names not in base.
  3. When writing an extension, give it a unique backend name rather than reusing 'git', 'local', etc.
  4. Upgrade/downgrade to matching jj versions if defaults changed to include a factory your code also registers.

Example fix

// before
let mut factories = StoreFactories::default();
add_git_factory(&mut factories); // 'git' already added by default

// after
let mut factories = StoreFactories::default();
if !factories.contains(&git_backend_name) {
    add_git_factory(&mut factories);
}
Defensive patterns

Strategy: validation

Validate before calling

// Before merging/adding factories, check for collisions:
fn merge_no_conflict(base: &mut StoreFactories, ext: StoreFactories) {
    for name in ext.keys() {
        assert!(!base.contains(name), "factory '{}' already registered", name);
    }
    base.merge(ext);
}

Try / catch

// Panics cannot be caught reliably in Rust; validate before merging instead (see validationCode). std::panic::catch_unwind is a last resort only.
let result = std::panic::catch_unwind(|| factories.merge(ext));
if result.is_err() { /* rebuild factories and report config error */ }

Prevention

When it happens

Trigger: Calling RepoEnv/AddBackend-style extension methods (add_working_copy_factories, or the higher-level merge/add-extension APIs that funnel into merge_factories_map) twice with the same backend name, e.g. registering the 'git' or 'local' store factory once via built-in defaults and again via a loaded extension. It also fires when merging two StoreFactories maps (merge) that both contain an entry for the same key.

Common situations: Re-initializing a RepoEnv/StoreFactories and re-adding default factories before adding custom ones; loading two jj extensions that both register a backend with the same name; a version change where defaults started including a factory (e.g. the git backend) that user code also registers manually; copy-pasting setup code so the same add_* call runs twice.

Related errors


AI-assisted analysis of jj-vcs/jj@6631dbd4a8 (2026-08-28). Data as JSON: /api/errors/626293191bb2ccb8. Report an issue: GitHub.