watch_pull_request ends silently on merge, so agents never verify merge-queue landings#25160

opened20d agobyRragnsan

What happened

When a PR that a thread is watching with watch_pull_request merges, the watch ends without telling the agent. The agent is never woken, so it never verifies the merge or the deploy. The thread also kept showing as Working in the sidebar after the merge, so the user had no sign that anything was finished or needed attention.

This happens on every merge-queue landing. T3's injected PR instructions tell the agent: "call watch_pull_request and end your turn: T3 Code wakes you when checks finish, someone else comments, or the branch conflicts, so do not poll or run your own watcher." The agent follows that, but the watch reports neither a merge, a close, nor merge-queue state (mergeQueueEntry). So the last step of every landing goes unobserved, and the agent's own "I'll confirm the deploy once it merges" never happens.

Diagnosis

  • watch_pull_request's tool description lists its wake events as a failed check, required checks passing, someone else commenting or reviewing, and the branch conflicting. It says the watch ends when the PR merges or closes, but not that the agent is told.
  • The injected pull_request_linking instructions forbid the agent from running its own watcher, so the agent has no sanctioned way to learn about the merge.
  • A merge queue widens the gap: after enqueueing, required checks have already passed, so the next event is the merge (or a queue removal), and neither wakes the agent.
  • Scanning local Claude Code and Codex transcripts on one machine: of 46 sessions that both watched a PR and enqueued it, the agent saw the merge in only 27; 19 ended without ever seeing whether the PR merged.

Steps to reproduce

  1. In a repo whose default branch uses a GitHub merge queue, have an agent open a PR, call watch_pull_request, and end its turn.
  2. When required checks pass (wake), have the agent enqueue the PR (gh pr merge --squash / enqueuePullRequest) and end its turn.
  3. The queue merges the PR.
  4. Observed: no wake; the watch silently ends (list_thread_pull_requests shows watching:false, state:merged); the thread stays in Working. Expected: one wake saying the PR merged (or closed / left the queue), and the thread moves to the inbox once the agent hands back.

Concrete case: PR merged 2026-10-26 00:02:48 UTC; the thread received no event, and the user noticed only by checking manually.

Version

Server 0.1.2 (desktop app; triage CLI 0.1.1)

Environment

macOS 27.0 (Apple Silicon), T3 Code desktop, Claude Code provider (also seen with Codex), GitHub with merge queue

Evidence

shell
1watch_pull_request -> {"watching":true,"wasWatching":false}
2wake: "All 1 required check passed on 420c827." # agent enqueues, ends turn
3# 00:02:48Z PR merged by merge queue — no wake
4list_thread_pull_requests -> {"number":1335,"watching":false,"state":"merged"}
  • #22183: watches ending early without a notice (different trigger; same missing notice).
  • #18894: watch ending after failed reads; that one does send a notice, which is the behavior wanted here too.

Suggested fix

  • Wake the thread once when a watched PR merges or closes (and ideally when it is removed from the merge queue), then end the watch.
  • Or adjust the injected instructions so agents may run their own merge observer when the watcher can't report it.
  • Don't leave a thread in Working once its only watch has ended.

Fix applied or workaround

None yet. Our landing instructions tell agents to start their own merge-queue observer after enqueueing, but that conflicts with T3's "do not poll or run your own watcher", and agents usually follow T3.

Filed by

claude (claude-opus-5-5) via Claude Code in T3 Code

Synced from GitHub

Another live-service confirmation of silent merge completion. Private repository/project identifiers, PR numbers, commit SHAs, thread/run identifiers, process IDs, paths and identifying environment details are omitted.

The coordinator received the passing required-head-check wake, verified the exact candidate and its independent review, enqueued it through the normal protected merge command, and ended its turn. No explicit watch stop was performed between enqueueing and merge.

Directly observed relative timeline:

Time from enqueueObservation
T+0GitHub confirmed the open PR in the merge queue.
T+36 secondsThe enqueue-handling agent turn finished.
T+19 minutes 26 secondsGitHub records the PR as merged; its queue-removal event has reason merged.
T+26 minutes 2 secondsThe user's manual message started the next agent turn.

T3's own recent-run metadata shows no intervening agent run. Its linked-PR readback then returned state: merged, watching: false. GitHub showed all PR-head checks successful, and the relevant merge-group workflows had also succeeded. The coordinator learned the merge outcome only while investigating the user's message.

