Repository navigation
feat: watch linked pull requests and wake the agent (#639) - #658
Merged
Merged
Conversation
A watch on a linked pull request records what its agent was told: failed and passed checks, a remark watermark, conflict and the comment-only wake count. The fold reports newly failed checks, the required gate passing, remarks by anyone but the viewer and entering conflict, and renders the exact wake and end messages. The GitHub reads are a batched one-point fingerprint, a detail read and an activity read whose lists past ten pages, or review-thread replies that cannot all be read, are reported incomplete.
A pass every two minutes after the previous one reads each watched pull request once per project, gated by the fingerprint, with the 10- and 30-minute backstops. A wake is a server-authored message queued behind any running turn and persisted on the watch with its watermark; it is cleared when the provider accepts it and delivered again after a restart. Stop, settle, archive, unwatch, merge, close, eight failed reads and ten comment-only wakes end a watch; a rate limit never does. The agent gets watch_pull_request and unwatch_pull_request and the watching half of the instruction block.
The Pull requests panel shows an eye on watched rows that stops the watch, Watch for changes and Stop watching in the row menu, and what the agent sees: the injected tools and instructions as the host sends them. A watched thread shows Waiting with the pull request it watches; wakes and ends raise one notice per thread, and a wake renders in the transcript as a disclosure, never a user bubble.
# Conflicts: # crates/protocol/src/lib.rs # crates/ui/src/shell.rs
# Conflicts: # crates/protocol/src/lib.rs # crates/runtime/src/app/settlement.rs # crates/runtime/src/app/snapshots.rs # locales/en.yml # locales/zh-CN.yml
Tryanks
marked this pull request as ready for review
October 8, 2026 18:03
This was referenced Oct 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
crates/core/src/pull_request_watch.rs: the watch, the fold and the exact wake/end texts (“Tcode”, no inbox wording).crates/services/src/github/pull_request_watch.rs: fingerprint (exact upstream selection), detail and activity reads; the two deviations below.crates/runtime/src/app/pull_request_watch.rs: pass, commit, delivery, acknowledgement, Stop/settle/archive hooks, user command and MCP handling;queue_automatic_turnis the one owner of “queue a host/child-authored turn and start the provider in the background”, now shared with orchestrate callbacks.watch_pull_request,unwatch_pull_request; the instruction block gains the watching half.SessionStatus.pull_request_tools, whenever the thread's provider would receive the tools — MCP-capable provider and the server mounted — so it is visible before the first turn).Deviations from upstream, each with its own test: the 30-minute fingerprint reread also drops that pull request's cached review-thread tails, one timer for both (
the_half_hour_reread_pages_cached_review_thread_tails_again); a list past ten pages is incomplete and never advances the watermark (a_list_past_ten_pages_is_incomplete).Closes #639
Evidence
After: desktop light — watched row (eye) and the Waiting chip; phone light — the thread list's Waiting line and the Pull requests sheet with the eye (synthetic
sample/projectprofile, English):crates/core/src/pull_request_watch.rs): required late-passed job; rerun on new head; same-second and edited remarks; viewer ≠ author; conflict once; loop resets; message caps/snippets and parse-back.crates/runtime/src/app/pull_request_watch_tests.rs, realGitHubApiagainst the loopback HTTP fixture, scripted provider): 20 rate-limited passes wake nobody and end nothing (that a rate limit never counts toward the 8-failure end is proven only through the internalfailuresmap, not by an observable end); two threads share one read with separate watermarks; a quiet fingerprint-backed pass makes only the fingerprint request, moved checks read the detail alone, a check still running is read again on a quiet pass ([FINGERPRINTS, DETAIL]), the 30-minute backstop rereads activity; the snapshot-gated 10-minute backstop; a wake reaches a non-resident thread with no client connected, and a restart between watermark persistence and acceptance redelivers the same wake once (recorded with originserver); during a real running turn, Stop discards a read in flight and an agentwatch_pull_requestissued before Stop but completing after it is refused, the provider receives onlyInterrupt, and a later user start succeeds; incomplete tails never advance the watermark while checks still report; the two deviations; 8 failed reads and closed end with a delivered last message, merged ends silently; MCP refusals (subagent, settled, archived, merged; saved closed allowed); settle ends the watch and un-settle does not restart it.ghlogin, private scratch repoTryanks/tcode-watch-scratch, Claude Code): asked the agent to watch PR Add cross-platform CI and release packaging #1 → it calledwatch_pull_request; the thread showed Waiting; a pushed commit failed CI and within one pass the thread woke withUpdate on pull request #1 (…), which Tcode is watching for you:\n- Checks failed on 45ee7bc:\n - check https://github.com/…/job/113381710738plus the toast "Add cross-platform CI and release packaging #1: checks failed" with Open thread; a comment bygithub-actions(a different account, posted by a dispatched workflow; no second human account was available) woke it with "- 1 new comment: github-actions: "Scratch review note from the bot account."". "What the agent sees" listed the five tools and the instruction block from the host. The PR was closed and its branch deleted afterwards.cargo fmt --all --checkclean;cargo clippy --workspace --all-targets --locked -- -D warningsclean;cargo nextest run --workspace --locked --no-fail-fast:1046 tests run: 1046 passed, 13 skipped;cargo machete: no unused dependencies.main(an idle provider never answers, which leftstoppingset and the Stop button dimmed);interrupt()always ends the thread's watches and discards its undelivered wakes, and sendsInterruptonly while a turn or background task runs. Re-watching over an ending watch keeps its undelivered last notice; unwatch, unlink and Stop still discard.app::tests::computer_use_registrations_survive_stop_but_are_replaced_after_provider_shutdown— failed after the Stop fix; step 1 (driver): it stopped an idle provider, which the app never does (validation refuses it), so the fake session now has a turn in flight. Contract and assertions unchanged.stop_discards_a_read_in_flight_and_refuses_a_late_agent_start(new in this PR) — rewritten to drive the app's order: a real user turn runs, the agent's broker start is issued, then Stop, then the executor runs, so the start really completes after Stop. Fails with the stop barrier removed.the_half_hour_reread_pages_cached_review_thread_tails_again(new in this PR) — simplified to age only the last read, the one timer left. Fails with the tail drop removed.reads_follow_the_fingerprint_and_its_half_hour_backstop(in-flight rule; fails without it),messages_list_ten_items_with_snippets_and_parse_back(an unclosed<!--keeps the rest, as upstream's/<!--[\s\S]*?-->/g; fails on the old snippet).pull_requests::tests::the_agent_tools_are_disclosed_before_the_first_turn(Claude Code shown, pi not, before any registration; fails on the registration-gated status).pull_requests_tests.rstakes the shared HTTP fixture fromtest_support(Clippy rejects loading the same file as two modules). No test deleted.Merge Danger
Door: two-way
Blast Radius: threads
The watch persists on
SessionMeta.pull_requests[].watch(optional field; older metas read without it). A wake starts a provider turn on its own, including for a thread nobody has open; Stop, settle, archive and unwatch all end it. The wire gainsCommand::WatchPullRequest,RuntimeToast::PullRequestWatchandSessionStatus.pull_request_tools(noted in the unreleased protocol history). The fingerprint costs 1 GraphQL point per 25 watched PRs every 2 minutes; detail/activity only when it moves.