{"record":{"id":"e2593b192be3368d","repo":"BigPizzaV3/CodexPlusPlus","slug":"owner-name","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":598,"sourceCode":"        subdir,\n        enabled: true,\n    })\n}\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, '.' | '_' | '-'))","sourceCodeStart":580,"sourceCodeEnd":616,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/skills.rs#L580-L616","documentation":"normalize_repo trims the SkillRepo fields and rejects the result when owner or name is empty after trimming. Whitespace-only input is the typical trigger; without owner/name the GitHub tree/zip URLs cannot be constructed, so upsert_repo fails fast.","triggerScenarios":"调用 upsert_repo 传入的 SkillRepo 中 owner 或 name 字段为空字符串或仅含空白字符（trim 后为空）。","commonSituations":"前端表单未填写完整就提交；从 GitHub URL 解析 owner/name 失败后得到空串；配置文件迁移时字段缺失被反序列化为空 String；默认占位值未替换。","solutions":["在调用 upsert_repo 前确保 owner 和 name 均为非空字符串（trim 后）","若从 GitHub URL 推导，检查 URL 解析逻辑是否正确提取了 owner/repo 两段","前端在提交前做必填校验，禁用空 owner/name 的提交按钮"],"exampleFix":"// before\nstate.upsert_repo(&SkillRepo { owner: \"\".into(), name: repo_name, ..Default::default() })?;\n// after\nif owner.trim().is_empty() || name.trim().is_empty() {\n    return Err(anyhow!(\"owner/name 必填\"));\n}\nstate.upsert_repo(&SkillRepo { owner: owner.into(), name: name.into(), ..Default::default() })?;","handlingStrategy":"validation","validationCode":"fn repo_fields_filled(owner: &str, name: &str) -> bool {\n    !owner.trim().is_empty() && !name.trim().is_empty()\n}","typeGuard":null,"tryCatchPattern":"match state.upsert_repo(&repo) {\n    Err(e) if e.to_string().contains(\"不能为空\") => eprintln!(\"owner/name 为必填字段，请补全后重试\"),\n    other => other?,\n}","preventionTips":["表单层对 owner/name 做必填校验后再提交","从 GitHub URL 解析 owner/name 时对解析结果做断言","反序列化 SkillRepo 配置后校验关键字段非空"],"tags":["validation","github-repo","empty-field"],"backgroundTag":"empty-required-field","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"}