feat(git): add git review-url to resolve an issue's Linear review URL - #45
feat(git): add git review-url to resolve an issue's Linear review URL#45oliviasculley wants to merge 2 commits into
git review-url to resolve an issue's Linear review URL#45Conversation
Linear's review page for a pull request (linear.app/<workspace>/review/<slug>) has no public lookup from a GitHub PR URL, and the slug appears nowhere on the issue or its attachments — `issue.attachments` and `attachmentsForURL` return the GitHub URL and GitHub metadata only. The slug lives on `PullRequest.slugId`, and the one path that reaches a `PullRequest` with a personal API key is the agent sessions attached to an issue (`Query.diff` is [Internal] and takes a `Diff` id nothing hands out). So `git review-url <issue>` walks `issue.agentSessions.pullRequests`, pairs each `slugId` with `organization.urlKey`, and prints the review URL — one per line, or `-o json` for the PR number, state, title and GitHub URL alongside it. A pull request linked by more than one session is listed once. The limitation is inherent to the API rather than to this command, so it is stated in `--help`, in the README, and in the error raised when an issue resolves to no slug, which points at the GitHub PR URL instead of failing silently.
The first pass claimed a review URL exists only for pull requests linked to an agent session. That is wrong: Linear creates a review page for any pull request it detects from a branch, and `PullRequestNotification` exposes it — `url` on the notification is the review page itself (`review/<title-slug>-<id>`), alongside the `pullRequest` it belongs to. So `review-url` now matches the issue's `github` pull request attachments against the notification feed and returns that URL verbatim, which also preserves the human-readable title slug instead of dropping it by assembling `review/<id>` by hand. A comment notification's `#comment-<id>` anchor is trimmed so the result is the page, not a position in it. The agent-session path stays as the fallback for a pull request with no notifications, and results are merged per pull request so a PR reachable both ways is listed once. The feed has no server-side pull request filter, so it is walked newest-first for at most 5 pages; a pull request whose activity is older than that falls through to the fallback. What remains genuinely unresolvable is a pull request with no notification at all — typically one opened minutes ago with no CI result, comment, or review yet — and the error says so rather than emitting a URL that would 404.
|
|
I'm not too familiar with rust but hopefully this is good, and the command design makes sense, I'm happy to change anything about this! |
|
Thanks for the PR, I will review this ASAP. |
Thermo-nuclear code quality reviewVerdict: request changes 1. [Blocker] Decompose
|
What
linear-cli git review-url <issue>prints the Linear review page URL(s) for the pull requests attached to an issue:How, and why this way
There is no public GitHub-URL → review-URL lookup, and the slug is not on the issue:
issue.attachments(andattachmentsForURL) carry the pull request as asourceType: "github"attachment with rich GitHub metadata and no review URL. So this resolves in two steps:PullRequestNotification— the one public place a pull request is paired with its review page. Itsurlis the review URL (review/<title-slug>-<id>), so it's returned verbatim; a#comment-<id>anchor from a comment notification is trimmed. The command matches the issue'sgithubpull request attachments against the feed. The feed has no server-side filter for pull requests, so it's walked newest-first for at most 5 pages — the one part of this I'd happily replace if there's a filter I missed.AgentSession.pullRequestsexposesPullRequest.slugIddirectly, from which the URL is assembled withorganization.urlKey.Results are merged per pull request, so a PR reachable both ways is listed once.
What stays unresolvable is a pull request with no notification and no agent session — in practice one opened minutes ago with no CI result, comment, or review yet. The command reports that (naming the GitHub URL as the alternative) rather than emitting a URL that would 404. Happy to switch that to exit 0 with empty output if you'd prefer it for scripting.
For completeness on what was ruled out:
Query.diff(id:)reaches aPullRequestbut is[Internal]and takes aDiffid nothing public hands out — it rejects a slug, a PR URL, an attachment id, a GitHub node id, and apullRequestIdfrom a notification.Testing
cargo fmtclean on the touched files;cargo clippy --all-targetsadds no new warnings.git::handlegains the&OutputOptionsargument the other command modules already take, so-o json/--formatwork here too.