{"record":{"id":"3225537946e267d7","repo":"astral-sh/ruff","slug":"circular-configuration-detected-chain","errorCode":null,"errorMessage":"Circular configuration detected: {chain}","messagePattern":"Circular configuration detected: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/ruff_workspace/src/resolver.rs","lineNumber":361,"sourceCode":"    initial_config_path: &Path,\n    transformer: &dyn ConfigurationTransformer,\n    origin: ConfigurationOrigin,\n) -> Result<Configuration> {\n    resolve_configuration_with_cache(initial_config_path, transformer, origin, None)\n}\n\nfn resolve_configuration_with_cache(\n    initial_config_path: &Path,\n    transformer: &dyn ConfigurationTransformer,\n    origin: ConfigurationOrigin,\n    configuration_cache: Option<&ConfigurationCache>,\n) -> Result<Configuration> {\n    let relativity = Relativity::from(origin);\n    let mut configurations = indexmap::IndexMap::new();\n    let mut next = Some(fs::normalize_path(initial_config_path));\n    while let Some(path) = next {\n        if configurations.contains_key(&path) {\n            bail!(format!(\n                \"Circular configuration detected: {chain}\",\n                chain = configurations\n                    .keys()\n                    .chain([&path])\n                    .map(|p| format!(\"`{}`\", p.display()))\n                    .join(\" extends \"),\n            ));\n        }\n\n        let project_root = relativity.resolve(&path);\n        let load = || {\n            let options = pyproject::load_options(&path).with_context(|| {\n                if configurations.is_empty() {\n                    format!(\n                        \"Failed to load configuration `{path}`\",\n                        path = path.display()\n                    )\n                } else {","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/astral-sh/ruff/blob/26f38c119cac42e4d320ba08f09224fdec74af2c/crates/ruff_workspace/src/resolver.rs#L343-L379","documentation":"Ruff resolves configuration by following `extends` chains from an initial config path. If walking the chain revisits a configuration file already processed, extending is circular and cannot converge, so resolution bails with the visited chain (paths joined by ' extends ').","triggerScenarios":"Configuration A extends B and B (directly or transitively) extends A; a config file extends itself; symlinked or normalized paths (`fs::normalize_path`) that loop back to an ancestor config.","commonSituations":"Shared team configs that extend each other for convenience; monorepo setups where a base config accidentally re-extends a child config; symlink loops between config directories.","solutions":["Inspect the printed chain and remove the `extends` entry that closes the loop","Move shared settings into a single base config that all others extend (a DAG, not cycles)","Resolve symlink loops between config files, then re-run Ruff"],"exampleFix":"# before (base.toml extends a.toml, a.toml extends base.toml)\n# a.toml\n[extends]\npath = \"base.toml\"\n\n# after: only the child extends the base\n# a.toml — remove the extends block referencing base.toml's ancestor chain\n[lint]\nselect = [\"E\", \"F\"]","handlingStrategy":"validation","validationCode":"import tomllib\nfrom pathlib import Path\n\ndef check_no_cycles(path: Path, seen=None) -> None:\n    seen = seen or set()\n    p = path.resolve()\n    if p in seen:\n        raise SystemExit(f\"Circular configuration detected at {p}\")\n    cfg = tomllib.loads(p.read_text())\n    seen.add(p)\n    ext = cfg.get(\"extends\")\n    if isinstance(ext, str):\n        check_no_cycles((p.parent / ext).resolve(), seen)","typeGuard":null,"tryCatchPattern":"import subprocess\np = subprocess.run([\"ruff\", \"check\", \".\"], capture_output=True, text=True)\nif p.returncode != 0 and \"Circular configuration detected\" in p.stderr:\n    fix_extends_chain(); retry()","preventionTips":["Keep the extends graph acyclic: shared settings go in one base config that children extend","Never let a base config extend a config that (transitively) extends it","Check for symlink loops when configs live in linked directories","Run `ruff check` locally after editing any extends entry"],"tags":["configuration","extends","recursion"],"backgroundTag":"circular-configuration","analyzedSha":"26f38c119cac42e4d320ba08f09224fdec74af2c","analyzedAt":"2026-09-05T10:32:37.492Z","contentChangedAt":"2026-09-05T10:32:37.492Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}