Skip to content

feat(issue): link and unlink external issues and GitHub PRs - #1559

Open
betegon wants to merge 22 commits into
mainfrom
bt/issue-link-unlink
Open

betegon wants to merge 22 commits into
mainfrom
bt/issue-link-unlink

Conversation

@betegon

@betegon betegon commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

Adds sentry issue link/unlink <issue> <url> for existing tracker issues and GitHub pull requests, with dry-run and JSON output. Unlink removes the stored Sentry association without fetching or deleting the remote ticket. Neither command changes the Sentry issue's status.

sentry issue link FRONT-123 https://github.com/example/app/pull/42
sentry issue link FRONT-123 https://linear.app/example/issue/APP-42/fix-error
sentry issue unlink FRONT-123 https://github.com/example/app/pull/42 --yes

All requests use the generated @sentry/api operations. Native integrations receive the full URL; the backend resolves provider identifiers and repositories. The CLI selects the installed integration and matches stored aliases for dry-run/unlink. Supports GitHub/GHE, GitLab, Jira/Jira Server, Bitbucket, and Azure DevOps; ambiguous installations require --integration.

Link always submits the mutation, including when preflight finds an existing association: a concurrent unlink can remove it before the write. HTTP 201/200 determines changed, and an already-absent unlink succeeds without a mutation.

Linear and other installed Sentry Apps use their issue-link forms, including choices, defaults, dependencies, and explicit --field values. This resolution remains in the CLI because the backend does not provide a URL-to-form resolver. Actions send expectedExternalIssueUrl to prevent replacements and mismatched callback URLs. Existing links use their stored canonical URL; new links require the exact tracker URL, including its title suffix. Callback POSTs are not retried automatically because external effects can precede persistence.

Discovery bypasses cached responses and requires complete pagination. Both commands use event:write and project access; App discovery also needs org:read. These scopes are already in the default OAuth login. Older self-hosted versions need the corresponding backend URL/guard support.

Validation:

  • 105 native/App contract tests and 102 transport/command/orchestration tests passed. Generated CLI SDK preserves repeated --field values.
  • Full suite: 10,246 passed, 14 skipped; six unchanged date tests failed under the local timezone. All 51 tests in that file passed when rerun with TZ=UTC.
  • Lint, typecheck, docs/SDK generation, fragment/dependency checks, and bundle build passed.
  • Live GitHub dry-run and repeated link returned the existing association with changed: false; the rebuilt bundle's dry-run passed. No production unlink/delete was run.

Backend follow-up: App DELETE addresses an association ID without an expected-URL precondition. A concurrent replacement of that record by a legacy unguarded client still needs a conditional-delete API change; another CLI preflight cannot make it atomic.

@vercel

vercel Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
cli Ready Ready Preview Sep 29, 2026 3:27pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Sep 29, 2026 3:27pm UTC

Request Review

@betegon betegon changed the title feat(issue): link and unlink external tracker issues feat(issue): link and unlink external issues and GitHub PRs Sep 8, 2026
Comment thread packages/cli/src/lib/api/issue-app-links.ts Outdated
Comment thread packages/cli/src/lib/api/issue-app-links.ts

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/lib/api/issue-app-links.ts Outdated
Comment thread packages/cli/src/lib/api/issue-integrations.ts Outdated

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/lib/api/issue-integrations.ts Outdated
Comment thread packages/cli/src/lib/api/issue-integrations.ts
Comment thread packages/cli/src/lib/api/issue-integrations.ts

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/lib/api/issue-integrations.ts
Keep sentry-client.ts identical to #1649 so merging it leaves no diff here. A final-attempt 401 now returns the response, which makes retry: false a plain single-attempt loop without its own code path.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel
vercel Bot temporarily deployed to Preview – sentry-local September 29, 2026 06:43 Inactive
betegon added a commit that referenced this pull request Sep 29, 2026
## Summary

Bumps `@sentry/api` from 0.256.0 to 0.337.0. #1559 needs the
issue-linking operations and the `expectedExternalIssueUrl` guard that
ship in the newer schema; landing the bump on its own keeps that PR
focused on the feature.

No source changes were needed. The only regenerated file is the `event`
skill reference, which now lists the `formatted` field the schema
documents for `event view`.

## Validation

- `tsc --noEmit` and lint pass.
- Unit suite: 473 files, 10,092 passed / 17 skipped (`TZ=UTC`).

Split out of #1559.

Co-authored-by: Cursor <cursoragent@cursor.com>
betegon and others added 5 commits September 29, 2026 12:04
Remove assertions on code the commands never call, a flag that never shipped, OAuth scope and transport retry behavior covered elsewhere, and duplicated JSON/error wrapper checks. Formatter states move to one table test instead of being asserted from three layers.

Co-authored-by: Cursor <cursoragent@cursor.com>
The four listings that authorize a link mutation each hand-rolled an all-or-nothing page loop with different errors, and only one detected repeated cursors. fetchAllPages validates every page and fails on an invalid page, a repeated cursor or the page limit. Error messages now name the listing, and running out of pages is an ApiError rather than a ValidationError.

Co-authored-by: Cursor <cursoragent@cursor.com>
Three modules each checked for an HTTP(S) URL without credentials, with different messages; parseHttpUrl now does that check. Link and unlink had near-identical native and App branches that built their results separately. Each action now has one path: planLink and findStoredLink isolate the backend differences, and results are built in one place. A dry run of an existing App link now reports its identifier, like native links already did.

Co-authored-by: Cursor <cursoragent@cursor.com>
issue link repeated resolveOrgAndIssueId's organization check only to read the project ID. The helper now returns it, so link and unlink share the resolver and its error hint.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/cli/src/lib/issue-links.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a8ed74a. Configure here.

Comment thread packages/cli/src/lib/api/issue-integrations.ts
betegon and others added 3 commits September 29, 2026 17:27
Installation selection and URL alias matching are pure, but most cases went through mocked fetches for a resolve and a PUT that were identical in every case. selectNativeIntegration is now exported, so both run as table tests. The HTTP contract keeps one test per behavior, and one end-to-end test covers link and unlink through the orchestration layer.

Co-authored-by: Cursor <cursoragent@cursor.com>
GitLab resolves group and project paths case-insensitively, so a link stored as /MyOrg/proj/-/issues/7 must match /myorg/proj/-/issues/7 when unlinking. GitHub and Bitbucket paths were already compared in lowercase.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

This branch was successfully deployed

2 active (1 outdated) and 1 inactive deployments
Preview – cli — 9c5765b5 Deployed Sep 29, 2026 by vercel[bot]
Preview – sentry-local — 9c5765b5 Deployed Sep 29, 2026 by vercel[bot]
Preview — 578a09b0 Deployed Sep 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants