watch_pull_request refuses an open PR as merged when a recreated repo reuses its number#33407

opened4d agobyPProject516

What happened

Agents called watch_pull_request on PRs that were open on GitHub. The tool answered "The pull request is merged, so there is nothing to watch." Agents then fell back to polling by hand. This happened 4 times in 2 days, in 2 repos.

Diagnosis

Both repos were deleted and recreated under the same name, so GitHub restarted PR numbering. Each affected thread still held links from the old repo. A link is keyed by host, repository and number, so the old merged snapshot now stands for a different, open PR.

  • setWatching refuses from the stored snapshot without asking the host: if (watching && before?.snapshot?.state === "merged") in apps/server/src/mcp/toolkits/pullRequests/handlers.ts:278.
  • PullRequestSyncReactor treats a merged snapshot with mergedAt as final and never reads it again (isDue, PullRequestSyncReactor.ts:356-361). The wrong snapshot never heals.
  • link_pull_request returns early when the link exists (Orchestrator.ts:3762), so linking again does not refresh it either.
  • PullRequestWatchReactor ends a watch on a stored merged snapshot without a read (endsWithoutRead, PullRequestWatchReactor.ts:410), so dropping the refusal alone would not help.

The stored snapshot for java-web #79 has the title and mergedAt (2026-10-27) of the old repo's #79. The current #79 was opened 2026-11-09 and is open. The thread also keeps links to old #81 to #101, which now return 404 on every sync (74 GitHubApiNotFoundError in one day of traces).

#15741 fixed the same refusal for closed, on the grounds that a merged PR cannot reopen. A recreated repo breaks that assumption. main still has the check.

Steps to reproduce

  1. In a thread, link PR #N of owner/repo and let it merge. Wait for the sync to store state: merged.
  2. Delete owner/repo on GitHub and create a new repo with the same name.
  3. Open new PRs in the new repo until one gets number #N.
  4. From the same thread, call watch_pull_request for owner/repo #N.

Expected: T3 reads the host and watches the open PR. Actual: "The pull request is merged, so there is nothing to watch."

Version

0.1.7 (server). Same check on main at time of filing.

Environment

Linux arm64, Node 26.12.1, server run as a background service, Claude agent provider, GitHub repos.

Evidence

text
1watch_pull_request {"repository":"Project516/java-web","number":79}
2-> The pull request is merged, so there is nothing to watch.
3
4GitHub: #79 OPEN, createdAt 2026-11-09T00:39:06Z, mergedAt null
5Stored snapshot: state merged, mergedAt 2026-10-27T23:43:18Z, title "Run StringBuilder.append(String) and toString as intrinsics"
6Repo createdAt: 2026-10-31T20:12:24Z
7
8Same for Project516/frc-dev-wasm #8 and #10 (repo recreated 2026-11-08T18:26:59Z; stored snapshots are October PRs).

#15744 (closed, fixed by #15741): same refusal for reopened closed PRs. Not a duplicate, since that fix kept the merged check and this case needs it. #25160: different watch problem (ends silently on merge).

Fix applied or workaround

Call unlink_pull_request for the stale PR, then watch_pull_request. Unlink drops a non-stack link, so the watch links it again with an empty snapshot and the sync reads the host. Nothing was changed on the machine during triage.

Filed by

Claude Code, claude-opus-5-5, via t3 triage.