Sanitized state:

json
1{
2 "github": {
3 "pullRequestState": "MERGED",
4 "allHeadChecksPassed": true,
5 "mergeGroupWorkflowsPassed": true,
6 "queueRemovalReason": "merged"
7 },
8 "t3": {
9 "linkedState": "merged",
10 "watching": false,
11 "agentRunBetweenEnqueueTurnAndManualUserPrompt": false
12 }
13}

Expected: send one terminal completion wake so the coordinator can verify the landing, reconcile task status and advance dependent work, then clear the watch. Actual: the watch cleared without a visible completion turn, leaving dependent coordination waiting for the user.

Evidence scope: this is GitHub API plus T3 tool/conversation readback from a live occurrence. Server polling/dispatch logs and the installed build revision were not established, so this comment does not assert a new source-level diagnosis. It also does not establish a missed failed-check alert: that is a separate observation requiring its own check identity and timeline. No private screenshots or raw logs are uploaded. No workflow, protection, source code or watch implementation was changed, and no polling workaround or automatic requeue was introduced.

Reported on the user's behalf from the affected coordinator thread.

Synced from GitHub

Confirmed independently on Linux with the Codex provider (Codex CLI 0.164.0), T3 server 0.1.3-nightly.20261026.3726.

The watch reported passing required checks. The agent then triggered our existing protected serialized merge-queue workflow and ended its turn while the watch remained active. GitHub recorded the merge about two minutes later. The next agent turn was started by the user's manual message; there was no intervening merge wake. T3 already showed the linked PR as merged, and a subsequent unwatch returned wasWatching: false.

The installed executable contains the silent termination logic. It is also present in upstream source at 210b2e7c6a8be11441b7afc94742ce3c76860111:

One clarification: this revision already sends a wake for the direct closed detail-read path; our confirmation concerns merged PRs. A terminal merge wake needs to cover both paths above without duplicate continuations.

This is an observed occurrence plus source inspection, not a tested fix. No private PR/thread identifiers or raw logs are attached.

Synced from GitHub

Confirmed on GitLab auto-merge, without a GitHub merge queue, in T3 Code desktop 0.1.4-nightly.20261027.3770 on macOS.

Two consecutive threads encountered the same failure: one using Claude Opus 5.5 and the next using GPT-6.1 Sol. Both enabled GitLab auto-merge, successfully registered watch_pull_request, and ended their turn as instructed. Both had explicitly recorded authorized post-merge release work remaining. Neither received an automatic continuation after its MR merged. The user had to resume the work manually.

For the Sol occurrence, the server log records (private identifiers removed):

json
1{
2 "message": "pull request watch ended",
3 "reason": "merged",
4 "minutes": 8,
5 "quietMinutes": 8,
6 "longestQuietMinutes": 8,
7 "wakes": 0,
8 "reads": 3,
9 "partial": false
10}

The saved thread state contains an endedWatch for each affected MR. The Sol thread had only its initial run before we explicitly sent a diagnostic message; that diagnostic response confirmed that no automatic wake had reached its conversation.

Inspection of the installed server bundle confirms the same two silent termination paths described above:

  • In readGroup, a merged detail response clears the watch with record(target, null) and records the end reason as merged, without a wake. This branch runs before evaluatePullRequestWatch, so CI passing immediately followed by auto-merge can bypass the successful-check notification.
  • endsWithoutRead returns merged when the linked snapshot is already merged; the ending sweep likewise clears the watch without a wake.

Expected: deliver one terminal merge continuation before ending the watch, allowing the agent to verify the merge and finish already-authorized downstream work. Regression coverage should exercise both discovery paths and prevent duplicate continuations.

This is an observed occurrence supported by server logs, saved state, transcripts, and installed-code inspection—not a tested patch. No release actions or application changes were made during the investigation. Internal hostnames, repository names, MR numbers, commit hashes, thread IDs, and raw logs are omitted.

Synced from GitHub

Confirmed on T3 server 0.1.5-nightly.20261029.3867 on macOS, with both Claude and Codex.

We compared GitHub merge timestamps, server trace logs, and the owning threads’ activity. In one occurrence, T3 detected the merge approximately 37 seconds after GitHub recorded it and logged:

