Skip to content

[Bug]: Linked PR details stay pending in No project threads without a checkout #17166

Description

@PixPMusic

Before submitting

  • 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

  1. Run a T3 environment with working GitHub authentication and no registered GitHub-backed project. Start a thread in a scratch directory outside a Git repository (shown as “No project”).
  2. Link an existing github.com pull request to the thread through the agent's link_pull_request tool.
  3. Open the linked PR overview on mobile or inspect the thread's PR badge on desktop. Allow the server's periodic PR sync to run.

Observed on a MacBook Air host connected to Android and desktop clients through T3 Connect, with eight linked PRs in pixpmusic/theatrebot.

Expected behavior

The linked PR's explicit host, repository, and number are enough to load its title and state using the environment's authenticated provider. A Git checkout should not be needed to read a linked PR.

Actual behavior

All eight rows retain the generic “Pull request” title and “Status pending”. Desktop also shows pending status. PR watches repeatedly stop because the server cannot read the linked PRs.

GitHub access works in the affected thread's terminal: gh pr view 1 --repo pixpmusic/theatrebot --json title,state returns the actual title and MERGED from the scratch directory.

PullRequestService.requireProject only accepts a hosted reference when a registered Git-backed project exists on that host. Without one, it fails with PullRequestUnavailableError (provider-unsupported) before asking GitHub. The sync reactor therefore never persists a PR snapshot.

Impact

Major degradation or frequent failure: linked PR status and PR watches do not work for affected scratch threads.

Version or commit

Observed in the nightly app. The routing restriction is also present on upstream main at a4c9494b0e3606775cc5fc929fc138399288bd43.

Environment

macOS host, T3 Connect, Android mobile client and macOS desktop client. GitHub CLI authentication verified in the host's T3 terminal.

Workaround

The current routing code can use a registered Git-backed project on the same host. The host terminal can read the PR directly while the thread UI remains pending.

Activity

  1. juliusmarminge commented on Oct 8, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Thanks for the precise report. I traced it on current main (a4c9494b, which is the #15946 merge commit). Your diagnosis matches what the code shows. I haven't reproduced it at runtime.

    Where it fails

    • A "No project" thread belongs to the managed Scratch project (apps/server/src/project/ManagedProjectFolders.ts:5). Its folder isn't a Git checkout, so it has no repositoryIdentity, and listWorkspaceProjects drops it from the supported list (apps/server/src/pullRequest/PullRequestService.ts:822-825).
    • requireProject (PullRequestService.ts:888-953) then has no project of its own for the thread. A hosted ref falls back to "any supported project on that host" (:915-934). With no GitHub-backed project registered, nothing matches, so it fails with PullRequestUnavailableError({ reason: "provider-unsupported" }) at :935-938 before any provider call.
    • Every read goes through it: summary, stack and detail are wrapped in credentialCached → canonicalRef → requireProject (:3398-3411, :3423-3435), and summaryUncached calls it directly (:1633-1641).
    • Sync: PullRequestSyncReactor builds the ref from thread.projectId plus the link's host, repo and number, then calls pullRequests.summary (apps/server/src/orchestration-v2/PullRequestSyncReactor.ts:281-289). The failure is counted as a skip (:355-368) and no thread.pull-request-link.sync is dispatched, so the snapshot stays null. The UI keeps showing "Pull request" and "Status pending". Because isDue treats a null snapshot as always due (:176), it's retried on every sweep.
    • Watches: PullRequestWatchReactor builds the same ref (apps/server/src/orchestration-v2/PullRequestWatchReactor.ts:443-446) and reads pullRequests.detail (:486). After READ_FAILURE_LIMIT = 8 failures in a row (:45) it gives up and posts the "stopped watching … failed to read it from the host" message (:330, :499-503). That likely explains the repeated watch stops you saw.

    What #15946 changed

    #15946 only touches the client. It changes usePullRequestLinking.ts (canLinkChangeRequest, apps/web/src/hooks/usePullRequestLinking.ts:40-49) and the dialog copy, so in multiple mode a URL can be linked with no matching project. Its own comment says such a link "starts with a null snapshot until some project can sync it" (:33-38). It doesn't change requireProject, the sync reactor or the watch reactor. The agent's link_pull_request tool never required a project for a URL (apps/server/src/mcp/toolkits/pullRequests/handlers.ts:75-84, :273-301). Linking now works from more places, but the server still can't read what gets linked.

    Bug or intended?

    I'd call it a gap rather than a deliberate limit. The requireProject docstring describes the project as "whose checkout and credentials serve a reference". For GitHub, though, the provider already gets an explicit repository and host, and credentials look like they're resolved per host (GH_TOKEN or the gh login for that host, apps/server/src/sourceControl/GitHubCredentials.ts). The checkout mainly supplies cwd and the provider kind. So for a known host like github.com, an explicit host, repo and number should probably be enough to read with the environment's credential.

    There are two caveats a fix would need to handle:

    Azure DevOps takes its organization from the checkout, so it would likely still need one.

    Possible fix direction (untested): when requireProject finds no supported project for a hosted ref, and the host maps to a known provider kind (at least github.com), return a synthetic route. It would use the thread's own workspace root (or the server cwd) as cwd, the ref's host and repository, and the registry's API for that kind. It would also need a test that a Scratch-project thread's linked PR gets a snapshot and that a watch on it doesn't stop.

    Related, but different problems: #16455 (handoffs check out into an unrelated project), #14123 / #14218 (per-workspace GitHub credentials), #17103 (t3_thread_list reports no linked PR for off-repo PRs), and discussion #13644 (nested repos under a non-Git root). #9435 / #15946 covered the client-side link only.

    Until then, the workaround you found should still work: register any GitHub-backed project on the same host, and requireProject should route through it.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions