jdx/mise · error

bootstrap resource dependency cycle

Error message

bootstrap resource dependency cycle: {cycle}

What it means

During bootstrap planning, the provisioner topologically sorts declared resources by their dependencies. If the sort yields fewer resources than were declared, a dependency cycle exists among the resources listed in the message. The library refuses to order (and therefore provision) a plan it can never execute.

Solutions

  1. Inspect the resource IDs listed in the error message and find the mutual/transitive dependency among them
  2. Remove or reverse one dependency edge so the graph becomes a DAG
  3. Split the cyclic group into independently ordered resources with a one-directional dependency
  4. Ensure explicit_dependencies only reference resources that cannot reference back

Example fix

// before
dir_a.depends_on("dir:b");
dir_b.depends_on("dir:a");
// after
dir_b.depends_on("dir:a"); // one direction only
Defensive patterns

Strategy: validation

Validate before calling

const ids = new Set(resources.map(r => r.id));
for (const r of resources) for (const d of r.depends_on) if (ids.has(d)) checkCycle(resources, r.id, d); // DFS for back-edges before calling the planner

Type guard

function isAcyclic(res) { const seen = new Set(), stack = new Set(); function visit(id) { if (stack.has(id)) return false; if (seen.has(id)) return true; stack.add(id); const deps = res.find(r => r.id === id)?.depends_on ?? []; for (const d of deps) if (ids.has(d) && !visit(d)) return false; stack.delete(id); seen.add(id); return true; } return res.every(r => visit(r.id)); }

Prevention

When it happens

Trigger: Calling the bootstrap plan/ordered ordering code (reached via plan -> output) when two or more declared resources (directories, files, users, groups, compose projects) depend on each other, directly or transitively, e.g. directory A depends on B and B depends on A.

Common situations: Hand-written resource manifests where a directory declares a parent that itself declares a dependency back on the child; copy-pasted resource blocks with swapped dependency directions; a compose project and a directory each listing the other in explicit_dependencies.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/48e8b1fab11cfb9a. Report an issue: GitHub.

Appendix: source

Thrown at src/system/resources.rs:317

                for dependent in dependents {
                    let count = incoming
                        .get_mut(dependent)
                        .expect("dependent resource is present");
                    *count -= 1;
                    if *count == 0 {
                        ready.push_back(dependent.clone());
                    }
                }
            }
        }

        if ordered.len() != self.resources.len() {
            let cycle = incoming
                .into_iter()
                .filter_map(|(id, count)| (count > 0).then_some(id.to_string()))
                .collect::<Vec<_>>()
                .join(", ");
            bail!("bootstrap resource dependency cycle: {cycle}");
        }
        Ok(ordered)
    }
}

/// Build the resource plan currently supported by the provisioning engine.
/// Other bootstrap sections will move into this graph as resource adapters land.
pub(crate) async fn plan(
    config: &Config,
    secrets: &super::secrets::SecretValues,
) -> Result<BootstrapPlan> {
    let mut plan = BootstrapPlan::default();
    let accounts = super::accounts::prepare_requests_from_config(config)?;
    let group_states = accounts
        .groups
        .iter()
        .map(|group| (group.name.clone(), group.state))
        .collect::<HashMap<_, _>>();

View on GitHub (pinned to 533346cc37)