BigPizzaV3/CodexPlusPlus · error · anyhow::Error

仓库 owner/name 只能包含字母、数字、`.`、`_`、`-`

Error message

仓库 owner/name 只能包含字母、数字、`.`、`_`、`-`

What it means

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.

Solutions

  1. owner/name 只填裸段:如 owner="anthropic",name="skills",不要填 URL
  2. 提交前用正则 ^[A-Za-z0-9._-]+$ 预校验两个字段
  3. 若是 URL,先按 '/' 分段取出最后两段再分别作为 owner/name 传入

Example fix

// before
state.upsert_repo(&repo_with_owner "https://github.com/anthropic/skills");
// after
let owner = "anthropic"; // 从 URL parse 出的裸段
let name = "skills";
assert!(owner.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.'|'_'|'-')));
state.upsert_repo(&SkillRepo { owner: owner.into(), name: name.into(), .. })?;
Defensive patterns

Strategy: validation

Validate before calling

use regex::Regex;
fn is_safe_repo_segment(s: &str) -> bool {
    Regex::new(r"^[A-Za-z0-9._-]+$").unwrap().is_match(s)
}
// 提交前:if !is_safe_repo_segment(owner) || !is_safe_repo_segment(name) { bail!("非法 owner/name") }

Try / catch

match state.upsert_repo(&repo) {
    Err(e) if e.to_string().contains("只能包含字母、数字") => eprintln!("owner/name 含非法字符:{}", e),
    other => other?,
}

Prevention

When it happens

Trigger: 调用 upsert_repo 时 owner 或 name 含有字母数字与 ._- 之外的字符,如空格、斜杠 `/`、中文、`@`、`:` 等。

Common situations: 用户把完整 URL(如 https://github.com/owner/repo)直接填进 owner 字段,带入 `://` 和 `/`;输入了带空格或大小写外符号的仓库名;从其他平台复制仓库名。

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/ba11f21f55766bd1. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/skills.rs:601

}

fn normalize_repo(repo: SkillRepo) -> anyhow::Result<SkillRepo> {
    let owner = repo.owner.trim().to_string();
    let name = repo.name.trim().to_string();
    let branch = {
        let trimmed = repo.branch.trim();
        if trimmed.is_empty() {
            default_branch()
        } else {
            trimmed.to_string()
        }
    };
    let subdir = repo.subdir.trim().trim_matches('/').to_string();
    if owner.is_empty() || name.is_empty() {
        anyhow::bail!("仓库 owner 和 name 不能为空");
    }
    if !is_safe_repo_segment(&owner) || !is_safe_repo_segment(&name) {
        anyhow::bail!("仓库 owner/name 只能包含字母、数字、`.`、`_`、`-`");
    }
    Ok(SkillRepo {
        owner,
        name,
        branch,
        subdir,
        enabled: repo.enabled,
    })
}

fn is_safe_repo_segment(value: &str) -> bool {
    !value.is_empty()
        && value
            .chars()
            .all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-'))
}

/// skill id 直接参与拼路径,必须挡住 `..` 和分隔符。

View on GitHub (pinned to b1ed92e5e4)