You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: watch_pull_request ends the watch after 8 consecutive failed reads, even when the failures are transient network errors #18894
I searched existing issues and did not find a duplicate.
I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
In a thread, call watch_pull_request on an open GitHub PR.
Interrupt the machine's network briefly, long enough to span 8 poll attempts (for example, move a laptop between networks or let it sleep and wake, power down your router for 20 minutes).
Restore the network.
Observed in one session: the watch on a single PR dropped 4 times in a row. The agent re-armed it each time. At least the latest drop coincided with moving the laptop between networks; the cause of the earlier ones was lost network connectivity (router lost power for 30 minutes). The PR, the GitHub credentials and gh on the same machine were all fine: gh pr view from the same machine succeeded immediately after each drop.
Expected behavior
A temporary loss of connectivity should not end the watch. Specifically:
Classify read failures. Connectivity errors (DNS failure, ECONNRESET, ETIMEDOUT, offline, TLS handshake errors, SSL Certificate Errors) and HTTP 5xx / 429 are transient. 401, 403 and 404 are permanent.
On transient errors, back off exponentially with a cap (for example 2, 4, 8 … up to 30 minutes). Pause while the OS reports offline, and poll again as soon as the network returns.
End a watch only on a permanent error, or after a long wall-clock outage (hours, not 8 polls). When it does end, the notification should say whether the cause was network or auth.
Actual behavior
After 8 consecutive failed reads the watch ends, and the thread gets: "T3 Code stopped watching pull request #N (...) because it failed to read it from the host 8 times in a row. Check it yourself, and call watch_pull_request to watch it again.". Ironically in some of these cases the agent lost the unwatch event message completely because the network issues prevented the inference call. The user's usual recourse of recovering a network incident by typing "resume" or "try again" was not sufficient to get things running again - the user also had to tell the agent to check all of its PR watches and resume them if they stopped.
With the 2-minute poll, that is about 16 minutes of connectivity loss, which a laptop hits routinely. Each drop also wakes the agent (a full turn on a possibly large thread; see #16829) only to re-arm the watch. Re-arming by hand every time defeats the purpose of an unattended watch, and a watch that silently is not running can miss the reply it was set up for.
Impact
Major degradation or frequent failure
Version or commit
T3 Code (Nightly) 0.0.46-nightly.20261011.2994
Environment
macOS 26.6.2 (Apple Silicon), T3 Code desktop (Nightly), Claude Code provider, GitHub PR on github.com
Logs or stack traces
Screenshots, recordings, or supporting files
No response
Workaround
Re-arm with watch_pull_request after every drop, or have the agent run its own poll loop instead of a T3 watch.
This is documented behavior, not a bug. docs/user/source-control.md says a watch stops "after 8 failed reads in a row… A rate limit only pauses watching", and PullRequestWatchReactor.ts matches that: READ_FAILURE_LIMIT = 8, a sweep every 2 minutes, and rate-limited reads are left out of the count since #16208. So the watcher already separates one kind of failure (rate limits). It just doesn't treat network errors any differently yet.
Classifying errors as transient or permanent, backing off, pausing while offline, and saying in the stop message whether the cause was network or auth would change how watches are meant to work. That makes this a feature request, so I'm moving it to Ideas, where it can get a design discussion. For now, the workaround is to re-arm with watch_pull_request once you're back online.
Another user has experienced a watched PR no longer being followed up without manually prompting the agent (Linux, Codex provider). Related observation posted in #16924.
In our session, watch_pull_request returned watching: true after the agent enqueued the PR. The PR subsequently merged, but no merge wake is visible in the conversation. When the user later prompted the agent to check, cleanup via unwatch_pull_request returned wasWatching: false.
We have no evidence of eight read failures or a network outage in this incident, so this is not a confirmed reproduction of the network failure mechanism in this issue; stopping after merge could also explain the watch state. The shared user-facing problem is that unattended follow-up was missed and required manual intervention. Reliable delivery of a final wake, or a visible explanation when a watch stops, would help. Posting on the user's behalf from the affected thread.
Written by Claude Opus 5.5 on behalf of andrewfree.
Following up on #19254, which was closed with a request to agree on scope here first. This asks about one piece of this issue: the stop message saying why the watch ended. The 8-read limit, the 2-minute sweep, the rate-limit pause and the stop itself stay as they are. No backoff or offline detection.
A correction to #19254 first: as written, it would not answer this issue's question. It chose a fixed sentence from the provider error's reason, but a network failure and an error answer from the host both arrive as failed. In gitHubProviderFailure, GitHubApiRequestError (DNS failure, reset, offline) and GitHubApiResponseError (a 5xx, or the FORBIDDEN field error from #19549) both fall through to default, so both would read "The host request failed."
Proposed scope:
Add an unreachable reason to PullRequestProviderError for "no answer from the host", and map each provider's transport error to it: GitHubApiRequestError for GitHub, plus the matching error in the GitLab, Forgejo, Azure DevOps, Bitbucket and GitCafe providers.
The stop wake appends one fixed sentence chosen by the last failure's reason. For example, unreachable: "T3 Code could not reach the host." and failed: "The host answered with an error." The existing credential and not-found cases keep their own sentences. No error detail, URL, token or command output goes into the message.
With that, an agent can re-arm after an outage, as the triage above suggests, and tell the user about a host-side error instead of re-arming blindly. The #19549 reporter had to reproduce the GraphQL query by hand to find the FORBIDDEN error behind the same generic stop message.
Does this work as a focused fix, or should it go to an Ideas discussion first? I didn't find one for this issue yet. If the scope works, I'll open a PR limited to exactly this.
Thanks for the report. This behavior is intended, so we will not change it. If you need different behavior, please post the idea in Ideas discussions so others can vote on it. If you see a different fault, please open a new issue with steps to reproduce it.
Activity
juliusmarminge commentedon Oct 13, 2026
Note
Grok responding on behalf of Julius.
This is documented behavior, not a bug.
docs/user/source-control.mdsays a watch stops "after 8 failed reads in a row… A rate limit only pauses watching", andPullRequestWatchReactor.tsmatches that:READ_FAILURE_LIMIT = 8, a sweep every 2 minutes, and rate-limited reads are left out of the count since #16208. So the watcher already separates one kind of failure (rate limits). It just doesn't treat network errors any differently yet.Classifying errors as transient or permanent, backing off, pausing while offline, and saying in the stop message whether the cause was network or auth would change how watches are meant to work. That makes this a feature request, so I'm moving it to Ideas, where it can get a design discussion. For now, the workaround is to re-arm with
watch_pull_requestonce you're back online.Kryckan commentedon Oct 15, 2026
Another user has experienced a watched PR no longer being followed up without manually prompting the agent (Linux, Codex provider). Related observation posted in #16924.
In our session, watch_pull_request returned watching: true after the agent enqueued the PR. The PR subsequently merged, but no merge wake is visible in the conversation. When the user later prompted the agent to check, cleanup via unwatch_pull_request returned wasWatching: false.
We have no evidence of eight read failures or a network outage in this incident, so this is not a confirmed reproduction of the network failure mechanism in this issue; stopping after merge could also explain the watch state. The shared user-facing problem is that unattended follow-up was missed and required manual intervention. Reliable delivery of a final wake, or a visible explanation when a watch stops, would help. Posting on the user's behalf from the affected thread.
andrewfree commentedon Oct 15, 2026
Note
Written by Claude Opus 5.5 on behalf of andrewfree.
Following up on #19254, which was closed with a request to agree on scope here first. This asks about one piece of this issue: the stop message saying why the watch ended. The 8-read limit, the 2-minute sweep, the rate-limit pause and the stop itself stay as they are. No backoff or offline detection.
A correction to #19254 first: as written, it would not answer this issue's question. It chose a fixed sentence from the provider error's
reason, but a network failure and an error answer from the host both arrive asfailed. IngitHubProviderFailure,GitHubApiRequestError(DNS failure, reset, offline) andGitHubApiResponseError(a 5xx, or theFORBIDDENfield error from #19549) both fall through todefault, so both would read "The host request failed."Proposed scope:
unreachablereason toPullRequestProviderErrorfor "no answer from the host", and map each provider's transport error to it:GitHubApiRequestErrorfor GitHub, plus the matching error in the GitLab, Forgejo, Azure DevOps, Bitbucket and GitCafe providers.unreachable: "T3 Code could not reach the host." andfailed: "The host answered with an error." The existing credential and not-found cases keep their own sentences. No error detail, URL, token or command output goes into the message.With that, an agent can re-arm after an outage, as the triage above suggests, and tell the user about a host-side error instead of re-arming blindly. The #19549 reporter had to reproduce the GraphQL query by hand to find the
FORBIDDENerror behind the same generic stop message.Does this work as a focused fix, or should it go to an Ideas discussion first? I didn't find one for this issue yet. If the scope works, I'll open a PR limited to exactly this.
t3dotgg commentedon Oct 21, 2026
Note
🤖 Claude Opus 5.5 responding on behalf of Theo
Thanks for the report. This behavior is intended, so we will not change it. If you need different behavior, please post the idea in Ideas discussions so others can vote on it. If you see a different fault, please open a new issue with steps to reproduce it.