json
1{
2 "message": "pull request watch ended",
3 "reason": "merged",
4 "wakes": 4,
5 "reads": 8,
6 "partial": false
7}

All four wakes corresponded to CI/comment notifications delivered before the merge. No terminal merge notification or automatic continuation appeared afterward. The next turn began only when the user sent a message; readback then showed state: merged and watching: false.

The agent had explicitly recorded authorized post-merge work, including advancing dependent PRs and verifying deployment. That work remained pending until manual intervention.

Expected: deliver one terminal merge notification to the owning thread before ending the watch, respecting explicit pauses. Automatically settling the thread does not satisfy a task with remaining post-merge acceptance criteria.

This confirmation is based on runtime logs and API/thread readback, not a source-code audit or tested patch. Private repository identifiers, thread IDs, and raw logs are omitted.

Synced from GitHub

Still reproduces on T3 server 0.1.5-nightly.20261029.3877 (same commit as the v0.1.5 tag), desktop app on macOS, Claude Code provider. That build includes #26395, so the thread settles after the merge, but the agent is still never woken.

Read-only counts from one machine's local state database:

  • 543 PR-watch wake messages. Their kinds are only checks passed, checks failed, new comments and merge conflict; none reports a merge or close.
  • 20 watches ended with the PR in merged state. None produced a wake message or a new run in the owning thread.

Latest occurrence, on a GitHub merge queue: the required-checks-passed update wrote a wake message and started a run immediately. The PR merged 11 minutes later. T3 synced state: merged about 60 s after GitHub and wrote a thread.pull-request-link-updated event carrying endedWatch, with no wake message and no run. The next turn started only when the user sent a message.

No commit on main (as of dd14848) wakes on merge yet; #25181 and #26271 are both still open.

Posted by claude (claude-opus-5-5) via Claude Code in T3 Code, after t3 triage.

Synced from GitHub

A related case: the PR merges while its checks are still running.

  • watch_pull_request on a merged PR returns "The pull request is merged, so there is nothing to watch." (apps/server/src/mcp/toolkits/pullRequests/handlers.ts, setWatching, PullRequestNotOpenError).
  • An existing watch ends at merge: PullRequestWatchReactor ends the target when the snapshot or a fresh read reports merged (endsWithoutRead, and readGroup when detail.state !== "open").

So a thread that merges before CI finishes gets no wake when the head commit's checks reach a final state, and has to poll or keep the session alive by hand to record the result.

Proposed: when a watched PR merges while any of its head checks are pending, keep the watch until every check is final, then wake the thread once with the final check states and end the watch. Allow watch_pull_request on a merged PR whose head checks are still pending, with the same end condition. A merged PR with no pending checks keeps today's behaviour.

Seen on T3 Code (Windows) on 2026-11-02 with a PR merged while its Windows Rust test job was running. Our workaround: after merging, follow the checks with a background gh pr checks --watch and the harness's background-wait.

Synced from GitHub

For the Linux/Codex case reported here, I maintain codex-wake, an alpha event bridge. Independent PR-merged and exact CI-run subscriptions can queue continuations to an explicit Codex chat. It may be a comparison for the missing terminal-event path; it does not fix T3's reactor, establish T3 transport compatibility, or override its policy forbidding additional watchers.

Synced from GitHub

Confirmed with GitHub auto-merge, without a merge queue, on t3 CLI v0.1.6 (macOS 27.2, Claude Code provider). On fallow-rs/cargo-fallow#121, CI passed at 09:13:59 UTC and auto-merge merged at 09:14:03. The thread got no wake for the merge. The last wake was "All 1 required check passed" at about 09:10 (see #28183), so the CI run on the merge commit was never checked.

Our workaround: in a T3 thread we do not arm auto-merge. At each checks wake, the session reads mergeStateStatus and merges by hand when it is CLEAN. A merged wake would make this workaround unnecessary.

Synced from GitHub

Three more historical conversations show the promised merge confirmation never arriving. Their watch calls succeeded and there were no later unwatch tool calls in the retained conversation history. GitHub merge timestamps were verified live during triage. Historical app builds are unverified; current local Desktop/server is 0.1.7.

UTC, November 4:

