Skip to content
Latchkey

Cargo "cyclic package dependency" in a Workspace in CI

Two crates depend on each other through their normal (build) dependencies, forming a cycle cargo cannot order. Cargo allows cycles only through dev-dependencies, so a build-dependency cycle is rejected.

What this error means

Resolution fails with error: cyclic package dependency: package A depends on itself and prints the chain (A -> B -> A). It appears after adding a dependency that closes a loop between workspace crates.

cargo
error: cyclic package dependency: package `a v0.1.0` depends on itself.
Cycle:
package `a v0.1.0 (.../crates/a)`
  ... which is depended on by `b v0.1.0 (.../crates/b)`
  ... which is depended on by `a v0.1.0`

Common causes

Mutual build dependencies

Crate A lists B as a dependency and B lists A. Normal dependencies must form a DAG, so the cycle is rejected.

A dev-dependency promoted to a normal one

A cycle is allowed only via [dev-dependencies]. Moving such an edge into [dependencies] (or [build-dependencies]) closes a forbidden loop.

How to fix it

Break the cycle with a shared crate

Extract the common code into a third crate both depend on, removing the back-edge.

  1. Identify the two crates in the printed cycle.
  2. Move the shared types/logic into a new lower-level crate.
  3. Have both crates depend on the new crate instead of each other.

Keep the back-edge as a dev-dependency

If the reverse edge is only needed for tests, declare it under dev-dependencies, where cycles are permitted.

Cargo.toml
# crates/b/Cargo.toml
[dev-dependencies]
a = { path = "../a" }   # cycle allowed for tests only

How to prevent it

  • Keep workspace crate dependencies a DAG; extract shared crates to avoid loops.
  • Confine unavoidable back-edges to [dev-dependencies].
  • Review the dependency direction before adding a cross-crate dep.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →