{"record":{"id":"0ea02840a46a743c","repo":"Hmbown/CodeWhale","slug":"install-source-must-not-be-empty-0ea028","errorCode":null,"errorMessage":"install source must not be empty","messagePattern":"install source must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/skills/install.rs","lineNumber":118,"sourceCode":"    GitHubRepo(String),\n    /// Raw `http(s)://…` tarball URL. Used as-is.\n    DirectUrl(String),\n    /// Curated registry lookup key. Looked up via the configured `registry_url`.\n    Registry(String),\n}\n\nimpl InstallSource {\n    /// Parse a user-supplied spec. Empty / whitespace-only input is rejected.\n    ///\n    /// * `github:owner/repo` → [`InstallSource::GitHubRepo`]\n    /// * `https://github.com/owner/repo[.git]` (no path past the repo) →\n    ///   [`InstallSource::GitHubRepo`]\n    /// * any other `http://` or `https://` prefix → [`InstallSource::DirectUrl`]\n    /// * anything else → [`InstallSource::Registry`]\n    pub fn parse(spec: &str) -> Result<Self> {\n        let trimmed = spec.trim();\n        if trimmed.is_empty() {\n            bail!(\"install source must not be empty\");\n        }\n        if let Some(rest) = trimmed.strip_prefix(\"github:\") {\n            let rest = rest.trim();\n            // Reject obviously bogus values up front. We intentionally accept\n            // case-insensitive owner/repo so `github:Hmbown/Foo` works.\n            let (owner, repo) = rest.split_once('/').with_context(|| {\n                format!(\"github source must be 'github:owner/repo' (got {spec})\")\n            })?;\n            let owner = owner.trim();\n            let repo = repo.trim().trim_end_matches('/');\n            if owner.is_empty() || repo.is_empty() {\n                bail!(\"github source must be 'github:owner/repo' (got {spec})\");\n            }\n            if owner.contains('/') || repo.contains('/') {\n                bail!(\"github source must be 'github:owner/repo' (got {spec})\");\n            }\n            return Ok(Self::GitHubRepo(format!(\"{owner}/{repo}\")));\n        }","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/skills/install.rs#L100-L136","documentation":"InstallSource::parse rejects an empty or whitespace-only skill install spec before any network or filesystem work. Every non-empty spec routes into the github:/https://registry classification, so this error purely means nothing was passed to install.","triggerScenarios":"Invoking the skill install flow with an empty argument: '/skill install' with no spec, a spec of only spaces, or a wrapper passing an unset environment variable as the spec.","commonSituations":"Argument-parsing bugs in wrappers, empty-string defaults ('${SKILL_SPEC:-}'), copy-paste that lost the spec, and UI flows submitting before input.","solutions":["Pass a real spec: 'github:owner/repo', an https URL, or a registry name.","If a wrapper builds the spec from variables, assert it is non-empty before invoking install.","Quote arguments so unbound variables do not collapse to empty strings.","Treat as a pure input error: nothing was fetched, no cleanup is needed."],"exampleFix":"# before\n/skill install \"\"\n\n# after\n/skill install github:owner/repo","handlingStrategy":"validation","validationCode":"let spec = spec.trim();\nif spec.is_empty() {\n    eprintln!(\"skill spec is required: github:owner/repo, https URL, or registry name\");\n    return;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make wrappers fail fast on empty or unset spec variables instead of forwarding them.","Quote arguments so shell splitting cannot silently drop them.","Treat this error as a no-op: nothing was fetched or written."],"tags":["skills","install","input-validation","rust","anyhow"],"backgroundTag":"empty-required-input","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}