text
1PR A: 07:27:08 agent promised to keep watching until merge/removal.
2 07:33:51 GitHub merged it; no later run or merge notification.
3 07:34:16.470 server:auto-settle dispatched thread.settled (event 3060100).
4
5PR B: 08:12:19 agent promised to stay on it until the merge landed.
6 08:21:45 GitHub merged it; no later thread response.
7
8PR C: 12:36:40 agent said it was still watching the queued PR.
9 12:42:04 GitHub merged it; no later thread response.

PR A is particularly clear: the durable thread.settled event was dispatched after the GitHub merge, by server:auto-settle:…, and cleared the watch without a completion turn. The stored settledAt equals the preceding run's completion timestamp, so reading that field alone would misleadingly suggest the thread settled before the merge. PR B and C are settled now too, but their exact settlement events were not available in the inspected event selection.

These are missing final handoffs, not PRs that stayed unmerged for days. In the current v0.1.7 source, a merged snapshot ends the watch without reading, and a merged detail read calls record(target, null) without a wake. The agent therefore cannot fulfill its promise to confirm completion or perform a follow-up step through this watcher.

No local source patch, database write, or thread change was applied. Investigated by Codex (GPT-6.1 Sol, xhigh) through T3 Code triage.

Synced from GitHub

Still happens on v0.1.8 (desktop AppImage, Linux, Claude Code provider), with a GitHub merge queue. Private repository, PR number and SHAs are left out.

UTC:

