Skip to content

Accept a pasted Linear URL wherever an identifier goes - #291

Open
schpetbot wants to merge 1 commit into
mainfrom
linear-urls
Open

schpetbot wants to merge 1 commit into
mainfrom
linear-urls

Conversation

@schpetbot

Copy link
Copy Markdown
Collaborator

Copying a link out of Linear and pasting it into the CLI is the obvious way to refer to something, and it did not work.

linear issue view <issue url> failed outright with "Could not determine issue ID". Project and document URLs appeared to work, but only because Linear's API quietly extracts a slug when a whole URL is handed to a slugId filter — undocumented behavior the CLI should not lean on, and one that produces a bare not found: https://… the moment the URL points at anything else.

What changed

A new pure module classifies a reference as one of three things:

  • ordinary input — falls through untouched, so every existing ID, slug, name and UUID lookup is unchanged;
  • a Linear URL naming something the CLI can look up — extracted locally and never sent to GraphQL as a URL;
  • a Linear URL it cannot use — reported, rather than retried as a name. This is what turns /settings from a mysterious missing project into a sentence.

Wiring it into the shared resolvers covers most of the surface at once, since positional arguments and flags like --project, --parent and --team already funnel through them. The rest had to be found: ten commands define their own local resolver shadowing the shared one, and five document commands pass an id straight to document(id:).

linear issue view https://linear.app/acme/issue/ENG-123/some-title
linear project view https://linear.app/acme/project/mobile-launch-272f50ef9250
linear issue update ENG-1 --parent https://linear.app/acme/issue/ENG-7/x

Scheme optional; query strings and title slugs ignored.

Errors

Input Before Now
issue URL to project view Project not found: https://… is an issue URL, not a project URL
URL from another workspace resolved, or not-found names both workspaces, suggests --workspace
/settings URL Project not found: https://… "settings" is not an entity this command can use
milestone / label / template not found: https://… this command does not take one

Deliberately refused rather than guessed

  • Cycle URLs. Cycle has no url field in the schema, so the /team/KEY/cycle/N shape is inferred — and a wrong guess resolves to the wrong cycle.
  • Comment IDs. The #comment- anchor keeps only the first 8 characters of the UUID. The issue is in the path, so an issue-scoped prefix search looked feasible — but linear.ts fetches comments(first: 50) with no pagination, and a lookup that can silently miss a match on page two has no business backing issue comment delete. A comment URL still names its issue, so issue view takes one.

issue link is untouched

A lone URL argument there already means the thing being linked, not the issue to link it to — and a Linear URL is a perfectly reasonable thing to attach. That branch is unchanged and locked by a regression test.

Testing

Most behavior is pure and covered by a table seeded from the url values Linear's own API returns, plus lookalike hosts (linear.app.evil.example), ports, credentials, scheme-less, www, http, casing, query strings, and unknown paths. Resolver tests prove the extracted value reaches GraphQL rather than the URL.

Live-QA'd against the real API across 15 cases. Two defects were found there and fixed: label delete and template view were missed in the first pass, and the workspace guard never fired because it had been written to stay silent under a raw LINEAR_API_KEY.

Known, pre-existing, not fixed here

Seven initiative commands (view, update, delete, archive, unarchive, add-project, remove-project) call their resolver before the action's try, so any resolution error prints a stack trace instead of a clean message. This is pre-existing — linear initiative view some-garbage-name stack-traces on main today — but this change makes the path easier to hit. Fixing it means restructuring seven action bodies, which did not belong in this commit. Happy to do it as a follow-up.

Copying a link out of Linear and pasting it into the CLI is the obvious way to
refer to something, and it did not work. `issue view <url>` failed outright with
"Could not determine issue ID". Project and document URLs appeared to work, but
only because Linear's API quietly extracts a slug when a whole URL is handed to
a `slugId` filter -- undocumented behavior the CLI should not lean on, and one
that produces a bare "not found: https://..." the moment the URL points at
anything else.

Parse the URL locally instead. A new module classifies a reference as ordinary
input, a Linear URL naming something the CLI can look up, or a Linear URL it
cannot use. The first falls through untouched, so every existing ID, slug, name
and UUID lookup is unchanged -- and so is `issue link`, where a lone URL
argument still means the thing being linked rather than the issue to link it to.
The third is reported rather than retried as a name, which is what turns
`/settings` from a mysterious missing project into a sentence.

Wiring it into the shared resolvers covers most of the surface at once, since
positional arguments and flags like --project, --parent and --team already
funnel through them. The rest needed finding: ten commands define their own
local resolver shadowing the shared one, and five document commands pass an id
straight to `document(id:)`. Each gets the same extraction.

A URL for the wrong kind of thing now names what it actually points at, and one
from another workspace names both. The workspace comparison trusts the
configured workspace even under a raw LINEAR_API_KEY, where the key's
organization is not knowable locally: refusing to guess there would disable the
check for the most common setup, which is exactly when a URL from the wrong
workspace gets pasted.

Two things are deliberately refused rather than guessed. Cycle URLs, because
Cycle has no url field in the schema and a wrong guess at the shape resolves to
the wrong cycle. And comment IDs, because the #comment- anchor keeps only the
first eight characters of the UUID; the issue is in the path, so an
issue-scoped prefix search looked feasible, but the issue query fetches
comments(first: 50) with no pagination, and a lookup that can silently miss a
match on page two has no business backing `issue comment delete`. A comment URL
still names its issue, so `issue view` takes one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants