feat(issue): Link external issues#1027
Conversation
Add URL-based external issue linking to update_issue using Sentry's native integration and Sentry App APIs. Resolve integration and installation identifiers internally so agents only provide an existing external issue URL. Add parser and resolver coverage for GitHub, GitLab, Jira, Bitbucket, Azure DevOps, Linear, and Shortcut links, plus mocks and generated tool definitions. Fixes #228 Co-Authored-By: GPT-5 Codex <codex@openai.com>
…e path When a combined update+link request succeeds on the issue update but fails on the external link write, the catch block now calls tryPostReasonComment before returning so the reason comment is not silently dropped. Co-Authored-By: Junior (claude-sonnet-4-5) --- [View Session in Sentry](https://sentry.sentry.io/traces/?project=4510944073809921&query=gen_ai.conversation.id%3A%22slack%3AC08J1NSPU6S%3A1780067705.499339%22)
Include the Azure DevOps organization segment when resolving dev.azure.com issue URLs so multiple VSTS integrations can be disambiguated by Sentry domainName metadata. Co-Authored-By: GPT-5 Codex <codex@openai.com>
Strip www prefixes from integration domain metadata before comparing against parsed external issue URLs. This keeps domain-based integration disambiguation consistent with host normalization. Co-Authored-By: GPT-5 Codex <codex@openai.com>
Return MCP tool result metadata when an issue update succeeds but external issue linking fails, so clients see the partial failure as an errored tool call instead of a silent success. Refs GH-228 Co-Authored-By: GPT-5 Codex <codex@openai.com>
Match native issue URLs using the full URL host so self-hosted Jira Server installations with non-default ports resolve against Sentry's domainName value. Refs GH-228 Co-Authored-By: GPT-5 Codex <codex@openai.com>
Reflect MCP tool results with isError=true in tracing span status so partial failures are not recorded as successful tool calls. Refs GH-228 Co-Authored-By: GPT-5 Codex <codex@openai.com>
…t matches - parseBitbucketUrl: add explicit bitbucket.org host guard so GitLab URLs missing the /-/ path marker no longer fall through as provider:bitbucket - resolveNativeTarget: for GitHub Enterprise URLs (host !== github.com), require a positive integration domain match; prevents arbitrary GitHub-shaped paths on unrelated hostnames from silently linking to null-domain GitHub integrations - Add regression tests covering both cases Co-Authored-By: junior (AI) --- [View Session in Sentry](https://sentry.sentry.io/traces/?project=4510944073809921&query=gen_ai.conversation.id%3A%22slack%3AC0B595QDZLL%3A1780085671.316309%22)
Treat issue updates that succeed before external linking fails as partial success instead of a failed tool call. This prevents MCP clients from retrying already-applied mutations while preserving the failure details in the response text. Refs GH-228 Co-Authored-By: GPT-5 Codex <codex@openai.com>
Drop the obsolete update_issue test helper after the partial-success tests switched to asserting normal text results. Refs GH-228 Co-Authored-By: GPT-5 Codex <codex@openai.com>
Return a comment-specific response when update_issue is called with only a reason. This avoids telling agents that no changes were needed after a comment was posted. Refs GH-228 Co-Authored-By: GPT-5 Codex <codex@openai.com>
There was a problem hiding this comment.
Non-matching domain Jira/GitLab/Bitbucket/VSTS integration selected when bestDomainScore is zero
When an organization has exactly one native integration (e.g., Jira) whose domainName is set but does not match the URL being linked, bestDomainScore is 0 and the domain-filter branch is skipped entirely, so the wrong-domain integration is selected and linkNativeExternalIssue is called against a different instance than the one the URL belongs to — producing a confusing backend error or, if the issue key coincidentally exists in the wrong instance, a silently incorrect external-issue link. The GitHub Enterprise guard exists for the same class of problem but only covers non-github.com GitHub URLs; no equivalent guard rejects a mismatched Jira/GitLab/VSTS/Bitbucket domain.
Evidence
resolveNativeTargetinissue-linking.tscomputesbestDomainScore = Math.max(0, ...candidates.map(domainMatchScore)). When the only Jira integration hasdomainName = 'other.atlassian.net'and the URL isacme.atlassian.net, everydomainMatchScorecall returns 0, sobestDomainScore = 0.- The GitHub-Enterprise guard (
parsed.provider === 'github' && parsed.host !== 'github.com' && bestDomainScore === 0) does not fire forjira/gitlab/bitbucket/vstsproviders. if (bestDomainScore > 0)is false, so the candidate list is never filtered — the wrong-domain integration remains.- Jira's
linkIssueConfigtypically contains only anexternalIssuefield with nochoices, soconfigMatchesParsedUrlreturnstruefor the wrong integration, making it the solematchingCandidate. buildNativeLinkPayloadsetspayload.externalIssue = parsed.issueIdand the PUT is dispatched to the wrong Jira integration, causing a backend error or a link to an unrelated ticket.
Identified by Warden code-review
…mismatched domains - Remove the ambiguity error when multiple candidates match a URL — use the first matching candidate instead of throwing. - When bestDomainScore is 0, filter out integrations that have an explicit domainName configured but don't match the URL. A configured domain that doesn't match is a stronger exclusion signal than having no domain at all (fixes silent wrong-instance linking for Jira/GitLab/VSTS/Bitbucket). - Update tests to reflect the new first-match behavior and add a test for the mismatched-domain exclusion case. Action taken on behalf of David Cramer. --- [View Session in Sentry](https://sentry.sentry.io/traces/?project=4510944073809921&query=gen_ai.conversation.id%3A%22slack%3AC0B595QDZLL%3A1780098433.595859%22)
- Remove unused `describeNativeCandidates` function (dead code since the multiple-candidates throw was replaced with first-match behavior). - Add an explicit guard after the bestDomainScore-0 domain filter: when all candidates are eliminated because their configured domains don't match the URL, throw a clear domain/path-specific error instead of falling through to the misleading 'can't access project or repository' message. Also short-circuits the unnecessary link-config fetch. - Reword the GitHub Enterprise guard message from 'Unsupported host' to 'No installed GHE integration matches the URL domain/path', which is accurate when the host is known but the org/path segment differs. - Tighten Jira domain-mismatch test to assert the new error shape and verify no link-config call is made; add a test for the github.com wrong-org case that exercises the new empty-candidates guard. Action taken on behalf of David Cramer. --- [View Session in Sentry](https://sentry.sentry.io/traces/?project=4510944073809921&query=gen_ai.conversation.id%3A%22slack%3AC0B595QDZLL%3A1780098433.595859%22) Co-authored-by: David Cramer <noreply>
Tighten external issue link result types and simplify parser dispatch without changing behavior. Co-Authored-By: OpenAI GPT-5 Codex <codex@openai.com>
Return an error-shaped tool result when update_issue succeeds on the issue update but fails to link the external issue. This lets MCP clients detect the partial failure programmatically while preserving the partial-success message. Co-Authored-By: OpenAI GPT-5 Codex <codex@openai.com>
Lower Warden's reporting threshold so low severity findings are posted as PR comments while keeping the failure threshold at high severity. Co-Authored-By: OpenAI GPT-5 Codex <codex@openai.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d005fa2. Configure here.
| text: output, | ||
| }, | ||
| ], | ||
| }; |
There was a problem hiding this comment.
Partial success returns isError: true contradicting reviewer guidance
Medium Severity
The partial-success response (issue update succeeded but link write failed) sets isError: true, which causes MCP clients to treat the entire operation as failed and potentially retry it. Since the Sentry issue update already succeeded, a retry would redundantly re-apply the status/assignment change. The PR discussion explicitly states this was addressed by removing isError: true so clients don't treat the already-applied update as retryable, but that fix isn't reflected in the final code.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit d005fa2. Configure here.
|
|
||
| function getTextToolResult(result: ToolHandlerResult): string { | ||
| return getToolResultText(result, false); | ||
| } |
There was a problem hiding this comment.
Unused getTextToolResult helper function never called
Low Severity
getTextToolResult is defined but never called anywhere in the codebase. The PR discussion mentions this was "fixed in a43ddea by removing the unused helper" but it remains in the final diff. Only getErrorToolResult is actually used by the partial-success tests.
Reviewed by Cursor Bugbot for commit d005fa2. Configure here.


Add URL-based external issue linking to
update_issueso agents can link an existing external ticket without knowing Sentry integration ids or provider form fields.Minimal Link API
update_issuenow links existing external issues with a single new input:The same field is used for Jira, GitHub, GitLab, Bitbucket, Azure DevOps, Linear, and Shortcut URLs. There is no provider field, integration id, installation UUID, or raw provider form payload in the public tool API.
Integration Resolution
Native issue integrations resolve through Sentry's group integration endpoints and validate parsed URL fields against Sentry link config. Sentry App links resolve installed app UUIDs internally for Linear and Shortcut URLs.
Verification
Covered with API client tests, resolver tests, update tool tests, generated definitions, and an end-to-end local stdio check linking a Sentry issue to a temporary GitHub issue.
Fixes #228