jdx/mise · error

brew-cask: : dependency cycle detected

Error message

brew-cask:{}: dependency cycle detected

What it means

install_one_with_ancestors (src/system/packages/brew/cask.rs:430) walks a cask's dependency closure carrying the set of ancestor tokens. If the cask currently being resolved (by its API token field) is already present in `ancestors`, the recursion would never terminate, so mise aborts with a cycle error naming that token before any staging or copying.

Solutions

  1. Clear cached brew metadata (mise cache for system-brew) so fresh cask JSON is fetched, in case stale tokens created a phantom cycle
  2. Inspect the involved casks' depends_on stanzas in the tap's api/*.json and report/fix the cycle upstream
  3. If you maintain the tap, remove or break the cyclic dependency stanza
  4. Install the cask with the Homebrew CLI as a stopgap, since brew's resolver may tolerate the graph mise refuses
Defensive patterns

Strategy: validation

Validate before calling

fn has_cycle(adj: &std::collections::HashMap<String, Vec<String>>, node: &str, path: &mut Vec<String>) -> bool {
    if path.iter().any(|n| n == node) { return true; }
    path.push(node.to_string());
    let cyclic = adj.get(node).into_iter().flatten().any(|d| has_cycle(adj, d, path));
    path.pop();
    cyclic
}

Prevention

When it happens

Trigger: A tapped cask whose dependency closure revisits itself: cask A depends on cask B and B back on A, or a tap's api/cask/<token>.json declares a `token` value that collides with an ancestor because the tap re-uses or renames tokens across cask files.

Common situations: Third-party taps with hand-written cask JSON containing mutually-referencing casks; taps where two casks share the same token under different filenames; stale cached metadata after an upstream cask rename mid-install.

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/2b44893c2de501f5. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/cask.rs:430

        opts: &InstallOpts,
        pr: Option<&dyn SingleReport>,
        manager_options: &ManagerPackageOptions,
    ) -> Result<String> {
        self.install_one_with_ancestors(req, opts, pr, &BTreeSet::new(), manager_options)
            .await
    }

    async fn install_one_with_ancestors(
        &self,
        req: &PackageRequest,
        opts: &InstallOpts,
        pr: Option<&dyn SingleReport>,
        ancestors: &BTreeSet<String>,
        manager_options: &ManagerPackageOptions,
    ) -> Result<String> {
        let cask = fetch_cask(req).await?;
        if ancestors.contains(&cask.token) {
            bail!("brew-cask:{}: dependency cycle detected", cask.token);
        }
        let mut ancestors = ancestors.clone();
        ancestors.insert(cask.token.clone());
        let artifacts = cask_artifacts(&cask)?;
        validate_platform_support(&cask, &artifacts)?;
        if homebrew_metadata_present(&cask.token) {
            bail!(
                "brew-cask:{}: Homebrew owns this cask; remove it with Homebrew before installing it with mise",
                cask.token
            );
        }
        let installed_version = installed_cask_version(&cask, &artifacts)?;
        if let Some(version) = installed_version.as_ref()
            && (cask.auto_updates || version == &cask.version)
        {
            info!("brew-cask:{}: already installed", cask.token);
            return Ok(version.clone());
        }

View on GitHub (pinned to 6f52dcdf99)