关于 research-archival
Research Archival 是用于归档 AI 研究对话和网页的技能,可将抓取内容保存为原始文本及带 YAML frontmatter 的编辑稿,并创建关联的 GitHub Issue。流程要求进行身份验证,并保留原始抓取内容的校验信息与指针。
主要功能
- 01抓取 AI 对话和网页
- 02分别保存原始抓取与编辑稿
- 03记录原始文件 SHA-256
- 04创建并交叉引用 GitHub Issue
- 05按来源类型路由抓取方式
适用场景
- 01归档 ChatGPT、Gemini 或 Claude 对话
- 02保存网页研究资料及其来源凭证
- 03将研究档案关联到 GitHub Issue
- 04在写入 Issue 前核验 GitHub 身份
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ terrylica/ cc-skills --skill research-archival安装后在 Claude Code 中输入/research-archival确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/evolution-log.mdfrontmatter-schema.mdurl-routing.md详细介绍
研究归档
抓取 AI 研究对话(ChatGPT、Gemini、Claude)和网页,将其归档为带有 YAML frontmatter 的 Markdown 文件,并创建相互引用的 GitHub Issues——每一步都必须验证身份。
自我演进技能:此技能会在使用过程中不断改进。如果说明有误、参数发生偏差,或需要采取变通方案,请立即修复此文件,不要拖延。仅针对真实、可复现的问题进行更新。
首先——TodoWrite 任务模板
必须:任何归档工作开始前,选择并加载适当的模板。
模板 A - 完整归档(抓取 + 保存 + issue)
1. Identity preflight — verify GH_ACCOUNT or resolve via curl /user
2. Scrape URL — route to Firecrawl or Jina per url-routing.md
3. Save the RAW scrape — docs/research/raw/…{source_type}.raw-scrape.txt, byte-exact, never edited
4. Record its sha256 — over the raw file's exact bytes
5. Write the EDITORIAL .md — frontmatter incl. raw_scrape_path + raw_scrape_sha256, and a POINTER.
Do NOT paste the transcript in; see "File Saving — TWO artifacts, and never one"
6. Survey labels — gh label list, reuse existing, max 3-6
7. Create GitHub Issue — use --body with heredoc or --body-file
8. Update frontmatter — add github_issue_url and github_issue_number
9. Post canonical backlink comment on Issue
模板 B - 仅保存(不创建 issue)
1. Identity preflight (still required for consistency)
2. Scrape URL — route to Firecrawl or Jina per url-routing.md
3. Save the RAW scrape as .raw-scrape.txt + record its sha256
4. Write the EDITORIAL .md with the provenance pin and a pointer — never the transcript itself
模板 C - 仅创建 issue(文件已存在)
1. Identity preflight
2. Read existing file frontmatter
3. Survey labels — gh label list, reuse existing, max 3-6
4. Create GitHub Issue — use --body with heredoc or --body-file
5. Update file frontmatter with issue cross-reference
6. Post canonical backlink comment on Issue
身份预检(必须 — 步骤 0)
必须在任何 gh 写入命令之前执行。不可妥协。
gh-repo-identity-guard.mjs PreToolUse 钩子提供了一层安全保障,但此技能会自行执行检查,以提供纵深防御。
解析顺序
- 显式覆盖 —
GH_ACCOUNT环境变量,仅当调用方导出该变量时才使用(现在不会再有任何东西注入它) - API 调用 — 设置了
GH_TOKEN时,执行curl -sH "Authorization: token $GH_TOKEN" https://api.github.com/user - 密钥环 — 在仓库的
gh.configdir下执行gh api user --jq .login(正确配置的机器上的常规路径)
验证
🔴 在 heredoc 内
cd进入仓库,并自行导出GH_CONFIG_DIR。 单独运行/usr/bin/env bash会启动一个新进程,不会继承交互式 shell 的函数;因此,在使用按所有者设置路径的机器上,你在该进程中调用的gh并不是你在提示符中调用的那个gh。2026-09-06 的测量结果:在同一目录中,交互式 shell 的gh api user返回的是仓库自己的账号,而 heredoc 返回的是另一个全局激活的账号——这是一个会阻止正确归档的虚假不匹配,看起来却与真实凭据问题完全相同。其原因是gh是一个 shell 函数,它会使用从每个仓库的gh.configdirgit 配置键中获取的GH_CONFIG_DIR来包装二进制文件,因此该配置键决定了使用哪个身份。在脚本中解析此值,而不是依赖调用方的 shell,检查就不会受 shell 影响。
/usr/bin/env bash << 'IDENTITY_EOF'
cd "$REPO_DIR" || { echo "BLOCKED — cannot cd to \$REPO_DIR"; exit 1; }
# Honour owner-per-path BEFORE calling gh. `gh.configdir` is a per-repository git config key, so it
# is invisible to a fresh shell unless read explicitly. Without this the next line asks the wrong
# account and the whole check is answered about a repo you are not writing to.
_cfgdir=$(git config --get gh.configdir 2>/dev/null)
if [ -n "$_cfgdir" ]; then
export GH_CONFIG_DIR="${_cfgdir/#\~/$HOME}"
unset GH_TOKEN GITHUB_TOKEN # an ambient token silently outranks GH_CONFIG_DIR
fi
# Resolve authenticated user
if [ -n "${GH_ACCOUNT:-}" ]; then
AUTH_USER="$GH_ACCOUNT"
AUTH_SOURCE="GH_ACCOUNT"
elif [ -n "${GH_TOKEN:-}" ]; then
AUTH_USER=$(curl -sf --max-time 5 -H "Authorization: token $GH_TOKEN" \
https://api.github.com/user 2>/dev/null | grep -o '"login":"[^"]*"' | cut -d'"' -f4)
AUTH_SOURCE="API /user"
else
# `gh` keeps the credential in the OS keyring, so GH_TOKEN is frequently absent from the
# environment on a correctly-configured machine. Asking gh is not a fallback, it is the
# normal path -- and omitting it made the API branch return empty on every keyring setup.
AUTH_USER=$(gh api user --jq .login 2>/dev/null)
AUTH_SOURCE="gh api user (keyring, GH_CONFIG_DIR=${GH_CONFIG_DIR:-default})"
fi
# Resolve target repo owner.
# `github\.com` alone does NOT match an SSH host alias: this machine's own owner-per-path policy
# gives remotes like `[email protected]:terrylica/repo.git`, where the character after
# `github.com` is `-`, not `:` or `/`. The optional `[^:/]*` consumes the alias suffix.
REPO_SLUG=$(git remote get-url origin 2>/dev/null | sed -n 's|.*github\.com[^:/]*[:/]\([^/]*/[^/.]*\).*|\1|p')
REPO_OWNER=${REPO_SLUG%%/*}
echo "Authenticated as: ${AUTH_USER:-<unresolved>} (via $AUTH_SOURCE)"
echo "Target repo owner: ${REPO_OWNER:-<unresolved>}"
# FAIL CLOSED. If either side is empty the comparison below is "" = "" and the guard reports
# SUCCESS while knowing nothing -- the exact green-signal-is-a-proxy failure this repo catalogues.
# Observed 2026-08-20: an SSH-alias remote produced an empty owner, an absent GH_TOKEN produced an
# empty user, and the check printed "Identity verified".
if [ -z "${AUTH_USER:-}" ] || [ -z "${REPO_SLUG:-}" ]; then
echo ""
echo "BLOCKED — identity could not be RESOLVED, which is not the same as verified."
[ -z "${AUTH_USER:-}" ] && echo " authenticated user : unresolved (set GH_ACCOUNT, or check \`gh auth status\`)"
[ -z "${REPO_SLUG:-}" ] && echo " repo slug : unresolved from $(git remote get-url origin 2>/dev/null || echo '<no origin>')"
exit 1
fi
if [ "$AUTH_USER" = "$REPO_OWNER" ]; then
echo "Identity verified (personal repo, owner == authenticated user)"
exit 0
fi
# NOT automatically a failure. On an ORGANISATION repo the owner is the org, never a user, so a
# string comparison can never match however correct the credential is. The real question is
# whether this identity may write here.
API_RC=0
REPO_JSON=$(gh api "repos/$REPO_SLUG" 2>/tmp/gh-identity-err) || API_RC=$?
OWNER_TYPE=$(printf '%s' "$REPO_JSON" | jq -r '.owner.type // empty')
CAN_PUSH=$(printf '%s' "$REPO_JSON" | jq -r '.permissions.push // empty')
if [ "$OWNER_TYPE" = "Organization" ] && [ "$CAN_PUSH" = "true" ]; then
echo "Identity verified (organisation repo; $AUTH_USER holds push on $REPO_SLUG)"
exit 0
fi
echo ""
echo "MISMATCH — do NOT proceed with gh write commands"
# Print what GitHub actually returned. A field that is absent prints as nothing, because absent is
# a state -- substituting a word like "unknown" would report a value GitHub never sent.
[ -n "$OWNER_TYPE" ] && echo " owner.type : $OWNER_TYPE"
[ -n "$CAN_PUSH" ] && echo " permissions.push : $CAN_PUSH"
[ "$API_RC" -ne 0 ] && echo " gh exit code : $API_RC"
[ -s /tmp/gh-identity-err ] && echo " gh stderr : $(head -1 /tmp/gh-identity-err)"
echo "Fix: export GH_TOKEN=\$(~/.claude/tools/bin/gh-token-for-repo)"
exit 1
IDENTITY_EOF
如果不匹配,则阻止操作 — 显示诊断信息,并且不要继续执行任何 gh 写入操作。
为什么存在组织分支。 最初的检查条件是
AUTH_USER != REPO_OWNER → block。在组织仓库中,所有者是组织,因此无论凭据多么正确,两者都不可能匹配——这会阻止所有向共享仓库进行的合法归档。2026-08-20 在example-org/research-repo上的测量结果:已验证身份为terrylica,所有者为example-org,owner.type = Organization,permissions.push = true。这就是正确的身份,但检查却将其判定为不匹配。此保护机制的目的是阻止向错误账号写入;而“此身份是否有权在此处写入”应由
permissions.push判断,而非与所有者进行字符串比较。个人仓库的相等性检查路径仍作为快速的离线路径保留;组织路径只需额外调用一次 API,并且仅在不相等时才会执行。
抓取工作流
根据 URL 模式对抓取请求进行路由。完整详情请参阅 url-routing.md。
决策树
Any JS-rendered share link (chatgpt.com/share/, gemini.google.com/share/, claude.ai/artifacts/)
→ Firecrawl public API (POST https://api.firecrawl.dev/v2/scrape)
→ Use curl (not WebFetch — it summarizes instead of returning raw)
Simple static page
→ Either works. Jina Reader (https://r.jina.ai/{URL}) is one GET and fine here.
默认使用 Firecrawl。 2026-08-13 对两个 chatgpt.com/share/* 链接的测量结果显示,Jina 返回的内容只有 Firecrawl 的 17% 和 12%,并且在句子中途截断;Firecrawl 两次都到达了页面真正的页脚。Jina 还需要 -H "x-timeout: 30",否则只会返回约 321 字节的登录界面内容。
Firecrawl 抓取
不做健康检查、不做预检、不做恢复。公共 API 无需密钥,也没有可能宕机的主机——请求失败时按单个请求处理,而不是先做存活探测再决定是否运行。
/usr/bin/env bash << 'SCRAPE_EOF'
set -euo pipefail
# waitFor gives the SPA time to render; without it a share link returns the shell.
RESPONSE=$(curl -sS --max-time 180 -X POST https://api.firecrawl.dev/v2/scrape \
-H "Content-Type: application/json" \
-d "$(jq -n --arg u "$URL" \
'{url: $u, formats: ["markdown"], waitFor: 8000, timeout: 60000}')")
CONTENT=$(printf '%s' "$RESPONSE" | jq -r '.data.markdown // empty')
if [ -z "$CONTENT" ]; then
echo "ERROR: Firecrawl returned no markdown:" >&2
printf '%s\n' "$RESPONSE" | head -c 400 >&2
echo "Retry once, then fall back to: curl -H 'x-timeout: 30' https://r.jina.ai/${URL}" >&2
exit 1
fi
printf '%s\n' "$CONTENT"
SCRAPE_EOF
不要重新引入自托管的 Firecrawl。 gpu-host-2 部署(端口 3002/3003,共 5 个容器)已于 2026-08-13 退役,并释放了约 18 GB。它需要健康检查、容器重启和 WORKER-STALLED 故障排查,而在当前用量下,公共 API 已无需这些操作。
文件保存——两个文件,绝不能只有一个
归档会生成两个文件。这是本页最重要的一点,因为弄错的代价很高,而且只有当有人尝试验证归档时,这个代价才会显现。
docs/research/raw/YYYY-MM-DD-{slug}-{source_type}.raw-scrape.txt the scrape, byte-exact, NEVER edited
docs/research/YYYY-MM-DD-{slug}-{source_type}.md editorial + a POINTER to it
slug— kebab-case 摘要(最多 50 个字符)source_type— 从枚举中选择:chatgpt、gemini、claude、web
🔴 不要把文字记录粘贴到 .md 中
这样做看似理所当然,实际上是错的。Research-repo PR #540 删除了一份重复的文字记录,前后经过 5 轮评审,每轮都发现一种会改变含义的编辑,而等价性检查器却判定内容完全相同:结构标记被删除(引用内容被降格为作者自己的断言),行内分隔符被删除(`not` 被读作英文单词),标识符和 URL 路径经由 _ 和 __ 改写,表格单元格边界被移动,导致结果对应到了错误的引用,以及字面星号在转义符、围栏代码块、缩进代码块和原始 HTML 属性中被吞掉。
最后一种情况属于类别错误,而非 bug。CommonMark 0.31.2 规定了解析分为两个阶段——"第一阶段会逐行读取输入,并构建文档的块结构……文本会被分配到这些块中,但不会被解析。……第二阶段会将段落和标题的原始文本内容解析为一系列 Markdown 行内元素"——因此,面向行的规范化器没有块解析阶段,无法区分段落和围栏代码块。再怎么打补丁也无法解决问题。
这个问题为何会出现:任何会重新格式化 Markdown 的仓库工具(这里是运行 prettier --write 和 markdownlint-cli2 --fix 的 itp-hooks Stop hook)都会让粘贴的副本无法保持字节级完全一致,因此每次提交时,你都必须回答“副本是否仍在表达与原文相同的内容?”这个问题。只有一份内容,就不存在这个问题。
强制仅保留一份:测量重复内容,绝不匹配标记
第一个门禁通过查找旧版 `` 注释来阻止重复嵌入。一次审查就绕过了它:在普通的 --- 后追加整份转录,省略标记,门禁便会返回成功——而名为 test_re_embedding_the_transcript_is_refused 的测试仍然通过,因为该测试使用标记来构建测试夹具。把模式匹配说成结构性保证,却只在 bug 不可能出现的那种输入上验证。
重复嵌入是一个近似重复检测问题,因此应采用这一问题的标准度量:Andrei Z. Broder 在《On the resemblance and containment of documents》,SEQUENCES 1997 中提出的基于 w-shingle 的_包含度_,doi:10.1109/SEQUEN.1997.666900——“A 在 B 中的包含度 c(A,B) 是一个介于 0 和 1 之间的数,当它接近 1 时,表示 A 大致包含在 B 中”,基于_D 中所有大小为 w 的 shingle 所组成的袋(多重集)_。
四点让它在实践中奏效:
- 包含度,而非相似度。 问题具有方向性——转录有多少内容再次出现——而相似度是对称的,因此较长的编辑内容会稀释结果,掩盖整段粘贴。
- 校准阈值,而非凭空设定。 测量真实存档和一系列不同粘贴比例。我们的结果:合法引用为
0.0097→ 粘贴十分之一为0.1172→ 整份内容为1.0000,因此0.10的上限约为引用值的 10 倍,同时低于所有有意义的粘贴比例。将这一系列结果和常量一同公开,否则这个数字看起来就像是随意猜的。 - 将字母数字字符转换为小写 token,这样即使经过格式重排,度量仍然有效,而正是格式重排让字节比较无法奏效。即使经过
prettier处理,重复嵌入的转录仍必须能被检测到。 - 使用集合,而非多重集。 Broder 定义的是一个袋;集合回答的是“有多少_不同的_内容被重现”,因此同一片段粘贴 20 次也只计一次——这正符合这里的语义。
将不变量的范围限定为整个仓库,而非单个文件。 第二种绕过方式很简单:移动副本。只扫描_固定_转录的存档会报告成功,即便同级的 .md 文件中包含了 100% 的转录。扫描所有 Markdown 文件,并且测量它们拼接后的内容——12 个片段各自都低于上限,仍可能拼出整份转录。
应明确说明这两项限制,而不是声称它们不存在:单行引用仍必须通过(存档本身就包含约 1% 的合法引用,因此任何会拒绝一行粘贴内容的阈值都会彻底禁止引用);高度改写的重复嵌入会绕过这项度量——此时文档确实已不相同,任何自动检查都无法判定含义是否得以保留。这与导致规范化器失效的不可判定性相同;唯一的控制手段是审阅者的判断。
.txt 扩展名至关重要
将原始抓取内容存为 .txt,而不是 .md。Markdown 工具会匹配 *.md;原始抓取内容若以 .md 保存,就会被改写,导致其哈希失效,即使无人碰过它。通过扩展名从设计上使该文件不受影响,而不是依赖配置实现——可以额外添加 .prettierignore 和 .gitattributes -text 作为纵深防御,但它们并非实现这一点的机制。
YAML Frontmatter
完整的字段约定请参阅 frontmatter-schema.md。
---
source_url: https://chatgpt.com/share/...
source_type: chatgpt-share
scraped_at: "2026-02-09T18:30:00Z" # a REAL timestamp; never a rounded guess
scraped_at_source: "how you obtained it, e.g. mtime of the scraper's output file"
raw_scrape_path: docs/research/raw/YYYY-MM-DD-{slug}-{source_type}.raw-scrape.txt
raw_scrape_sha256: "sha256 of the raw file's exact bytes"
transcript_location: "not reproduced in this file; the sole copy is raw_scrape_path"
model_name: gpt-4o
claude_code_uuid: SESSION_UUID
github_issue_url: ""
github_issue_number: ""
---
raw_scrape_path 和 raw_scrape_sha256 必须成对出现。只声明其中一个比两者都不声明更糟:有哈希却没有文件,什么也证明不了;有文件却没有哈希,则未经验证,却看起来像是已固定。门禁应检测任意一个键,以便拒绝只声明一项的情况,而不是悄然跳过。
将 github_issue_url 和 github_issue_number 留空——在创建 Issue 后更新。
.md 中应包含的内容
仅写编辑性内容,而且值得认真撰写,因为这是人类会阅读的部分:
- 一条来源警示,说明此处未复现对话记录,且其内容是未经信任的 AI 输出。
- 一条审核说明——检查了哪些引文、采用了什么方法,以及发现了什么。要区分_身份_检查(DOI 是否指向该标题、作者和刊物)与_内容_检查(来源是否确实表达了摘要所声称的内容)。两者不同,混为一谈是常见错误。
- 源文件中的任何已知错误,应写在读者看得到的地方,而不只是 PR 中。
- 指向
.txt文件的链接,并注明其大小和 sha256。
通过 Crossref API 按内容核验引文,切勿根据 HTTP 状态码判断——403 机器人拦截和 404 从状态行看无法区分。如果 Crossref 未提供摘要,请明确说明,并将相关说法标记为未经核验,而不要暗示已核验。
以编程方式将每一处逐字引文与检索到的文档逐一比对,并先检查提取结果,再相信比对结果。对 PDF 使用 textutil -convert txt -stdout 时,返回的字节数恰好等于文件大小——原始字节直接通过,却报告成功——任何引文都会因此在二进制噪声中“核验通过”。先根据页数检查提取文本的长度,再进行比对时忽略空白差异并折叠连字,因为提取过程会破坏词间空格。
比对失败时,先查明是哪种失败,再进行任何修改。我们遇到过两次失败:一次是提取器在句子中插入了页码(引文本身准确无误);另一次是引号内部的 ' 被替换成了源文中的 "。无论看起来多么微小,第二种情况都属于修改引文内容,这和把无法提取的字形替换成口述的名称是同一种缺陷。
创建 GitHub Issue
标签调研
先调研现有标签——优先复用,只有概念确实新颖时才创建标签。
gh label list --repo owner/repo --limit 100
政策:每个 Issue 最多使用 3-6 个标签。常见标签:research、ai-output、chatgpt、gemini、archival。
创建 Issue
使用 --body 和 heredoc 进行内联撰写,或使用 --body-file 处理非常长的内容。
/usr/bin/env bash << 'ISSUE_EOF'
# Write body to temp file
cat > "/tmp/issue-body-${SLUG}.md" << 'BODY_EOF'
## Summary
Brief description of the archived research content.
## Source
- **URL**: SOURCE_URL
- **Type**: source_type
- **Model**: model_name
- **Scraped**: scraped_at
## Key Findings
- Finding 1
- Finding 2
## Archived File
`docs/research/FILENAME.md`
BODY_EOF
# Create issue
gh issue create \
--repo owner/repo \
--title "Research: descriptive title here" \
--body-file "/tmp/issue-body-${SLUG}.md" \
--label "research,ai-output"
# Clean up
rm -f "/tmp/issue-body-${SLUG}.md"
ISSUE_EOF
更新 Frontmatter
创建 Issue 后,使用 Issue 的 URL 和编号更新归档文件的 frontmatter。
规范的反向链接评论
在 Issue 上发表评论,链接回归档文件。元数据应使用 Markdown 列表,而不是连续的散文行:
**Archived**: `docs/research/YYYY-MM-DD-slug-source_type.md`
- **Scraped**: 2026-02-09T18:30:00Z — scraper, byte count, whether it reached the page footer
- **Source**: [chatgpt-share](https://chatgpt.com/share/...)
- **Session**: `SESSION_UUID`
- **PR**: #NNN — commit `abcdef12`
不要恢复成连续裸行的写法。 GitHub 会将评论中的每个换行符渲染为
<br>,因此连续的四行Key: value看起来与硬换行的散文无法区分,而GH-HARD-WRAP-GUARDPreToolUse 钩子会拒绝该命令(测量日期为 2026-08-20——本文件中先前的模板本身就是触发问题的内容)。列表通过结构表达“这些是不同的条目”,这既符合本意,也符合守护机制的要求。在这里使用GH-HARD-WRAP-OK覆盖选项,只会压制正确的警告。
每个 Issue/PR 正文都必须写成不间断的段落
这也适用于 Issue 正文和 PR 正文,而不仅是反向链接。每个段落都应写成一整行,让 GitHub 自动换行;只在列表项、标题、代码块和空行处换行。编辑器的软换行会让这一点不易察觉——可用以下命令检查:
awk '{ if (length($0) > 100 && $0 !~ /^[-*|#> ]/) printf "L%d: %d cols\n", NR, length($0) }' body.md
散文段落使用长行是预期且正确的;问题在于连续出现许多约 80–100 列的行。
修改后的检查清单
修改此技能后:
- YAML 前置元数据有效(描述中不含冒号)
- 描述中的触发关键词是最新的
- 所有
./references/链接均可访问 - 身份预检部分仍位于工作流的首位
- 将更改追加到 evolution-log.md
- 验证:
uv run plugins/plugin-dev/scripts/skill-creator/quick_validate.py plugins/gh-tools/skills/research-archival - 验证链接:
bun run plugins/plugin-dev/scripts/validate-links.ts plugins/gh-tools/skills/research-archival
故障排除
| 问题 | 原因 | 修复 |
|---|---|---|
GH-HARD-WRAP-GUARD 拒绝该命令 | 正文采用了硬换行的行文方式 | 将每个段落重新排为一行;对于堆叠的 Key: value 元数据,使用列表。不要尝试使用 GH-HARD-WRAP-OK——此检查是正确的 |
| 身份预检因组织仓库而拦截 | 所有者是组织,因此 AUTH_USER != REPO_OWNER 始终成立 | 这是预期行为——现在检查会继续判断 owner.type == Organization 和 permissions.push == true。如果仍被拦截,说明你确实没有推送权限 |
预检报告 MISMATCH,显示的账号并非你正在使用的账号,而且在提示符下 gh 可以正常工作 | heredoc 是一个全新的进程,没有 shell 函数,因此它调用了原始 gh 二进制文件,未找到每个仓库的 gh.configdir | 在 heredoc 内执行 cd,并在首次调用 gh 前,从 git config --get gh.configdir 导出 GH_CONFIG_DIR。分别在 heredoc 内外运行 gh api user --jq .login 进行确认——结果不同说明这是此问题,而非凭据错误 |
成功推送后,gh pr create 仍提示“you must first push the current branch” | 从关联的 worktree 运行;gh 无法解析上游 | 显式传入 --head <branch> --base main。使用 git ls-remote --heads origin <branch> 确认该分支确实在远程仓库中 |
| 使用错误的账号发帖 | GH_TOKEN 不匹配 | 在仓库内运行 git remote get-url origin(预期为 [email protected]<account>:owner/repo)和 gh api user --jq .login |
| 正文超过 65536 个字符 | GitHub API 限制 | 将内容拆分到 issue 正文和第一条评论中 |
| Firecrawl 未返回 Markdown | API 暂时性故障 | 重试一次,然后使用 Jina 并传入 -H "x-timeout: 30" |
| 抓取结果只有页面外壳 | SPA 尚未完成渲染 | 在请求正文中调高 waitFor(8000 → 15000)和 timeout |
| Jina 返回约 321 字节 | 缺少超时标头 | 添加 -H "x-timeout: 30"——不加时,Jina 返回的是登录页面框架 |
| Jina 输出被截断 | Jina 对 JavaScript 密集型页面的覆盖不足 | 这是预期行为——请使用 Firecrawl;在 2026-08-13 的测试中,Jina 的覆盖率为 17%/12% |
| 身份检查拦截 | 使用的账号不是所有者 | export GH_TOKEN=$(~/.claude/tools/bin/gh-token-for-repo) |
参考资料
- Frontmatter Schema — YAML 字段约定
- URL Routing — 抓取器路由表
- Evolution Log — 变更历史
执行后复盘
此技能完成后,请在结束前检查:
- 命令是否成功? — 如果没有,请修复导致失败的指令或错误表。
- 参数或输出是否发生变化? — 如果底层工具的接口发生了变化,请相应更新 Usage 示例和 Parameters 表。
- 是否需要采用变通方法? — 如果你不得不临时变通(使用不同的标志、增加额外步骤),请更新此 SKILL.md,确保下次调用时无需采用相同的变通方法。
仅在问题真实且可复现时更新,不要根据推测进行更新。