jdx/mise · error

brew-cask:{}: dependency cycle detected

Error message

brew-cask:{}: dependency cycle detected

What it means

While recursively installing a cask's dependency chain, Homebrew tracks ancestor cask tokens; if a cask appears in its own ancestor set the chain is cyclic and installation is aborted. This prevents infinite recursion when cask `depends_on cask:` declarations form a loop.

Source

Thrown at src/system/packages/brew/cask/mod.rs:555

        self.install_one_with_ancestors(req, opts, pr, &BTreeSet::new(), manager_options, mode)
            .await
    }

    /// Installs a cask while detecting dependency cycles and preserving ownership.
    /// Dependencies use install mode. The requested cask's eligibility is checked
    /// before staging and again under the installation lock before replacement.
    async fn install_one_with_ancestors(
        &self,
        req: &PackageRequest,
        opts: &InstallOpts,
        pr: Option<&dyn SingleReport>,
        ancestors: &BTreeSet<String>,
        manager_options: &ManagerPackageOptions,
        mode: InstallMode,
    ) -> Result<String> {
        let cask = fetch_cask(req, !opts.dry_run).await?;
        if ancestors.contains(&cask.token) {
            bail!("brew-cask:{}: dependency cycle detected", cask.token);
        }
        let mut ancestors = ancestors.clone();
        ancestors.insert(cask.token.clone());
        if let Some(version) = homebrew_installed_version(&cask.token)? {
            info!(
                "brew-cask:{}: installed and managed by Homebrew; leaving unchanged",
                cask.token
            );
            return Ok(version);
        }
        let artifacts = cask_artifacts(&cask)?;
        validate_platform_support(&cask, &artifacts)?;
        let installed_version = mise_installed_cask_version(&cask)?;
        if let Some(reason) =
            installed_skip_reason(&cask, &artifacts, installed_version.as_deref(), mode)?
        {
            info!("brew-cask:{}: {reason}", cask.token);
            return Ok(reason.to_string());

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Inspect the cask's `depends_on cask:` declarations to find the cycle
  2. Fix the tap/cask by removing the erroneous dependency edge
  3. Install the problematic cask directly if its dependency is spurious
  4. Report the cycle to the tap maintainer if it's a third-party cask

Example fix

// before (cask B)
depends_on cask: "a"   # while cask A depends_on cask: "b"
// after (cask B)
# remove the circular depends_on line
Defensive patterns

Strategy: validation

Validate before calling

fn cycle_check(token: &str, ancestors: &BTreeSet<String>) -> Result<()> {
    if ancestors.contains(token) {
        Err(format!("dependency cycle at {token}"))
    } else { Ok(()) }
}

Try / catch

match install_cask(req).await {
    Err(e) if e.to_string().contains("dependency cycle detected") => {
        eprintln!("inspect depends_on cask: chain for {}", req.name);
        Err(e)
    }
    r => r,
}

Prevention

When it happens

Trigger: install_one (or the recursive dependency walker) encounters a cask whose token is already present in the `ancestors` BTreeSet — e.g. cask A depends on B and B depends on A, or a self-dependency.

Common situations: Third-party taps with mis-declared `depends_on cask:` stanzas; newly edited casks introducing a cycle; users installing two casks whose dependency sets reference each other.

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@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/25614116e268aa55. Report an issue: GitHub.