watch-github
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseWatch GitHub
监控 GitHub
One daemon per Claude Code session, started by the session as a background task, polls GitHub for the pull requests and branch tips that session watches. The session subscribes once and keeps working. Comments, reviews, CI verdicts, head moves, label moves, conflicts with the base, merges, and closes arrive as messages in the session, one message per poll, and as lines in a log any other agent can read.
The daemon is the skill's, installed beside this one; this skill is its GitHub source. That page owns the envelope, the runtime directory and files, the file sink, and the two-hour cap. This page owns what is polled and what the events say.
/session-eventsOperating envelope: one Claude Code session, one user, the daemon started once by that session, GitHub reachable through an authenticated .
gh每个 Claude Code session 启动一个 daemon,由 session 作为 background task 启动,轮询 GitHub 上该 session 关注的 PR 和 branch tips。Session 只需订阅一次,便可继续工作。Comments、reviews、CI verdicts、head moves、label moves、与 base 的 conflicts、merges 和 closes 会作为消息抵达 session,每次 poll 一条消息,同时也会写入任何其他 agent 都能读取的日志。
Daemon 属于 skill,该 skill 安装在此 skill 旁边;本 skill 是 daemon 的 GitHub source。该页面负责 envelope、runtime directory 及其文件、file sink 和两小时上限。本页面负责轮询什么,以及 events 传达什么。
/session-events运行 envelope:一个 Claude Code session、一个用户、由该 session 启动一次的 daemon,以及通过已认证的 可访问的 GitHub。
ghThe Specimen
示例
A push went up and its PR is open. Once per session the daemon is started as a background task, then one line subscribes the PR with its gate workflow:
bash
bun "<skill-dir>/scripts/github-events.mts" daemon # once per session, as a BACKGROUND task
bun "<skill-dir>/scripts/github-events.mts" watch owner/repo#12 --expect-workflow checksThe session goes back to work. Minutes later the gate finishes, and this arrives in the conversation unasked:
text
<cross-session-message from="uds:/run/user/1000/session-events/4242.github-events.sock" from-name="github-events" from-mode="bypass">
PR owner/repo#12: CI green on 1a2b3c4d5e6f: 3 workflows
https://github.com/owner/repo/pull/12/checks
automated notice from github-events; no reply needed
</cross-session-message>No turn was spent on "nothing changed yet". The daemon is the session's own child: its messages arrive at once, named in the envelope, and it exits on its own when the session does.
github-events一次 push 已发出,相应 PR 已开启。每个 session 只启动一次 daemon 作为 background task,然后用一行命令订阅该 PR 及其 gate workflow:
bash
bun "<skill-dir>/scripts/github-events.mts" daemon # once per session, as a BACKGROUND task
bun "<skill-dir>/scripts/github-events.mts" watch owner/repo#12 --expect-workflow checksSession 随后继续工作。几分钟后,gate 完成,以下内容会未经询问便出现在对话中:
text
<cross-session-message from="uds:/run/user/1000/session-events/4242.github-events.sock" from-name="github-events" from-mode="bypass">
PR owner/repo#12: CI green on 1a2b3c4d5e6f: 3 workflows
https://github.com/owner/repo/pull/12/checks
automated notice from github-events; no reply needed
</cross-session-message>没有 turn 消耗在“还没有变化”上。Daemon 是 session 自己的子进程:消息会立即抵达,在 envelope 中命名为 ,并在 session 结束时自行退出。
github-eventsWhen to Apply
适用场景
- A just ran on a branch with CI, or a PR was just opened: watch the PR.
git push - Waiting on a PR: a review, a reply, a merge, or "did CI pass?"
- A PR just merged, or a direct push landed on the mainline: watch the mainline branch, , once per session; every tip it gets from then on is judged.
owner/repo:main
- 刚在带 CI 的 branch 上运行,或 PR 刚创建:监听该 PR。
git push - 等待 PR:review、reply、merge,或“CI 通过了吗?”
- PR 刚合并,或 direct push 落到 mainline:每个 session 监听一次 mainline branch ;此后它获得的每个 tip 都会进行判定。
owner/repo:main
Targets
Targets
bash
bun "<skill-dir>/scripts/github-events.mts" watch owner/repo#12 --expect-workflow checks --expect-workflow lint
bun "<skill-dir>/scripts/github-events.mts" watch owner/repo:main --expect-workflow checks| Target | Events | Ends |
|---|---|---|
| comment, review, ci, head, label, conflict, merged, closed, trouble | at the merge or close |
| ci, head, trouble | never; every push to the branch is a new head |
Issues and security alerts are future target forms of this source, never new commands or skills.
--expect-workflow <name>name:.github/workflows/watchWithout a daemon, exits 3 and prints the exact start command. A PR's first sighting reports none of the comments, reviews, or labels already on it; only what lands afterwards is an event. Its CI verdict, its merged or closed state, and a conflict it already carries are judged at once.
watchbash
bun "<skill-dir>/scripts/github-events.mts" watch owner/repo#12 --expect-workflow checks --expect-workflow lint
bun "<skill-dir>/scripts/github-events.mts" watch owner/repo:main --expect-workflow checks| Target | Events | Ends |
|---|---|---|
| comment、review、ci、head、label、conflict、merged、closed、trouble | 合并或关闭时结束 |
| ci、head、trouble | 永不结束;对 branch 的每次 push 都会产生一个新 head |
Issues 和 security alerts 是此 source 未来的 target 形式,不会新增 commands 或 skills。
--expect-workflow <name>.github/workflows/name:watch没有 daemon 时, 会以退出码 3 退出,并打印精确的启动命令。PR 首次被发现时,不会报告其已有的 comments、reviews 或 labels;只有此后发生的内容才构成 event。它的 CI verdict、merged 或 closed 状态,以及已经存在的 conflict 都会立即判定。
watchEvents
Events
Each message holds one poll's events, each with a URL, and the closing line . Never reply to it. Every text is structure: who, where, how many, which state, which SHA. Nothing a comment, a review, or a build log said is in a message; the URL is where to read it.
automated notice from github-events; no reply needed| Kind | The line says | What to do |
|---|---|---|
| | open the URL and triage it as a review round |
| | the same, per the |
| | green: carry on; red: |
| | a head you did not push: pull before touching the branch |
| | a label that names you or a gate: act on it; any other is information |
| | rebase onto the base and push; the line comes again when a push leaves the conflict standing |
| | stop babysitting; the mainline's CI is the branch target's business |
| | stop babysitting |
| | tooling trouble, never a red or green pipeline; Gotchas below |
| busy | not a kind: a poll with an event, a pending rollup, or a head still being judged keeps the 60 s interval; each quiet poll doubles the wait, 120 s, 240 s, then 300 s | nothing |
A branch target's lines open with instead. The comment line counts every author, the session's own login included: an owner who shares that login is not told apart from the agent, and a decision of theirs is never dropped as an echo. A review's inline comments are counted on its line and not again as comments.
branch owner/repo:main:A review the login drafts is nothing until submitted, then an event under the same id. Two edges stay silent or odd: a draft submitted after newer activity pushed it out of its read window (ten reviews, or three replies in its thread) is not found, and a comment older than the watch that deletions pull back into the window is reported once.
After a push on either side GitHub recomputes the merge test and answers UNKNOWN meanwhile. Nothing is said until it settles.
每条消息包含一次 poll 的 events,每个 event 都带有一个 URL,末尾为 。永远不要回复它。每段文本都是结构化信息:谁、在哪里、多少、哪个状态、哪个 SHA。Comment、review 或 build log 中的任何内容都不会出现在消息里;应通过 URL 阅读。
automated notice from github-events; no reply needed| Kind | 消息内容 | 操作 |
|---|---|---|
| | 打开 URL,将其作为 review 轮次进行分流处理 |
| | 同上,遵循 |
| | green:继续;red:运行 |
| | 对于不是你 push 的 head:触碰 branch 前先 pull |
| | 若 label 指名你或某个 gate,则据此行动;其他 label 仅作信息 |
| | 将 branch rebase 到 base 并 push;当 push 后 conflict 仍存在时,该消息会再次出现 |
| | 停止盯守;mainline 的 CI 属于 branch target 的事务 |
| | 停止盯守 |
| | 工具故障,绝非 red 或 green pipeline;见下方 Gotchas |
| busy | 并非 event kind:一次 poll 若存在 event、pending rollup,或 head 仍在判定中,则保持 60 s 间隔;每次 quiet poll 都会将等待时间翻倍,依次为 120 s、240 s、300 s | 无操作 |
Branch target 的消息行以 开头。Comment 行会计入每一位 author,包括 session 自己的 login:如果 owner 与该 login 相同,也不会将其与 agent 区分开;其 decision 绝不会作为 echo 被丢弃。Review 的 inline comments 计入 review 行,不会再次作为 comments 计入。
branch owner/repo:main:由该 login 起草的 review 在提交前不构成任何内容;提交后,会以同一 id 产生 event。两个边缘情况会保持静默或表现异常:在更新的 activity 将其推出 read window(十次 reviews,或其 thread 中三次 replies)之后才提交的 draft 不会被发现;而早于 watch、但因 deletions 被拉回 window 的 comment 会被报告一次。
任一侧发生 push 后,GitHub 会重新计算 merge test,期间返回 UNKNOWN。在结果稳定前不会发送任何消息。
The CI rules
CI 规则
- Green is every named gate's latest run passed (skipped counts), or, with no gate named, the PR's aggregate rollup . With gates named, a failed run outside them is listed after
SUCCESS, and a bystander turning red later is a new; other checks red:event, since theciskill's green counts every check./pr-landing-discipline - Red is any named gate's latest run failed or cancelled with no newer run, or, with no gate, the rollup or
FAILURE. The red line names up to 50 failed jobs per suite, each as workflow, job, and first failed step, and any gate with no run afterERROR.; no run of - A gate with no run is pending while the rollup is pending, and one event once the rollup settled without it, or after two polls on a head with no checks at all: a typo in the name, or a workflow that does not trigger on this ref.
trouble - No checks and no gate is no CI, not pending: nothing is said, and the head idles until a rollup appears. Name the gate with when a run must register.
--expect-workflow - The unit is the workflow. Re-triggers stack runs on one SHA; the newest run of each workflow is judged, and an older cancelled one is superseded.
- After a verdict, the head is judged again when something moves: a gated head on every poll (a gate's re-run need not move the rollup), a gateless one when the rollup changes. The same verdict stays silent; a different one is a new event. A new head is judged from nothing.
ci
- Green 表示每个指定 gate 的最新 run 均已通过(skipped 也算通过);若未指定任何 gate,则表示 PR 的 aggregate rollup 为 。指定 gates 时,gate 之外失败的 run 会列在
SUCCESS之后;之后某个 bystander 变红也会产生新的; other checks red:event,因为ciskill 的 green 判定会计入每一个 check。/pr-landing-discipline - Red 表示任一指定 gate 的最新 run 失败或 cancelled 且没有更新的 run;若未指定 gate,则表示 rollup 为 或
FAILURE。Red 行会按 suite 列出最多 50 个失败 jobs,每个都包含 workflow、job 和首个 failed step;没有 run 的 gate 会列在ERROR之后。; no run of - 没有 run 的 gate 在 rollup 仍为 pending 时视为 pending;当 rollup 已 settled 但仍无该 run 时,或某个 head 连续两次 poll 都没有任何 checks 时,会产生一次 event:这通常表示名称拼写错误,或 workflow 不会在此 ref 上触发。
trouble - 既无 checks 又无 gate 表示没有 CI,而非 pending:不会发送任何消息,head 会保持 idle,直到出现 rollup。需要某次 run 被登记时,应使用 指定 gate。
--expect-workflow - 判定单位是 workflow。 同一 SHA 上的重新触发会叠加多个 runs;每个 workflow 的最新 run 都会被判定,而较早的 cancelled run 会被视为 superseded。
- 得出 verdict 后,当有内容移动时,会再次判定 head:指定 gate 的 head 每次 poll 都判定,未指定 gate 的 head 在 rollup 变化时判定。相同 verdict 保持静默;不同 verdict 是新的 event。新 head 从零开始判定。
ci
Polling Budget
Polling Budget
GitHub throttles GraphQL in its own bucket and the Actions REST endpoints (, , ) in a secondary one that parallel watchers lock for longer than a run lasts. The daemon reads GraphQL only, once a minute while something moves and, after the skill's ramp, every five minutes while nothing does.
gh run listgh run watchgh run view/session-eventsOne poll costs one request per repository: every watched PR's row, every watched branch's tip, and the check suites of each head still being judged or carrying named gates, aliased in one query (one more request per 30 PRs of a repository). On top of that:
- one detail read per PR whose row moved (comments, reviews, threads), paged back only to the last activity already seen
- one paged read for a head with more than 100 check suites
- one read per red verdict, naming up to 50 failed jobs per suite with their steps
At steady state, nothing moving, that is one request per repository every five minutes. Never run , spawn a watcher subagent, or read the PR by hand between messages: the daemon is the one poller for this session.
gh run watchWhen a workflow's runs share a group key, a newer push cancels the older head's pending run, so only the newest head gets a verdict; wait for it before pushing again.
concurrencyGitHub 会对自己的 bucket 中的 GraphQL,以及 secondary bucket 中的 Actions REST endpoints(、、)进行限流;parallel watchers 对 secondary bucket 的锁可能会持续到 run 结束之后。Daemon 只读取 GraphQL:有内容移动时每分钟一次;在 skill 的 ramp 之后,无变化时每五分钟一次。
gh run listgh run watchgh run view/session-events每次 poll 对每个 repository 消耗一个 request:每个被监听 PR 的 row、每个被监听 branch 的 tip,以及每个仍在判定或带指定 gates 的 head 的 check suites;这些会在同一个 query 中做 alias(每个 repository 每 30 个 PR 再增加一个 request)。除此之外:
- PR row 有变化时,每个 PR 进行一次 detail read(comments、reviews、threads),只分页回溯到已见过的最后一次 activity
- check suites 超过 100 个的 head 进行一次 paged read
- 每个 red verdict 进行一次 read,按 suite 列出最多 50 个 failed jobs 及其 steps
稳定状态下没有内容移动,也就是每个 repository 每五分钟一个 request。绝不要在消息之间运行 、启动 watcher subagent,或手动读取 PR:daemon 是此 session 唯一的 poller。
gh run watch当多个 workflow 的 runs 共享同一 group key 时,更新的 push 会取消旧 head 上尚未完成的 run,因此只有最新 head 会得到 verdict;再次 push 前应等待它完成。
concurrencyCommands
Commands
text
usage: github-events.mts <command> [options]
daemon [--interval <s>] [--mode bypass|prompting] [--id <name> [--pid <pid>]]
watch <target> [the source's flags] [--id <name>]
unwatch <target> | --all [--id <name>]
wait [--since <id>] [--timeout <s>] [--id <name>]
poll | status | stop [--id <name>]
the daemon serves the Claude Code session named by CLAUDE_CODE_MESSAGING_SOCKET and exits with it;
--id <name> names a file-sink daemon for a non-Claude agent instead, --pid <pid> ends it with that process;
--mode is the session's permission mode, carried in the envelope: bypass (the default) or prompting;
--interval is 60 s by default, 1 to 86400
targets: <owner/repo>#<n> for a pull request, <owner/repo>:<branch> for a branch tip
--expect-workflow <name> names a workflow whose run must appear on the head before CI reads green; repeatable
the session-events skill must be installed beside this one; without it every command exits 2 naming the install: npx skills add Vivswan/skills -g --skill session-events
files: <dir>/<pid>-<start>.github-events.{state.json,events.jsonl,log} and <dir>/<pid>.github-events.sock for a session; <id>.github-events.* for a file sink;
<dir> is $SESSION_EVENTS_DIR, else $XDG_RUNTIME_DIR/session-events, else $CLAUDE_CODE_TMPDIR/session-events-<uid>, else <os tmpdir>/session-events-<uid>, else /tmp/session-events-<uid>
prints: one message per poll with alerts, wrapped in <cross-session-message from="uds:<socket>" from-name="github-events" from-mode="<mode>"> and ending "automated notice from github-events; no reply needed";
wait prints the new events as JSON lines, then cursor: <id>
exits: 0 ok (stop with no daemon running, and a second daemon start, are ok too); 2 usage, a state file that is not JSON, or no runtime directory;
3 no daemon answers for this session: stderr names the socket and the background command that starts one; 4 wait only: the timeout passed with no newer line| Exit | Meaning |
|---|---|
| 0 | ok; |
| 2 | usage ( |
| 3 | no daemon answers for this session: stderr names the socket and the exact background command that starts one |
| 4 | |
--interval--idwait/session-events{"id":17,"at":"2026-10-12T14:03:20Z","repo":"owner/repo","pr":12,"sha":"1a2b...","kind":"ci","text":"...","url":"..."}branchprIn a fleet, the lead's session runs the daemon as its own background task, matching the session, and watches the fleet's PRs; a builder never runs there. When the lead wants a builder to act on a review line, it forwards that line by one message.
--modewatchtext
usage: github-events.mts <command> [options]
daemon [--interval <s>] [--mode bypass|prompting] [--id <name> [--pid <pid>]]
watch <target> [the source's flags] [--id <name>]
unwatch <target> | --all [--id <name>]
wait [--since <id>] [--timeout <s>] [--id <name>]
poll | status | stop [--id <name>]
the daemon serves the Claude Code session named by CLAUDE_CODE_MESSAGING_SOCKET and exits with it;
--id <name> names a file-sink daemon for a non-Claude agent instead, --pid <pid> ends it with that process;
--mode is the session's permission mode, carried in the envelope: bypass (the default) or prompting;
--interval is 60 s by default, 1 to 86400
targets: <owner/repo>#<n> for a pull request, <owner/repo>:<branch> for a branch tip
--expect-workflow <name> names a workflow whose run must appear on the head before CI reads green; repeatable
the session-events skill must be installed beside this one; without it every command exits 2 naming the install: npx skills add Vivswan/skills -g --skill session-events
files: <dir>/<pid>-<start>.github-events.{state.json,events.jsonl,log} and <dir>/<pid>.github-events.sock for a session; <id>.github-events.* for a file sink;
<dir> is $SESSION_EVENTS_DIR, else $XDG_RUNTIME_DIR/session-events, else $CLAUDE_CODE_TMPDIR/session-events-<uid>, else <os tmpdir>/session-events-<uid>, else /tmp/session-events-<uid>
prints: one message per poll with alerts, wrapped in <cross-session-message from="uds:<socket>" from-name="github-events" from-mode="<mode>"> and ending "automated notice from github-events; no reply needed";
wait prints the new events as JSON lines, then cursor: <id>
exits: 0 ok (stop with no daemon running, and a second daemon start, are ok too); 2 usage, a state file that is not JSON, or no runtime directory;
3 no daemon answers for this session: stderr names the socket and the background command that starts one; 4 wait only: the timeout passed with no newer line| Exit | 含义 |
|---|---|
| 0 | 正常;在没有 daemon 运行时执行 |
| 2 | 用法错误( |
| 3 | 此 session 没有 daemon 响应:stderr 会指出 socket 和启动 daemon 的精确 background command |
| 4 | 仅适用于 |
--interval--idwait/session-events{"id":17,"at":"2026-10-12T14:03:20Z","repo":"owner/repo","pr":12,"sha":"1a2b...","kind":"ci","text":"...","url":"..."}branchpr在 fleet 中,lead 的 session 会将 daemon 作为自己的 background task 运行, 与 session 保持一致,并监听 fleet 的 PRs;builder 不会在那里运行 。当 lead 希望 builder 根据某条 review 消息采取行动时,它会通过一条消息转发该消息。
--modewatchGotchas
Gotchas
| What you see | What it means | What to do |
|---|---|---|
| no daemon for this session | run the printed command as a background task, then |
| the daemon stops on its own about two hours in, the session still running | the harness's cap on a background task; the | start it again with the same command; the saved targets carry on |
a | gh, the network, or GitHub refused three polls in a row; | fix what the daemon log names; a stale token needs |
a | the named workflow never registered a run on the head | check the name against |
a | the PR or branch answers null: deleted, renamed, or no access | |
a | this one target's detail or paged suites read failed three polls in a row while the repository read kept answering | read the daemon log; the target is retried every poll and the counter resets on a success |
| a red line with workflow names only | the failed-jobs read failed; the verdict stands | |
| | install and authenticate the GitHub CLI; until then, report the push with the PR URL and never silently skip the watch |
A local subagent spawned with the Agent tool shares its session's inbox socket and pid, so a run inside it subscribes the session's daemon, and the events land with the session. A teammate from agent teams is a separate process with its own socket and runs its own daemon.
watch| 你看到的情况 | 含义 | 操作 |
|---|---|---|
| 此 session 没有 daemon | 将打印出的 command 作为 background task 运行,然后再次执行 |
| daemon 在 session 仍运行时约两小时后自行停止 | harness 对 background task 的上限;该规则由 | 使用相同 command 再次启动;已保存的 targets 会继续生效 |
连续三次 poll 失败后出现 | gh、网络或 GitHub 连续三次拒绝 poll;首先检查 | 修复 daemon log 指出的问题;过期的 token 需要执行 |
| 指定 workflow 从未在 head 上登记 run | 对照 |
| PR 或 branch 返回 null:已删除、已重命名或无权访问 | 执行 |
| 在 repository read 持续正常响应的同时,这个 target 的 detail 或 paged suites 连续三次 poll 读取失败 | 读取 daemon log;target 每次 poll 都会重试,成功后计数器会重置 |
| Red 行只有 workflow 名称 | failed-jobs read 失败;verdict 仍然有效 | 在 run URL 的 id 上运行 |
缺少 | daemon 从首次 poll 起就会失败 | 安装并认证 GitHub CLI;在此之前,报告 push 和 PR URL,绝不能静默跳过 watch |
使用 Agent tool 启动的 local subagent 共享其 session 的 inbox socket 和 pid,因此在其内部运行 会订阅 session 的 daemon,events 也会随 session 落地。Agent teams 中的 teammate 是独立进程,拥有自己的 socket,并运行自己的 daemon。
watch