面向 Agent 的 GitHub CLI(gh)调用实战指南:结构化输出、分页与搜索模式
本文是基于 GitHub 官方命令行工具 gh 仓库内 skills/gh/SKILL.md 编写的 Agent 调用模式技术指南。gh(GitHub’s official command line tool)不仅能被人类在终端中使用,也常常被 AI Agent 直接调用去查询 issue、PR、仓库内容或执行写操作;本指南汇集了在非交互、无 TTY 环境下安全且稳定地驱动 gh 的全部关键模式:结构化 JSON 输出、分页与静默截断、仓库定位、搜索与列表的语义差异、Issue 2.0 字段、图片视频附件、Discussions、免克隆读文件,以及 gh api 兜底策略。读完本文,你可以让自己的 Agent 以更少的试错成本完成可靠的 GitHub 数据获取与变更操作。
非交互(non-TTY)环境下的交互策略
在由脚本或 Agent 发起的调用中,gh 在非 TTY 上下文里已经"自动做正确的事":
- 自动跳过 pager;
- 自动剥离 ANSI 颜色;
- 不再弹交互式提示,而是快速报错并给出可操作信息。
例如 gh issue create 缺少必要参数时会直接提示 must provide --title and --body when not running interactively,而不是挂起等待输入。
因此你不必防御性地设置 GH_PAGER 或传 --no-pager 参数——后者根本不存在。如果你想在 Agent 测试框架里强制获得 TTY 风格输出(颜色、表格、分页器、交互),可以设置 GH_FORCE_TTY=1;NO_COLOR、CLICOLOR_FORCE、GH_FORCE_TTY 均会被 gh 识别(详见本文最后一节)。
依据:skills/gh/SKILL.md 的 "Interactivity policy" 一节。
解析 JSON:--json / --jq / --template
gh 命令面向人类的默认输出是列格式。需要结构化数据时,按以下模式操作:
- 追加
--json field1,field2,...以获得结构化 JSON 输出; - 只运行
--json而不给出字段列表,即可打印该命令当前支持的全部可用字段,之后再挑选你需要的字段; - 需要过滤时,用
--jq '<expr>'内联过滤,不必再管道到独立的jq可执行文件; - 想要"塑形"的文本输出时,可以同时使用
--json与--template '<go-template>'(Go 模板语法)。
这些标志的底层实现在 pkg/cmdutil/json_flags.go:AddJSONFlags 会同时注册 --json、--jq(别名 -q)与 --template(别名 -t),并挂入每个子命令的 PreRunE 做参数校验。
有两个容易踩的坑:
- 不带字段列表的
--json并不是"输出所有字段"。实际行为是触发校验错误,提示你指定逗号分隔的字段列表,例如Specify one or more comma-separated fields for --json:后接可用字段清单——这正是官方 SKILL 建议你用它"枚举可用字段"的原因。若你传入一个该命令不认识的字段,会得到Unknown JSON field错误并列出所有可用字段(见 json_flags.go)。 --template/-T会在少数命令上与"body 模板"参数冲突。例如gh pr create -T、gh issue create -T中-T指的是 body 模板(issue/PR 模板),而gh pr view -T等命令中才是 Go 模板。因此在使用之前务必先--help确认你命中哪一个参数。
校验规则(从源码可以确认)还包括:--web 不能与 --json 混用;--jq 和 --template 不能脱离 --json 单独使用。jq 表达式的求值由 gh 内嵌的 go-gh jq 模块完成(jsonExporter.Write 中调用 jq.EvaluateFormatted),模板则通过内置 template 引擎输出,并在 TTY 下做语法高亮。
分页与"静默截断"
列表类命令都会对结果数量设上限,必须显式地通过分页或限制参数控制你要的数据量:
gh issue list、gh pr list、gh search ...需要传-L N(即--limit N);默认值通常是 30。gh search系列--limit的有效范围是 1 到 1000(源码在 pkg/cmd/search/issues/issues.go 中校验)。gh issue list/gh pr list不会通过--json暴露类似totalCount的聚合总数。若确需真实总数,用gh api graphql查询totalCount;否则就把-L当作本次调用的硬上限来对待。- 对裸 REST API 调用,使用
gh api --paginate <path>,并可与--jq、(可选)--slurp组合拼出一个完整数组。
分页的底层逻辑在 pkg/cmd/api/pagination.go:对 REST,gh 解析响应的 Link 头中 rel="next" 关系逐页跟进(findNextPage),并可通过 per_page 参数控制页大小;对 GraphQL,则从 pageInfo.hasNextPage / endCursor 提取游标。多页 JSON 结果会被包装器合并成一个合法的 JSON 数组再输出。
记忆点:一切列表都有上限——
-L决定单次调用能拿多少,--paginate决定你能连续拿多少页,totalCount只能走 GraphQL 获取。
仓库定位(Repo targeting)
gh 会从当前工作目录(cwd)的 git remote 推断仓库归属(实现见 context/remote.go 与 pkg/cmdutil/repo_override.go)。
- 当你想覆盖 CWD 解析出的仓库时,传入
--repo OWNER/REPO(别名-R)。 - 大量命令都通过
cmdutil.EnableRepoOverride(cmd, f)挂上-R支持,从任何目录都能针对指定仓库操作,这对 Agent 特别重要——你不需要先 clone 或cd进仓库。
Search 与 list 的差异
这是 Agent 最容易写错查询的地方。核心区别:gh search ... 走的是 GitHub 的搜索索引,而 gh issue list --search / gh pr list --search 只在一个仓库内做过滤。
gh search 系列
gh search issues|prs|code|repos|commits|users 接受完整搜索语法(is:open、author:、label:、repo:owner/name、in:title 等)。关键规则:
- 每个 qualifier 单独作为一个裸 token 传入,而不是包成一个被引号括起来的字符串:
- ✅
gh search issues repo:cli/cli is:open author:monalisa正常; - ❌
gh search issues "repo:cli/cli is:open"会被当作单一关键字,解析成repo:"cli/cli is:open",并以Invalid search query失败。
- ✅
- 只有多词自由文本才加引号,如
gh search issues "broken feature"。 - 大多数 qualifier 都有对应的专用 flag(
--repo、--author、--label…)。 - 凡是跨仓库、或按 author/label 过滤的需求,优先用 search 而非 list。
Bot 作者与 --app
Bot 在 GitHub 上以 GitHub App 身份发表内容,因此:
--author dependabot匹配不到任何东西;- 正确做法是
--app dependabot(在pr/issue list与search prs|issues上可用,底层展开为author:app/<slug>),或用--author "dependabot[bot]"。
在源码层面,--app 的实现就是把 qualifier 的 Author 改写成 app/<slug>(见 pkg/cmd/search/issues/issues.go),且 --author 与 --app 互斥。
--search-type(仅 issue,github.com / GHEC)
gh search issues 还支持 --search-type <lexical|semantic|hybrid>:
lexical(默认):精确关键词匹配;semantic:当用户用自然语言描述问题而非精确术语时使用,按语义相关性排序;hybrid:混合关键词与语义排序。
源码中的约束(issues.go)进一步说明:
- semantic/hybrid 仅限 issue 搜索,不能与
--include-prs组合; - 它们按相关性排序,因此不支持
--sort/--order,也不支持--web; - 只返回单页结果(不分页);在 GitHub Enterprise Server 上不可用;
lexical不会作为 search type 上送 API(它是 API 的默认行为),只有非 lexical 值才写进请求(issues.go)。
list 命令的 --search
gh issue list --search "..." 与 gh pr list --search "..." 把整个查询作为一个被引号括起的字符串(因为它是 flag 值),并限定在单一仓库内。注意这与 gh search 的"裸 token"风格正好相反。
Issue 类型、子任务与关联关系(Issue 2.0)
较新的 gh issue 子命令建模了 issue type(类型)、sub-issue(子任务层级)与 blocked-by/blocking(阻塞关系):
- 创建
gh issue create:--type <name>、--parent <number|url>(把新 issue 创建为子任务)、--blocked-by <number|url,...>、--blocking <number|url,...>。 - 编辑
gh issue edit(可一次编辑同一仓库内的多个 issue,如gh issue edit 23 34):--type <name>/--remove-type;--parent <n|url>/--remove-parent;--add-sub-issue <n,n>/--remove-sub-issue <n,n>;--add-blocked-by <n,n>/--remove-blocked-by <n,n>;--add-blocking <n,n>/--remove-blocking <n,n>。
- 关系与 parent 引用是 issue 编号或 URL;URL 可指向同一主机上的另一仓库,但指向不同主机则被拒绝。
--add-sub-issue在同时编辑多个 issue 时不可用。- 过滤
gh issue list --type <name>可按 issue 类型过滤。
对于读取侧,gh issue view 与 gh issue list 支持把这些字段纳入 --json(官方 SKILL 建议优先于解析文本输出使用):issueType、parent、subIssues、subIssuesSummary、blockedBy、blocking。
需要特别警惕的数据形状问题:subIssues、blockedBy、blocking 是 {"nodes": [...], "totalCount": N} 形式的对象而非扁平数组,且 nodes 有上限(subIssues 上限 100,blockedBy/blocking 上限 50)。因此处理时必须拿 node 数量与 totalCount 对比来检测截断。
版本前提(GHES):issue types 与 sub-issues 需要 GHES 3.17+;blocked-by/blocking 关系需要 GHES 3.19+。本仓库 issues-2.0 相关的端到端用例可在 acceptance/testdata/issues-2.0/ 中找到(覆盖 create/edit 的类型与 parent、子任务编辑、按类型过滤列表、Issue 2.0 字段的 view 等场景)。
上传图片与视频附件(--attach)
--attach <path> 可用于 gh issue create、gh issue edit、gh issue comment、gh pr create、gh pr edit、gh pr comment。
- 多文件:重复
--attach即可,例如gh issue comment 12 --attach ./before.png --attach ./after.png。 - 支持的格式:
png、jpg、jpeg、gif、webp、svg、mp4、mov、webm。 - 图片 alt 文本:在路径后用
#追加,例如gh pr create --attach './login.png#The login error state';务必加引号让 shell 不把#当注释。未提供 alt 文本时使用文件名。 - 路径解析:
--attach路径与正文中的本地 Markdown 引用可相对gh的运行目录解析,也可用绝对路径。 - 正文引用改写:若 body 引用了某个附件路径,
gh会把这个 Markdown 引用改写为上传后的 URL,并保留原有 alt 文本;否则gh把附件追加到正文末尾。示例:gh pr edit 23 --body 'error' --attach ./login.png。
视频行为比较特殊:
- 视频不能携带 alt 文本;
- 独立的
recording会变成裸播放器 URL,行内视频图片则变成链接; - 引用式视频图片
![recording][clip]+[clip]: ./repro.mp4会被拒绝——请改用引用式链接。
使用限制:
gh issue create/gh pr create:--attach不能与--web同用;gh pr create --attach也不能与--dry-run同用;gh issue edit:--attach一次只能编辑一个 issue;gh issue comment/gh pr comment:--attach不能与--web或--delete-last同用;可以单独使用,也可以与--edit-last或--body/--body-file/--editor之一组合。
权限与主机要求:上传需要 GitHub.com 或 GHE.com 租户,token 为 OAuth token、classic PAT 或 fine-grained PAT,且具备仓库的 WRITE、MAINTAIN 或 ADMIN 权限;GitHub Enterprise Server 与 GitHub App token 不受支持。
失败语义:上传在第一个失败处停止。若此前已有文件成功上传,gh 仍会写出这些附件并返回非零退出码。实现细节见 internal/attachments/attach.go:UploadAndAttach 按序上传、首个失败即 break,并把成功上传的 URL 回填进 Markdown 引用;返回计数大于 0 时调用方必须写出改写后的 Markdown,否则这些无法撤销、也没有删除端点的附件就会被孤儿化。create/edit 命令还会打印 issue 或 PR 的 URL。
Discussions(gh discussion)
预览命令集,未来可能变化。子命令如下:
gh discussion list [--state open|closed|all] [--category <name>] [--author <handle>] [--label <name>,...] [--answered] [--search <query>] [--sort created|updated] [--order asc|desc] [--limit N] [--after <cursor>] [--json <fields>] [--web]:列出仓库的讨论。--state默认 open,--sort默认 updated,--order默认 desc。--answered是三态布尔(对 Q&A 分类,--answered=false表示"未回答")。gh discussion view {<number>|<url>|<comment-id>|<comment-url>} [--comments] [--order oldest|newest] [--limit N] [--after <cursor>] [--json <fields>] [--web]:显示讨论正文;加--comments查看评论,或把 comment ID/URL 作为参数传入来列出该评论的回复。没有--replies标志;传了评论参数时--comments会被拒绝。--order(默认 newest)、--limit、--after只作用于评论与回复的列表。gh discussion create [--title <t>] [--body <b> | --body-file <path>] [--category <name>] [--label <name>,...]:创建讨论。非交互下--title、正文(--body或--body-file)与--category都是必需的;省略任意一项,在终端里才会进入交互提示。gh discussion edit {<number>|<url>} [--title <t>] [--body <b>] [--body-file <path>] [--category <name>] [--add-label <name>,...] [--remove-label <name>,...]:编辑标题、正文、分类或标签。gh discussion comment {<number>|<discussion-url>|<comment-id>|<comment-url>} [--body <b>] [--body-file <path>] [--edit] [--delete] [--yes]:对讨论添加顶级评论(给 discussion 参数时)或对评论添加回复(给 comment 参数时);--edit或--delete用于更新/删除评论或回复,需要 comment ID 或 URL;--yes跳过--delete的确认。
输出约定:只有 list 与 view 支持 --json/--jq/--template;create 与 edit 打印讨论 URL;comment 打印讨论评论(或回复)URL。
命令骨架与参数校验位于 pkg/cmd/discussion/(list、view、create、edit、comment 各自独立子包),其 API 交互通过 pkg/cmd/discussion/client/client.go 完成;仓库的端到端覆盖见 acceptance/testdata/discussion/ 下的 *.txtar。
免克隆读取文件与目录(gh repo read-file / gh repo read-dir)
这两个预览命令通过 API 直接读取仓库内容,无需 clone,并遵循 --repo OWNER/REPO(-R)与 --ref <branch|tag|commit>(省略时用默认分支)。实现见 pkg/cmd/repo/read-file/read_file.go 与 pkg/cmd/repo/read-dir/。
gh repo read-file <path> [--ref <ref>] [--output <path> [--clobber]] [--allow-escape-sequences] [--json <fields>] [--jq <expr>]:打印文件内容。- 非 TTY 下原始字节直接写入 stdout(适合管道);二进制文件在管道时会原样写出,但在 TTY 上会被拒绝(提示用
--output存盘或管道 stdout)。 - 默认情况下,含终端转义序列的文件会被拒绝读取(防止恶意内容操控下游终端),需要
--allow-escape-sequences才放行。判断依据是文件内容中是否包含转义序列(read_file.go)。 --output <path>(-o)改为写盘而非输出到 stdout:路径尾带斜杠时按目录处理并使用远端文件名写入其下;--clobber允许覆盖已存在文件。写盘始终包含原始字节,不受转义序列检查约束(等价于隐式--allow-escape-sequences)。实现上还拒绝了输出路径为 symlink 的场景(read_file.go)。--output与--json互斥(校验见 read_file.go)。--json字段:name、path、gitSHA、size、type、encoding、content(base64 编码)。另外源码中fileFields还含url、htmlUrl、gitUrl、downloadUrl;若 API 未内联返回内容(大文件以encoding: "none"标记),gh 会在你需要content字段或普通输出时自动补拉原始字节(loadContent,见 read_file.go)。
- 非 TTY 下原始字节直接写入 stdout(适合管道);二进制文件在管道时会原样写出,但在 TTY 上会被拒绝(提示用
gh repo read-dir [<path>] [--ref <ref>] [--json <fields>] [--jq <expr>]:列目录,无 path 时列出仓库根。- 非 TTY 输出为制表符分隔,依次为类型、名称、八进制权限模式、字节大小。
--json字段:name、path、type、gitType、mode、modeOctal、gitSHA、size、submodule。- path 指向文件时报错并提示应使用
read-file;反过来也一样。
兜底:gh api 拿 --json 没暴露的数据
类型化命令偶尔覆盖不到某些数据,官方建议直接回退到 gh api。典型例子:
- PR 上 review 线程的评论:
gh api repos/{owner}/{repo}/pulls/{n}/comments——gh pr view --comments只展示 issue 级别的评论。 - 任意 GraphQL:
gh api graphql -f query='...' -F var=value。底层实现会把query、operationName之外的键归入variables(见 pkg/cmd/api/http.go 的groupGraphQLVariables),并自动使用 GraphQL 端点。 - REST 快捷方式:
gh api repos/{owner}/{repo}/...。当你在带已识别 remote 的仓库内运行时,{owner}/{repo}占位符会被自动填充(该路径未经转义、原样拼接,见 http.go);若想要确定性行为,请显式写死占位符内容。
认证状态查询
gh auth status:打印当前生效的主机(hosts)、用户,以及正在被采纳的环境变量(若有)。gh auth status --json也受支持,适合 Agent 用结构化方式判断是否已登录。
其他 Agent 常用注意事项
gh pr checkout <n>会切换分支;如果只需要读取,用gh pr diff <n>或gh pr view <n>,避免改变工作区状态。gh pr checkout <n> --worktree <path>:把 PR 检入位于<path>的 git worktree,而不是切换当前分支。gh issue develop <n> --checkout:为 issue 创建关联分支并检出来。加--worktree <path>则在 worktree 中检出该分支;--worktree依赖--checkout、不能为空、不能与--list组合。对应场景在 acceptance/testdata/issue/issue-develop-worktree.txtar 等用例中有覆盖。- 环境变量:
NO_COLOR、CLICOLOR_FORCE、GH_FORCE_TTY均被识别。在 Agent 测试框架里想要 TTY 风格输出(颜色、表格、pager、交互性)时设置GH_FORCE_TTY=1;不需要就保持不设置。
小结:Agent 调用 gh 的黄金规则
把本指南压缩为几条可记忆的规则:
- 机器要数据就给
--json;先空跑--json枚举字段,再精挑字段;过滤用--jq,塑形用--template,但先--help确认-T的语义。 - 列表皆有限制:默认约 30,
-L放大,--paginate翻页,真实总数走 GraphQLtotalCount。 - 仓库解析来自 cwd,异地操作一律
-R OWNER/REPO。 - 跨仓库/按作者过滤用
search且 qualifier 拆成裸 token;bot 作者用--app;同仓库过滤用list --search "..."。 - 关联数据(
subIssues/blockedBy/blocking)是{nodes,totalCount}对象且 nodes 有上限,务必比对totalCount防止截断误判。 - 附件上传在首个失败处停止,成功写入的部分无法撤销。
--json覆盖不了就gh api兜底,REST 占位符自动填充、GraphQL 变量自动分组。- 非 TTY 下 gh 已自动跳过 pager/颜色/交互,不要画蛇添足设
GH_PAGER;要 TTY 行为才设GH_FORCE_TTY=1。
本文所有命令行行为与参数均以当前仓库 skills/gh/SKILL.md 及其对应源码、测试实现为准;在真实环境中以 gh <command> --help 的在线输出为最终依据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00