{"record":{"id":"699d90e78226621b","repo":"BigPizzaV3/CodexPlusPlus","slug":"http-skills","errorCode":null,"errorMessage":"{}/{} 的文件树请求失败：HTTP {}{}","messagePattern":"(.+?)/(.+?) 的文件树请求失败：HTTP (.+?)(.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/skills.rs","lineNumber":907,"sourceCode":"/// 拉一个仓库的 skill 清单：trees API 一次拿到全树，再按需补 SKILL.md 的标题和描述。\n///\n/// `cached` 传上一次的结果，哈希没变的 skill 直接复用元数据，不重复请求。\npub async fn fetch_repo_skills(\n    repo: &SkillRepo,\n    cached: &BTreeMap<String, RemoteSkill>,\n) -> anyhow::Result<Vec<RemoteSkill>> {\n    let client = github_client()?;\n    let response = with_github_auth(client.get(repo_tree_url(repo)))\n        .header(reqwest::header::ACCEPT, \"application/vnd.github+json\")\n        .send()\n        .await\n        .with_context(|| format!(\"请求 {}/{} 的文件树失败\", repo.owner, repo.name))?;\n    // 不要用 error_for_status() 一把带过：403 限流、404 仓库/分支不存在、401 令牌\n    // 无效，处理方式完全不同，但用户只会看到「返回错误状态」，没法自助也没法报障\n    // （#1989 里四个仓库全失败，看不出到底是限流还是网络）。\n    let status = response.status();\n    if !status.is_success() {\n        anyhow::bail!(\n            \"{}/{} 的文件树请求失败：HTTP {}{}\",\n            repo.owner,\n            repo.name,\n            status.as_u16(),\n            github_status_hint(status.as_u16())\n        );\n    }\n    let tree = response\n        .json::<Value>()\n        .await\n        .context(\"解析 GitHub 文件树响应失败\")?;\n\n    let mut skills = parse_skills_from_tree(repo, &tree);\n    for skill in &mut skills {\n        if let Some(hit) = cached.get(&skill.id) {\n            if hit.content_hash == skill.content_hash && !hit.description.is_empty() {\n                skill.name = hit.name.clone();\n                skill.description = hit.description.clone();","sourceCodeStart":889,"sourceCodeEnd":925,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/skills.rs#L889-L925","documentation":"fetch_repo_skills calls the GitHub trees API and, when the status is not a success, bails with the HTTP code plus github_status_hint context (403/429 rate limit, 404 repo/branch missing, 401 token issues). It fires whenever the tree request fails so the caller sees which repo failed and why.","triggerScenarios":"reqwest GET 仓库文件树 API 返回 403（限流）、404（仓库/分支不存在）、401（token 无效）或任何非成功状态码时触发。","commonSituations":"未配置 GitHub token 导致匿名请求触发 60 次/小时限流；仓库改名或删除；指定的分支/tag 不存在；token 过期或权限不足；GitHub 服务端 5xx。","solutions":["查看错误消息中的 HTTP 码与 hint：403 配置 GITHUB_TOKEN 提升限额，404 核对 owner/name/分支，401 更换 token","用 curl -I 复现请求，确认是限流（X-RateLimit-Remaining: 0）还是路径错误","稍后重试（限流窗口重置或 GitHub 故障恢复后）"],"exampleFix":"// before\nlet response = client.get(&url).send().await?;\n// after\nlet response = client\n    .get(&url)\n    .header(reqwest::header::AUTHORIZATION, format!(\"Bearer {token}\"))\n    .send()\n    .await?;","handlingStrategy":"retry","validationCode":"// 调用前检查 token 是否已配置\nlet has_token = std::env::var(\"GITHUB_TOKEN\").map(|t| !t.trim().is_empty()).unwrap_or(false);\nif !has_token { eprintln!(\"警告：未配置 GITHUB_TOKEN，匿名限额易触发 403\"); }","typeGuard":null,"tryCatchPattern":"match fetch_repo_skills(&repo).await {\n    Err(e) if e.to_string().contains(\"HTTP 403\") => {\n        eprintln!(\"GitHub 限流，等待限流窗口重置后重试\");\n        tokio::time::sleep(Duration::from_secs(60)).await;\n        // 重试一次\n    }\n    Err(e) => eprintln!(\"拉取文件树失败：{e}\"),\n    Ok(skills) => { /* ... */ }\n}","preventionTips":["配置 GITHUB_TOKEN 避免匿名限流","批量操作间加延时，避免短时间内打满限额","核对 owner/name/branch 拼写后再调用"],"tags":["github","http","api","rate-limit"],"backgroundTag":"http-error-response","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"}