{"record":{"id":"4116d6860e0e7ec7","repo":"Humanizr/Humanizer","slug":"locale-inheritance-cycle-detected-at-localecode","errorCode":null,"errorMessage":"Locale inheritance cycle detected at '{localeCode}'.","messagePattern":"Locale inheritance cycle detected at '(.+?)'\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"critical","filePath":"src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs","lineNumber":457,"sourceCode":"\n        static ResolvedLocaleDefinition ResolveLocale(\n            string localeCode,\n            Dictionary<string, LocaleDefinition> parsedLocales,\n            HashSet<string> resolving,\n            ImmutableArray<ResolvedLocaleDefinition>.Builder cache,\n            ImmutableArray<Diagnostic>.Builder diagnostics)\n        {\n            if (cache.FirstOrDefault(cached => cached.LocaleCode == localeCode) is { } cached)\n                return cached;\n\n            if (!parsedLocales.TryGetValue(localeCode, out var locale))\n            {\n                throw new InvalidOperationException($\"Locale '{localeCode}' is not defined.\");\n            }\n\n            if (!resolving.Add(localeCode))\n            {\n                throw new InvalidOperationException($\"Locale inheritance cycle detected at '{localeCode}'.\");\n            }\n\n            ResolvedLocaleDefinition inherited;\n            if (string.IsNullOrWhiteSpace(locale.Inherits))\n            {\n                inherited = ResolvedLocaleDefinition.Empty(localeCode);\n            }\n            else\n            {\n                var inheritedLocale = locale.Inherits!;\n                if (!parsedLocales.ContainsKey(inheritedLocale))\n                {\n                    diagnostics.Add(Diagnostic.Create(\n                        HumanizerSourceGenerator.Diagnostics.InvalidLocaleDefinition,\n                        Location.None,\n                        localeCode,\n                        $\"Inherited locale '{inheritedLocale}' is not defined.\"));\n                    inherited = ResolvedLocaleDefinition.Empty(localeCode);","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer.SourceGenerators/Common/LocaleYamlCatalog.cs#L439-L475","documentation":"During locale resolution the source generator walks each locale's 'inherits' chain recursively, tracking the current path in a HashSet. If it re-enters a locale already on the resolution stack, a circular inheritance graph exists and the generator aborts with a hard build failure rather than producing infinite recursion or incorrect merged data.","triggerScenarios":"Two or more locale YAML files form a cycle via their 'inherits:' field. For example 'sr-Latn.yml' declares 'inherits: sr' while 'sr.yml' declares 'inherits: sr-Latn'. A self-referential 'inherits: <own-code>' also triggers it.","commonSituations":"Refactoring or splitting locale files and rewiring inherits pointers; renaming a locale tag without updating dependents; accidentally setting a parent locale to inherit from a child variant.","solutions":["Trace the inherits chain starting from the locale code named in the message until you find the node that points back into the chain.","Break the cycle by removing or correcting the 'inherits:' field on the offending locale so the graph becomes a DAG rooted at a language-only locale (e.g. 'en', 'fr', 'de').","Run a quick grep of all 'inherits:' values to verify no locale is its own ancestor."],"exampleFix":"# before — cycle:\n# fr-CA.yml\ninherits: fr-CH\n# fr-CH.yml\ninherits: fr-CA\n\n# after — DAG:\n# fr-CA.yml\ninherits: fr\n# fr-CH.yml\ninherits: fr","handlingStrategy":"validation","validationCode":"# Before building, verify no inherits cycle exists in all locale YAML files.\n# Run as a pre-commit or CI check (PowerShell or bash + yq):\n#\n# Collect every locale file and build the inherits graph, then detect cycles.\nimport pathlib, re\nlocales_dir = pathlib.Path('src/Humanizer/Locales')\ngraph = {}\nfor f in locales_dir.rglob('*.yml'):\n    text = f.read_text()\n    code = f.stem  # or parse 'locale:' key\n    m = re.search(r'^inherits:\\s*(\\S+)', text, re.MULTILINE)\n    graph[code] = m.group(1) if m else None\n\ndef has_cycle(node, graph, seen):\n    if node in seen:\n        return True\n    if node is None or node not in graph:\n        return False\n    seen.add(node)\n    return has_cycle(graph[node], graph, seen)\n\nfor code in graph:\n    if has_cycle(code, graph, set()):\n        print(f'CYCLE detected involving {code}')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat locale inheritance as a DAG rooted at language-only locales (en, fr, de) and never point a root back to a variant.","Add a CI step that builds the inherits graph and rejects cycles before the source generator runs.","When renaming or splitting a locale, grep all 'inherits:' references to the old code and update them."],"tags":["locale","yaml","source-generator","inheritance","cycle"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}