README
What does the watch print about a pull request, and what ends the watch?
Binds: architecture, code
The script pull-request-watch-for-agents runs watch.main.
Who it is for
The watch is for a coding agent that opened a pull request. The agent misses feedback, a failed check, a conflict and the merge unless something reports each one. The watch prints one line for each finding, and the agent answers each line as it arrives.
One line is one finding
A coding agent runs the watch under a tool that reports each line of output. So each line names one finding that asks the agent for work. No other line prints.
These findings print a line:
- The merge, with the merged commit. A close with no merge. Each one ends the watch with exit code 0.
- A conversation comment, a diff comment or a review.
- A check run of the head commit that ends with a conclusion in
FAILED:action_required,cancelled,failure,startup_failureortimed_out. Only the newest run of each check name counts. A rerun, or a run for a later event such as a label, adds a run of the same name to the same head commit. GitHub gives the later run the higher id. The line names the head commit by the firstHEAD_DIGITSdigits of its hash. - A conflict with the base branch. The line names the base branch and the pull
request. It states the goal: resolve the conflict, then push. It names no
strategy, such as a rebase or a merge, and no
gitcommand. Only the agent knows how it built its branch. - A commit status of the head commit in
STATUS_FAILED:errororfailure. The line names the context, the state and the link of the status. A status with no link takes the link of the pull request. A commit status is not a check run, so the check runs do not hold it.pr-commit-messageis such a status. - A change of the base branch. When its parent merges, GitHub retargets a pull
request. The line names the new base and the first base that the watch read.
It names no
gitcommand, because GitHub can rebase the branch already.
A new finding prints at once. A comment that somebody edits is a new finding, because the edit is new feedback.
An open finding prints again
While a finding stays open, its line prints again each REPEAT_SECONDS. The
repeated line starts with REPEATED, which reads Still open:. The rest of the
line is the same as the first line. A tool that holds output back then still
gives the agent a turn.
Each kind of finding stays open while its condition holds:
- A conflict stays open while
mergeableof the pull request readsfalse. GitHub returnsnullwhile a background job computes the value. The watch then keeps the last value that it read. - A failed check stays open while it is the newest run of its name on the current head commit. A push of a new head commit closes it. A later run of the same name on the same head closes it too.
- A failed commit status stays open while the latest status of its context on
the current head commit is in
STATUS_FAILED. A push of a new head commit closes it. - A change of base prints once, and never again. When the base reads as the first base again, it closes.
- A comment or a review stays open until a later record handles it. Two kinds of record handle it: a head commit, and a comment or a review from the author of the pull request. The watch reads the committer date of the head commit as the moment of the push. An amend or a rebase moves that date. A push of an older commit does not.
Some feedback prints once, and never again. A comment or a review from the
author of the pull request is the reply, so it asks for no work. A review with
the state APPROVED asks for no work either.
A finding that closes and opens again prints as a new finding.
The watch reads REST and polls
The watch reads the REST API of GitHub through gh api. It reads no GraphQL.
cli_tool_runtime_for_git_repos.github.pull_requests states why.
A local session has no address that GitHub can reach, so the watch polls. The
default poll is DEFAULT_POLL_SECONDS apart.
The watch reads comments from the committer date of the head commit. A head commit handles each older comment, so that bound skips no open finding.
When a poll fails, one line names the reason, and the watch continues. Three
events fail a poll: gh exits with an error, gh runs past its timeout, or
GitHub answers something that is not the JSON that the watch expects. A second
failure with the same reason prints nothing. So silence means no open finding,
and never a broken poll.
The watch runs until the pull request ends
The watch has no timeout. The merge, the close or a kill from a tool ends it. A tool that kills the watch tells the agent. The agent then starts the same command again.
Each start reads the current state of the pull request. So a new watch prints each open finding as a new finding. No open finding falls between two watches.
A reply of the author prints once, and an earlier watch printed it. So a start prints nothing for a reply of the author that it finds already there. A session on a laptop runs on the token of the operator. So a comment that the operator typed while no watch ran reads as such a reply, and it prints nothing.
A repository that wraps the watch passes its own command to main as command.
The help and the errors then name that command. The default is COMMAND.
The pull request comes from the caller or the branch
A caller names the pull request by number. With no number, the watch takes the
one open pull request of the current branch. When the branch carries no open
pull request, or more than one, the watch exits with code 2. A git or gh
call that fails or runs past its timeout there also exits with code 2, and the
error names the failure.