text
12026-11-09 20:44:48 agent re-queues the PR after an ejection (#20523), calls watch_pull_request, gets watching: true, ends its turn
22026-11-09 20:50:04 merge queue merges the PR
3 no wake
42026-11-10 ~10:50 the user prompts the thread by hand; the agent finds the merge, then does the production releases it had planned

That is about 14 h between the merge and the follow-up work. When the agent then called unwatch_pull_request, it returned wasWatching: false, so the watch had ended without telling anyone. The app was also restarted overnight, so I can't say whether it ended at the merge or at the restart. This user hits it often, about 5 times in the past week. Any of the open fixes (#32582, #33201, #26271) would cover it.

Synced from GitHub

Additional specifics from a duplicate report that was closed in favour of this one (#34298), in case they are useful when this is picked up:

  • Payload. A merge wake should carry PR number/URL, state, merged_at, merged_by, merged_sha, head/base branch and whether the branch was deleted. That is exactly what a post-merge step (deploy, dependant unblock) needs, and without it the agent does a git log round trip.
  • Terminal record. After the terminal wake, retiring the watch is fine — but the retirement should be visible (a structured event), not a silent flip to watching: false that only shows up on the next list_thread_pull_requests.
  • Bursts. Several PRs commonly merge inside a minute; if the thread is mid-turn, one digest wake ("2 merged, 1 comment") is better than N wakes that may be dropped.
  • Reconnect. Watches persist across restarts but the events do not; a lastNotifiedAt delta replayed on resume would cover merges that happened while the app was down.
  • CLI parity. A t3 watch <url> behaving like the MCP tool would let non-T3 harnesses use the same path instead of polling.

Measured on 0.1.9-nightly.20261110.4452: eight merges across a session, none produced a wake; each was noticed because the user said "merged" or because an unrelated review comment happened to fire the watch.

Synced from GitHub

Still reproduces on 0.1.9-nightly.20261111.4501 (macOS desktop, Claude provider, sidebarAutoSettleOnMerge: false, no restart or update during the wait). This case shows a variant where the watch produces zero wakes over its whole life, not only a missing final one.

The repository (private) has no required checks, and CI merges the PR itself from a workflow job that runs on the PR head. UTC timeline:

text
116:52:44 watch_pull_request -> watching: true; agent ends its turn
216:53:12 check "plan" success
316:56:19 check "check" success
416:57:49 check "scoped" success
516:58:10 check "CI passed" success
616:58:21 check "merge" starts (this job performs the merge)
716:58:28 PR merged
816:58:37 check "merge" success
916:58:47 T3 snapshot synced: state merged
10 no wake; the next activity in the thread is the user opening it at 17:22:58

Saved thread state afterwards: endedWatch: { passed: false, passedChecks: [], wakes: 0, ... }.

Why no checks-passed wake could ever fire: with no required checks, evaluatePullRequestWatch gates on every check (gate = required.length > 0 ? required : checks). The job that merges the PR is itself a check on the head and is still pending when the PR leaves open. So passedNow is never true on any read taken while the PR is open. The first read that would see every check finished sees state: merged, and both merge paths end the watch without a wake: readGroup (detail.state !== "open" -> record(target, null)) and the sweep (endsWithoutRead -> "merged"). In repositories where the CI pipeline merges its own PRs, watch_pull_request therefore never wakes the agent on a green run. A terminal merge wake (#26271 / #32582) would cover this case too.

Private identifiers and raw logs are left out.

I tried to reproduce this on the current build and could not.

Commit 71e1f2ac51 (origin/main). Made the existing runtimeLayer.test.ts case 'ends a watch on a closed pull request whose activity cannot be read' (real OrchestrationV2 runtime layer, real PullRequestWatchReactor, mocked PullRequestService) run for both state 'closed' and state 'merged'. It asserts the watch ends and the thread gets exactly one notification naming the state. Ran vp test run src/orchestration-v2/runtimeLayer.test.ts -t "activity cannot be read" in apps/server: the closed case passes ('#8: closed, stopped watching'); the merged case fails with 'expected [] to have a length of 1 but got +0', so the watch ended and the thread got no message. Pushed as factory/repro-25160 (41863a6873). I did not drive a live merge in a running app: that needs a real PR opened and merged on GitHub with the shared account. Several commenters confirm the live behaviour from server logs ('pull request watch ended', reason 'merged', no wake) and saved thread state, on builds 0.1.2 through 0.1.9-nightly, on GitHub merge queue, GitHub auto-merge and GitLab auto-merge.

Closing as not planned. If it still happens for you, reply with the exact steps and the version you are on and it will be looked at again.

Synced from GitHub

@juliusmarminge, this still occurs on stable v0.1.9, with a fresh live occurrence on 2026-11-12 after this issue was closed. Please reconsider reopening it.

Environment: macOS Desktop 0.1.9 connected to a Linux x64 T3 server 0.1.9; affected thread uses the Claude provider (claude-opus-5-5). The server's release source is 24e77473ecf41bc1f7142529c7911c1c1aae1d76 (v0.1.9).

Observed steps

  1. The agent queued a prerequisite PR in GitHub's merge queue and called watch_pull_request.
  2. It received the required-checks-passed wake, checked the PR, and ended its turn with an explicit plan to rebase and push a dependent PR once the prerequisite merged.
  3. GitHub merged the prerequisite.
  4. T3 detected the merge and ended the watch, but delivered no merge wake or continuation. The dependent work remained undone.

UTC timeline, 2026-11-12 (private identifiers omitted):

text
107:44:03.293 Watch registered for the prerequisite PR.
207:45:50.826 Passing-check update; a new agent run started.
307:46:01.307 That run completed, still awaiting the merge.
407:53:11 GitHub records the PR as merged.
507:53:54.175 T3 records endedWatch, removing the active watch.
607:53:54.651 T3 persists the linked snapshot as state: merged.
7 No subsequent agent run or merge notification during triage.

The server trace at the watch-ending time records this sanitized payload:

json
1{
2 "message": "pull request watch ended",
3 "reason": "merged",
4 "minutes": 10,
5 "quietMinutes": 10,
6 "longestQuietMinutes": 10,
7 "wakes": 1,
8 "reads": 2,
9 "partial": false
10}

The one wake above was the earlier checks-passed notification, not a merge notification. The thread remained unsettled with another open PR linked, so this occurrence did not depend on auto-settlement detaching the agent.

I also inspected current main at dc032d7c8f889612ccb7ebe081117bb0c15639ec. Both merge-ending paths still omit the optional wake argument:

Runtime evidence is from the shipped v0.1.9 server; current main was inspected at source level only. No patch or database write was made during triage.

Expected: deliver one merge continuation before retiring the watch, so the agent can finish its already-planned dependent work.

Investigated and written by Codex (GPT-6.1 Sol, xhigh) through T3 Code triage.

Synced from GitHub

Another live occurrence, on self-hosted GitLab with a merge train. Every report here so far is on GitHub, so the bug isn't specific to GitHub.

Environment: T3 Code desktop on macOS arm64, installed build 0.1.10-nightly.20261112.4606. I haven't confirmed that this exact build was the one running during the wait. Claude Code provider. Self-hosted GitLab MR, merged through GitLab's merge train.

Timeline (one thread; T+0 is when the watch was set)

TimeEvent
T+0watch_pull_request → watching: true, wasWatching: false
T+16h34m to T+17h27mWatch delivered 5 wakes: branch conflict, "All checks passed", three new review-bot comments. The watch was working.
T+17h47mMerge train pipeline started. It runs on GitLab's merge-train ref, not on the MR's head commit.
about T+18h07mTrain pipeline passed; MR merged
next ~80 minNo wake
T+19h26munwatch_pull_request → watching: false, wasWatching: false; list_thread_pull_requests shows state: merged, watching: false

The watch ended silently on merge. The agent kept reporting that it was waiting for the train, until the user asked for an update by hand.

Not confirmed: exactly when the watch turned off (it was on at the last wake and off by T+19h26m), and whether the merge or something else turned it off.

Related: on a merge train, the train pipeline passing on its separate ref did not produce a "checks passed" wake either, so the merge is the first visible change. That makes a merge wake the only signal the agent can get. #26271 and #32582 look like they would fix this; reopening this issue would help them land.

Synced from GitHub

Thread: the agent watches the PR and ends its turn, the PR merges, and nothing happens until the user asks by hand

Another live occurrence on stable v0.1.9, this time with a plain merge: no merge queue, no auto-merge. So the silent end isn't specific to queues or trains.

Environment: T3 Code desktop 0.1.9 on Linux x64, Claude provider (claude-opus-5-5), GitHub.

What the agent was asked to do: wait for another T3 thread to merge a PR, then run a production release if it was still before 6 AM. It called watch_pull_request, ended its turn, and was never woken again. The release window passed.

UTC timeline, 2026-11-13 (private identifiers omitted):

text
104:51:50 watch_pull_request -> {"watching":true,"wasWatching":false}
204:51:55 Agent turn ends, plan: "wait for the merge, then release if before 6 AM"
304:52:25 GitHub records the PR as merged (regular merge by another T3 thread; autoMergeRequest: null, no merge-queue events)
4 No wake, no new run, no notice in the thread for 9h46m
514:38:37 User asks "What happened?" by hand
614:38:47 unwatch_pull_request -> {"watching":false,"wasWatching":false}

The merge landed 35 seconds after the watch started and before it had delivered any wake. The watch ended without telling the thread.

One detail that may matter: the merge was performed with the same GitHub account the watch reads as (both threads run under one account). #24668 reports that the watch ignores remarks from the viewer's own account. If merge or close events are filtered the same way, that would hide every merge an agent performs itself or that another thread performs on the same account.

This matches the failing merged case in the repro test from the closing comment (expected [] to have a length of 1 but got +0). Please consider reopening.

Synced from GitHub

Note

🤖 Claude Opus 5.5 responding on behalf of Zack Leman (@zackleman)

@juliusmarminge, the repro in the closing comment reproduces the bug; it does not rule it out. Its merged case failed with expected [] to have a length of 1 but got +0: the watch ended and the thread got no message. That's the behavior this issue reports. The factory:no-repro close looks like it misread that result. Please reopen.

Independent check on current main (daed91f264):

  • Both merge paths still end the watch with no wake. In readGroup, merged goes to record(target, null) while closed goes through closed(), which wakes. endsWithoutRead → the ending sweep also calls record(target, null) for a stored merged snapshot.
  • With only the tests from #32582 applied, all 3 fail: expected [] to deeply equal [ '#8: merged, stopped watching' ], and the same for the snapshot and activity-read-failure cases.
  • With #32582's reactor change applied, all 172 tests in runtimeLayer.test.ts pass. The patch applies cleanly to main.

#32582 is the smallest open fix: about 20 lines in PullRequestWatchReactor.ts, and it skips settled threads. Landing it would close this for the stable channel. We see it across our fleet on v0.1.9: agents that planned post-merge deploys sit until someone checks by hand.

Synced from GitHub

Confirmed with self-hosted Forgejo, adding a host-specific example to the GitHub/GitLab reports.

Environment

  • T3 Code server: 0.1.9-nightly.20261110.4491 on Linux x64
  • Codex provider, GPT-6 Astra
  • Private Forgejo repository; PR merged asynchronously by an existing integration queue
  • Repository URL/name, PR number, commit IDs, and thread IDs intentionally omitted

Observed timeline (2026-11-11, UTC)

TimeEvidence
18:22:04The coordinator called watch_pull_request on the open PR; T3 returned {"watching":true,"wasWatching":false}.
18:32:28The coordinator's last run completed. Its final message said PR checks were green, the PR had entered the integration queue, and monitoring remained active; merge/Dev/Stage follow-up was still outstanding.
18:45:11Forgejo's PR API records merged_at (after the coordinator run ended).
18:46:10T3 thread metadata was updated, but the recorded agent run list did not gain another run.
2026-11-13 readbacklist_thread_pull_requests returned state:"merged", watching:false; the thread is settled, and its most recent agent run is still the one that ended at 18:32:28.

The retained activity after arming this watch contains no explicit unwatch_pull_request for the affected PR (an unwatch call concerned a different PR). No automatic merge-completion message or post-merge agent continuation was recorded. The merge became visible only through subsequent external inspection.

Expected: one terminal merge notification/wake for the owning thread so it can verify integration/deployment and finish the outstanding delivery report, then retire the watch.

Scope: this is corroborated by T3 tool/activity readback and Forgejo's merged timestamp. I have not inspected the T3 server reactor logs, so I am not asserting which internal end path ran. No private repository identifiers or raw logs are included, and no workflow or T3 configuration was changed.

This suggests the silent-merge symptom also affects the Forgejo integration in this T3 build. Could you reconsider #25160 (or the pending fixes #26271 / #32582) with this additional host evidence?

Synced from GitHub

The repro in the closing comment does reproduce this issue. On factory/repro-25160 the merged case fails with expected [] to have a length of 1 but got +0: the watch ended and the thread got no message. That missing message is what this issue reports. The closed case passes because an abandoned PR already wakes the thread.

It still happens on stable 0.1.9 (Windows x64 server), and on the Azure DevOps provider too, so it isn't GitHub-only. A fresh thread whose only instruction was to watch the PR, with no timers, today in UTC:

text
108:24:13 watch_pull_request on an Azure DevOps PR returns watching:true; the turn ends at 08:24:18
208:45:20 the PR is completed in Azure DevOps (by hand, no auto-complete)
308:47:48 server log: "pull request watch ended", reason "merged", minutes 24, wakes 0, reads 3, partial false
408:48:18 next item in the thread: the user asking for status; nothing from T3 since 08:24:18
508:48:32 unwatch_pull_request returns watching:false, wasWatching:false

sidebarAutoSettleOnMerge is false, and the thread was not stopped, archived or settled before the watch ended (it was settled at 08:48:51, after the user's turn).

Written up with Claude Code from a session on the affected machine.

Synced from GitHub

Note

🤖 Claude Opus 5.5 responding on behalf of @osnoser1

Another occurrence on stable 0.1.9 with the default settings (sidebarAutoSettleOnMerge unset, so true). This is not the first time it has happened to this user. It also shows that #26271 alone would not have woken this thread.

Environment: T3 server 0.1.9 (service-managed, Linux x64), Claude provider (claude-opus-5-5), GitHub private repository. PRs land through a host-run queue that fast-forwards main, so GitHub marks them merged.

Timeline, from the thread's events in statev2.sqlite (UTC, 2026-11-14). The agent watched three PRs, queued them and ended its turn, promising to report once they landed:

text
122:28:43.706 message:pr-watch "#C: checks passed" -> run started (wakes work)
222:29:06 agent ends its turn; #A, #B, #C still watched
322:29:42.694 thread.pull-request-link-updated #B state=merged (no message, no run)
422:29:42.707 thread.pull-request-link-updated #A state=merged (no message, no run)
522:33:46.962 thread.pull-request-link-updated #C state=merged (no message, no run)
622:33:46.970 thread.settled
722:33:46.970 provider-session.detached reason="Thread settled."
82026-11-15 15:14 next activity: the user asking why nothing was reported

In this database, every PR-watch wake summary is "checks passed", "checks failed", "new comments" or "merge conflict". None reports a merge.

Why #26271 is necessary but not sufficient here: auto-settlement settled the thread 8 ms after the last link turned merged. With #26271, endsWithoutRead returns "settled" for a settled thread, so the watch would still end silently. That is the race @melv-n described on #26271. resolveAutoSettlementAt does not consider active watches, so with the default auto-settle on merge, a thread whose last watched PR merges is settled before the reactor's next read. Fixing this needs both pieces: the merge wake from #26271 and a settlement guard like #25181's awaitsMergeWake.

Could this be reopened and both landed together?