{"record":{"id":"ba11f21f55766bd1","repo":"BigPizzaV3/CodexPlusPlus","slug":"owner-name-skills","errorCode":null,"errorMessage":"仓库 owner/name 只能包含字母、数字、`.`、`_`、`-`","messagePattern":"仓库 owner/name 只能包含字母、数字、`\\.`、`_`、`-`","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/skills.rs","lineNumber":601,"sourceCode":"}\n\nfn normalize_repo(repo: SkillRepo) -> anyhow::Result<SkillRepo> {\n    let owner = repo.owner.trim().to_string();\n    let name = repo.name.trim().to_string();\n    let branch = {\n        let trimmed = repo.branch.trim();\n        if trimmed.is_empty() {\n            default_branch()\n        } else {\n            trimmed.to_string()\n        }\n    };\n    let subdir = repo.subdir.trim().trim_matches('/').to_string();\n    if owner.is_empty() || name.is_empty() {\n        anyhow::bail!(\"仓库 owner 和 name 不能为空\");\n    }\n    if !is_safe_repo_segment(&owner) || !is_safe_repo_segment(&name) {\n        anyhow::bail!(\"仓库 owner/name 只能包含字母、数字、`.`、`_`、`-`\");\n    }\n    Ok(SkillRepo {\n        owner,\n        name,\n        branch,\n        subdir,\n        enabled: repo.enabled,\n    })\n}\n\nfn is_safe_repo_segment(value: &str) -> bool {\n    !value.is_empty()\n        && value\n            .chars()\n            .all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-'))\n}\n\n/// skill id 直接参与拼路径，必须挡住 `..` 和分隔符。","sourceCodeStart":583,"sourceCodeEnd":619,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/skills.rs#L583-L619","documentation":"normalize_repo then applies is_safe_repo_segment to owner and name, allowing only ASCII alphanumerics, '.', '_' and '-'. This bail means one of them contains other characters (spaces, slashes, Unicode) that would break repo URL construction or path safety.","triggerScenarios":"调用 upsert_repo 时 owner 或 name 含有字母数字与 ._- 之外的字符，如空格、斜杠 `/`、中文、`@`、`:` 等。","commonSituations":"用户把完整 URL（如 https://github.com/owner/repo）直接填进 owner 字段，带入 `://` 和 `/`；输入了带空格或大小写外符号的仓库名；从其他平台复制仓库名。","solutions":["owner/name 只填裸段：如 owner=\"anthropic\"，name=\"skills\"，不要填 URL","提交前用正则 ^[A-Za-z0-9._-]+$ 预校验两个字段","若是 URL，先按 '/' 分段取出最后两段再分别作为 owner/name 传入"],"exampleFix":"// before\nstate.upsert_repo(&repo_with_owner \"https://github.com/anthropic/skills\");\n// after\nlet owner = \"anthropic\"; // 从 URL parse 出的裸段\nlet name = \"skills\";\nassert!(owner.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.'|'_'|'-')));\nstate.upsert_repo(&SkillRepo { owner: owner.into(), name: name.into(), .. })?;","handlingStrategy":"validation","validationCode":"use regex::Regex;\nfn is_safe_repo_segment(s: &str) -> bool {\n    Regex::new(r\"^[A-Za-z0-9._-]+$\").unwrap().is_match(s)\n}\n// 提交前：if !is_safe_repo_segment(owner) || !is_safe_repo_segment(name) { bail!(\"非法 owner/name\") }","typeGuard":null,"tryCatchPattern":"match state.upsert_repo(&repo) {\n    Err(e) if e.to_string().contains(\"只能包含字母、数字\") => eprintln!(\"owner/name 含非法字符：{}\", e),\n    other => other?,\n}","preventionTips":["用户输入 URL 时先解析出裸 owner/name 段再入库","输入框做实时字符白名单校验","避免直接粘贴带协议前缀的 GitHub 地址到 owner 字段"],"tags":["validation","github-repo","invalid-characters"],"backgroundTag":"invalid-identifier-format","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}