{"record":{"id":"a2f6e5130c4653ef","repo":"helix-editor/helix","slug":"cycle-found-in-inheriting","errorCode":null,"errorMessage":"Cycle found in inheriting: {}","messagePattern":"Cycle found in inheriting: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"helix-view/src/theme.rs","lineNumber":245,"sourceCode":"        let mut cycle_found = false; // track if there was a path, but it was in a cycle\n        self.theme_dirs\n            .iter()\n            .find_map(|dir| {\n                let path = dir.join(&filename);\n                if !path.exists() {\n                    None\n                } else if visited_paths.contains(&path) {\n                    // Avoiding cycle, continuing to look in lower priority directories\n                    cycle_found = true;\n                    None\n                } else {\n                    visited_paths.insert(path.clone());\n                    Some(path)\n                }\n            })\n            .ok_or_else(|| {\n                if cycle_found {\n                    anyhow!(\"Cycle found in inheriting: {}\", name)\n                } else {\n                    anyhow!(\"File not found for: {}\", name)\n                }\n            })\n    }\n\n    pub fn default_theme(&self, true_color: bool) -> Theme {\n        if true_color {\n            self.default()\n        } else {\n            self.base16_default()\n        }\n    }\n\n    /// Returns the default theme\n    pub fn default(&self) -> Theme {\n        DEFAULT_THEME.clone()\n    }","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/helix-editor/helix/blob/079a789e8cb08ead67f19e1971a1b7438b37354b/helix-view/src/theme.rs#L227-L263","documentation":"Helix's theme Loader resolves the 'inherits' key recursively: each parent theme name is looked up as <name>.toml across the theme directories (highest priority first), and every visited file path is recorded in a HashSet. When the chain arrives at a theme file whose exact path was already visited, the loader refuses to re-enter it (marking cycle_found), and if no lower-priority directory supplies a fresh file it raises 'Cycle found in inheriting: {name}'. This is a hard stop: the merged theme TOML is never produced and load() returns an Err.","triggerScenarios":"Calling Loader::load / load_with_warnings with a theme whose inheritance chain loops back on itself: (1) a theme file containing inherits pointing to its own name (mytheme.toml with inherits = \"mytheme\") when only one file of that name exists; (2) mutual recursion, A.toml inherits \"B\" and B.toml inherits \"A\"; (3) a longer transitive loop A -> B -> C -> A. Note that inheriting a same-named file in a LOWER-priority themes directory is legal (different path), so the error strictly means the same path was re-entered.","commonSituations":"Users editing custom themes in ~/.config/helix/themes/ who copy a theme file and rename the file but forget to update the inherits key (so it inherits itself); hand-built theme families where a parent/child pair reference each other; refactoring a inherits chain (e.g. base -> common -> base) and introducing a back-edge; shipping a theme that inherits a user's local override of the same name, turning an intended shadowing into a loop.","solutions":["Trace the loop: start at the theme named in the message, read its inherits key, and follow the chain file by file until you return to a file you already saw; that back-edge is the bug.","Fix the self/mutual reference: in the offending toml, change inherits to a real ancestor (usually \"default\") or delete the inherits key entirely if the theme is standalone.","If you intentionally inherit from a same-named theme, make sure the parent file lives in a lower-priority themes directory (e.g. the runtime themes/ dir while your override sits in the user config dir) — same directory shadowing is what closes the loop.","After editing, verify with :theme <name> inside Helix or by launching hx; a successful switch means the chain now terminates.","If you are a library consumer calling Loader::load, validate the inherits graph yourself before loading (see defense section) so a user-authored loop cannot abort startup."],"exampleFix":"# before — ~/.config/helix/themes/mytheme.toml (file is named mytheme.toml)\ninherits = \"mytheme\"\n\n# after\ninherits = \"default\"","handlingStrategy":"validation","validationCode":"// Walk the inherits chain before calling Loader::load,\n// using the same dirs you passed to Loader::new (priority order).\nuse std::collections::HashSet;\nuse std::path::PathBuf;\n\nfn validate_inherits_chain(mut name: &str, dirs: &[PathBuf]) -> Result<(), String> {\n    let theme_dirs: Vec<PathBuf> = dirs.iter().map(|d| d.join(\"themes\")).collect();\n    let mut visited: HashSet<PathBuf> = HashSet::new();\n    loop {\n        if name == \"default\" || name == \"base16_default\" {\n            return Ok(()); // built-ins are served from embedded data\n        }\n        let filename = format!(\"{name}.toml\");\n        let path = theme_dirs\n            .iter()\n            .map(|d| d.join(&filename))\n            .find(|p| p.exists())\n            .ok_or_else(|| format!(\"File not found for: {name}\"))?;\n        if !visited.insert(path.clone()) {\n            return Err(format!(\"Cycle found in inheriting: {name}\"));\n        }\n        let raw = std::fs::read_to_string(&path).map_err(|e| e.to_string())?;\n        let value: toml::Value = toml::from_str(&raw).map_err(|e| e.to_string())?;\n        match value.get(\"inherits\").and_then(|v| v.as_str()) {\n            Some(parent) => name = parent,\n            None => return Ok(()),\n        }\n    }\n}","typeGuard":"fn has_inherits_cycle(dirs: &[PathBuf], name: &str) -> bool {\n    validate_inherits_chain(name, dirs).is_err()\n        && validate_inherits_chain(name, dirs)\n            .unwrap_err()\n            .starts_with(\"Cycle found\")\n}","tryCatchPattern":"// anyhow errors are untyped here, so match on the message prefix:\nmatch loader.load(theme_name) {\n    Ok(theme) => theme,\n    Err(err) if err.to_string().starts_with(\"Cycle found in inheriting\") => {\n        log::warn!(\"theme '{theme_name}' has an inherits loop, using default\");\n        loader.default_theme(true_color)\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Never let a theme's inherits key equal the theme's own file name when only one file of that name exists.","When splitting a theme into parent + child files, keep the parent chain acyclic: draw the graph once (child -> parent) and check no arrow points back.","Keep user overrides in ~/.config/helix/themes/ and treat the runtime themes/ dir as read-only base themes; same-directory shadowing is the main source of accidental loops.","Run :theme <name> after every inherits edit — it exercises the full chain immediately.","If you embed Helix's loader in an app, run validate_inherits_chain on all shipped themes in CI so a loop fails the build, not the user's editor."],"tags":["helix","theme","toml","config","inheritance","cycle-detection"],"backgroundTag":null,"analyzedSha":"079a789e8cb08ead67f19e1971a1b7438b37354b","analyzedAt":"2026-08-16T09:09:59.668Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}