{"record":{"id":"bcff4cde718eb727","repo":"zeroclaw-labs/zeroclaw","slug":"skills-extra-registries-i-name-is-invalid","errorCode":null,"errorMessage":"skills.extra_registries[{i}].name '{}' is invalid; use only lowercase ASCII letters, numbers, '-' or '_' so it can be addressed as registry:<name>/<skill>","messagePattern":"skills\\.extra_registries\\[(.+?)\\]\\.name '(.+?)' is invalid; use only lowercase ASCII letters, numbers, '-' or '_' so it can be addressed as registry:<name>/<skill>","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/schema.rs","lineNumber":21972,"sourceCode":"                && !std::path::Path::new(tpl_dir).exists()\n            {\n                anyhow::bail!(\"project_intel.templates_dir path does not exist: {tpl_dir}\");\n            }\n        }\n\n        // Proxy (delegate to existing validation)\n        self.proxy.validate()?;\n        self.cloud_ops.validate()?;\n\n        // Skills — extra registries\n        {\n            let mut seen = std::collections::HashSet::new();\n            for (i, reg) in self.skills.extra_registries.iter().enumerate() {\n                if reg.name.trim().is_empty() {\n                    anyhow::bail!(\"skills.extra_registries[{i}].name must not be empty\");\n                }\n                if !ExternalRegistry::is_valid_name(&reg.name) {\n                    anyhow::bail!(\n                        \"skills.extra_registries[{i}].name '{}' is invalid; use only lowercase ASCII letters, numbers, '-' or '_' so it can be addressed as registry:<name>/<skill>\",\n                        reg.name\n                    );\n                }\n                if !seen.insert(reg.name.as_str()) {\n                    anyhow::bail!(\"skills.extra_registries has duplicate name '{}'\", reg.name);\n                }\n                if reg.url.trim().is_empty() {\n                    anyhow::bail!(\n                        \"skills.extra_registries[{}].url must not be empty\",\n                        reg.name\n                    );\n                }\n                if reg.kind != ExternalRegistryKind::Git {\n                    anyhow::bail!(\n                        \"skills.extra_registries[{}].kind must be 'git' (got '{}'); other protocols are not yet supported\",\n                        reg.name,\n                        reg.kind","sourceCodeStart":21954,"sourceCodeEnd":21990,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/schema.rs#L21954-L21990","documentation":"ExternalRegistry::is_valid_name gates registry names to lowercase ASCII letters, digits, '-' and '_' only; Config::validate() bails for anything else. The constraint exists because the name is embedded in skill addresses of the form registry:<name>/<skill> and must round-trip through the skill-resolution grammar without quoting; uppercase, spaces, dots, or ':' would make the address ambiguous or unparseable.","triggerScenarios":"A registry entry with name = \"My Registry\", name = \"Team.Skills\", or name = \"org/registry\" failing ExternalRegistry::is_valid_name during Config::validate().","commonSituations":"Using a human-friendly display name instead of a slug; copying an org/team name with spaces from a Git host; national characters or smart quotes pasted from documentation.","solutions":["Rewrite the name as a lowercase slug using only a-z, 0-9, '-', '_' (e.g. \"team-skills\")","Keep it stable: renaming later changes every registry:<name>/<skill> reference","Add a separate display/description field elsewhere if a pretty label is needed"],"exampleFix":"# before\n[[skills.extra_registries]]\nname = \"Team Skills\"\nurl = \"https://git.example.com/team/skills.git\"\n\n# after\n[[skills.extra_registries]]\nname = \"team-skills\"\nurl = \"https://git.example.com/team/skills.git\"","handlingStrategy":"type-guard","validationCode":"for reg in &cfg.skills.extra_registries {\n    if !ExternalRegistry::is_valid_name(&reg.name) { /* reject early */ }\n}","typeGuard":"// Mirrors ExternalRegistry::is_valid_name: registry:<name>/<skill> must stay parseable\nfn is_valid_registry_name(name: &str) -> bool {\n    !name.is_empty()\n        && name\n            .chars()\n            .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '-' || c == '_')\n}","tryCatchPattern":"match config.validate() {\n    Ok(()) => {}\n    Err(e) if e.to_string().contains(\"name '\") && e.to_string().contains(\"is invalid\") => {\n        // slugify the name: lowercase, spaces/dots -> '-'\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Slugify registry names the way you slugify URLs (kebab-case)","Treat the name as an API surface: changing it breaks registry:<name>/<skill> references","Keep display labels out of machine keys"],"tags":["config","skills","registry","naming"],"backgroundTag":"config-field-invalid-characters","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}