Skip to content

[Bug]: Self-hosted Azure DevOps Server PR links are rejected or rewritten to GitHub URLs #14400

Closed

Description

@MartinPTielemans

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. Use T3 Code with a self-hosted Azure DevOps Server/TFS repository, e.g. https://scm.example.com/tfs/Collection/Project/_git/Repo.
  2. Ask the agent to call link_pull_request with https://scm.example.com/tfs/Collection/Project/_git/Repo/pullrequest/123.
  3. Observe the rejection.
  4. Follow the tool's suggested fallback: host=scm.example.com, repository=tfs/Collection/Project/_git/Repo, number=123.
  5. Inspect the URL returned by link_pull_request and list_thread_pull_requests.

Expected behavior

Recognize the distinctive /_git//pullrequest/ URL shape on self-hosted Azure DevOps Server domains and preserve the supplied web URL, including its collection path. If provider support is unavailable, report that explicitly instead of silently synthesizing a GitHub URL.

Actual behavior

The full Azure DevOps Server URL is rejected with: "This is not a recognised pull request URL. Pass repository and number instead."
The fallback accepts the identity but returns https://scm.example.com/tfs/collection/project/_git/repo/pull/123, which is not an Azure DevOps PR route.

Verified in installed nightly code: parseChangeRequestUrl in packages/shared/src/changeRequestUrl.ts gates Azure URLs on dev.azure.com / *.visualstudio.com. The same hostname restriction is still present in public main inspected on 2026-09-30. resolveTarget in apps/server/src/mcp/toolkits/pullRequests/handlers.ts falls back to /pull/ for an unknown provider. detectSourceControlProviderFromRemoteUrl also classifies the custom server domain as unknown.

Related but distinct: #5272 concerned cloud Azure SSH remotes; #7647 concerns repository identity mismatch for cloud Azure details; #14041 concerns browser fallback after list-loading errors. I did not find an existing report of this self-hosted URL parsing/fallback bug.

The workspace PR list was also empty, but our project was a non-Git parent directory containing multiple repositories, so that screenshot is not evidence that URL parsing alone caused the empty list. A linked PR and a discovered repository are separate paths.

Full native PR listing is a separate limitation: the Azure provider uses az repos pr / az devops invoke, while Microsoft's CLI documentation explicitly supports Azure DevOps Services only, not Azure DevOps Server (https://learn.microsoft.com/en-us/azure/devops/cli/). Merely relaxing URL/provider detection must not be presented as working Server listing/authentication.

Impact

Major degradation or frequent failure

Version or commit

0.0.45-nightly.20260930.2468

Environment

Windows 11, T3 Code desktop nightly, Codex provider; self-hosted Azure DevOps Server/TFS on a custom HTTPS domain.

Logs or stack traces

link_pull_request({url: 'https://scm.example.com/tfs/Collection/Project/_git/Repo/pullrequest/123'})
=> This is not a recognised pull request URL. Pass repository and number instead.

link_pull_request({host: 'scm.example.com', repository: 'tfs/Collection/Project/_git/Repo', number: 123})
=> url: https://scm.example.com/tfs/collection/project/_git/repo/pull/123

Examples anonymized; the behavior was reproduced against a real server.

Screenshots, recordings, or supporting files

No response

Workaround

Open the original Azure DevOps URL in the browser. A local modification accepting the distinctive Azure URL path allowed the full URL to be registered and preserved after restart; this verified the link-only fix. It does not implement self-hosted provider authentication, PR listing, or status synchronization.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Sep 30, 2026
  2. juliusmarminge commented on Sep 30, 2026

    @juliusmarminge
    Member

    Triage

    I confirmed this on current main. It's a link-parsing bug, and it isn't a duplicate of #5272 (closed issue, cloud Azure SSH remotes), #7647 (open issue, cloud Azure repository identity), or #14041 (open issue, browser fallback when PR listing fails).

    Why the full URL is rejected. In packages/shared/src/changeRequestUrl.ts, parseChangeRequestUrl only accepts the /_git/<repo>/pullrequest/<id> shape on dev.azure.com and *.visualstudio.com. A self-hosted URL like https://scm.example.com/tfs/Collection/Project/_git/Repo/pullrequest/123 comes back as null, so link_pull_request returns "This is not a recognised pull request URL. Pass repository and number instead." GitLab's /-/merge_requests/ path is already trusted on any host because that shape is distinctive. /_git/.../pullrequest/<id> is just as distinctive, and the regex already captures the collection prefix (tfs/Collection/...). The hostname check is the only thing dropping Azure DevOps Server.

    Why the fallback stores a GitHub-style URL. detectSourceControlProviderFromRemoteUrl in packages/shared/src/sourceControl.ts classifies a custom domain as unknown. That makes changeRequestUrlFor return null, so resolveTarget in apps/server/src/mcp/toolkits/pullRequests/handlers.ts falls back to https://${host}/${repository}/pull/${number}. That's exactly the .../_git/repo/pull/123 URL you saw. The /pull/ fallback is deliberate for unrecognized hosts (it's covered in handlers.test.ts), but it's the wrong route when the path is clearly an Azure pull request. When a URL does parse, the original URL is kept, which fits what you saw with your local patch surviving a restart.

    Scope of a fix. Recognizing that path and keeping the supplied URL fixes linking. It won't make listing, status, or auth work: the Azure provider goes through az repos pr / az devops invoke, and Microsoft documents that CLI as Azure DevOps Services only. So relaxing the host check shouldn't be presented as on-prem PR sync support.

    Thanks for the careful write-up and for separating the linking bug from the listing limitation.

  3. added
    via-triageFiled through npx t3 triage
    and removed
    needs-triageIssue needs maintainer review and initial categorization.
    on Sep 30, 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