Run it

Importing from Linear

There is no importer in this binary, and there is not going to be one. SPEC-32 worked out why: the thing an import needs is a judgement per workspace — which Linear team is a project here, whether cycles are milestones or phases, what “Triage” becomes — and an importer that decides that for you is one you fight. What you want instead is a conversation with something that can call both sides.

On this page

That is this guide. It is a recipe for an agent session, not a feature.

What it needs: an MCP client (Claude Code, Claude Desktop, or anything that speaks MCP) with two servers connected — Linear’s, and this one. Nothing is installed and no secret is added to this deployment beyond the API token you would issue anyway.


1. Connect this server#

Issue a token from Settings → Tokens in the web UI, then run the stdio proxy. It is a client of the HTTP server, not a second way into the database (D-016), so the server must be running:

terminal
export ROADMAP_ADDR=127.0.0.1:8080
export ROADMAP_TOKEN=rm_...
roadmap mcp --stdio

For Claude Code, that is:

terminal
claude mcp add roadmap -- roadmap mcp --stdio

with ROADMAP_ADDR and ROADMAP_TOKEN in the environment. Connect Linear’s own MCP server alongside it however your client does that.

The token carries your whole authority — no scopes, no expiry. Use one belonging to an account whose access you are willing to lend to the import, and revoke it afterwards.

2. Decide the mapping first, in writing#

This is the step that makes an import boring, and it is the one an in-binary importer could never do for you. Before any call is made, write down four answers:

QuestionWhy it has no default
Which Linear team becomes which project?A project here needs a key you will be typing for years (ENG-142). Linear’s team key is a reasonable start and is not automatically right.
Do Linear cycles become milestones or phases?A milestone is a dated checkpoint; a phase is an ordered stage. A cycle is a dated iteration, so usually milestone — but a team using cycles as a pipeline means phases.
How do Linear states map to the six statuses?Linear’s workflow states are per-team and user-defined; ours are a closed enum: backlog, todo, in_progress, in_review, done, cancelled. Two Linear states will collide onto one of ours, and you should choose which rather than discover it.
What happens to what does not fit?Estimates, sub-issues, blocking relationships, cycles-as-velocity and attachments have no home here — PRD.md puts each explicitly out of scope. Decide whether they are dropped or written into the description, and say so once.

Give those four answers to the agent as the brief. An agent that has them makes calls; an agent that does not makes guesses that look like calls.

3. The sequence#

Order matters, because every reference is checked at write time. A composite foreign key makes an issue’s phase and milestone belong to its own project (D-007), so a phase must exist before an issue can point at it — the database rejects the alternative rather than absorbing it.

  1. project_create — one per Linear team you are bringing across.
  2. phase_create — in the order you want them; position is an integer and the order you create them in is the order they appear.
  3. milestone_create — with the dates from the cycles.
  4. label_create — Linear’s labels are per-team and may collide once flattened. Decide on a collision rather than creating bug twice.
  5. issue_create — the bulk of it. Each carries title, description, status, priority, labels, and optionally a phase and a milestone.
  6. comment_create — if you are bringing threads. This is the step that multiplies the call count; skip it deliberately rather than by forgetting.

Assignees do not map. A Linear user is not a user here, and creating accounts as a side effect of an import is a way to end up with accounts nobody can log into. Import unassigned and assign afterwards, or map by hand for the handful of people who matter.

4. Do a slice first#

Import one project, ten issues and look at the result in the web UI before running the rest. What you are looking for is not whether the calls succeeded — they either did or the tool told you why — but whether the mapping reads correctly to a person: are the statuses where you expected, do the phases mean anything, is the description still legible after Linear’s markdown made the trip.

A mapping mistake found at ten issues is an edit. Found at four hundred it is a second migration.

5. Watch it happen, and resume it if it stops#

Two properties come free, and they are the reason this recipe beats the importer that was specced against it:

  • Every created issue writes an event row through the same path the UI uses, so a half-finished import is visible in the activity log — and on the issue list live, if you have it open (SPEC-25). You can see the import running.
  • It cannot drift from the domain. It calls the same tools as everything else, so an integrity rule added later applies to it for free. A bespoke importer would be a second write path with its own bugs.

If a session dies halfway, the state is in the database and the log says where it got to. Tell the next session what is already there and let it continue.


When this recipe is the wrong tool#

A repeated, unattended sync. Everything above assumes a one-off migration with a person nearby. A nightly Linear→Roadmap sync is a different requirement: it needs conflict rules, an identity mapping that survives restarts, and a schedule — none of which a conversation provides. That is SPEC-32’s Revisit if clause, and it should be specced by somebody with a real workspace rather than bolted onto this page.

A workspace nobody understands. The mapping table in step 2 assumes somebody can answer it. If nobody can, the import is not the problem you have.


Why this is a guide and not a feature#

Briefly, because SPEC-32 has the full argument. An in-binary importer would add the first outbound HTTP call this binary makes, a new secret, a new section in the deployment guide, and a mapping configuration that is a small language — to replace a conversation. And a CSV parser written without a real export would be a parser asserting its own guesses about column names and date formats: green, wrong, and wrong only on somebody’s real migration.

The MCP surface exists so that an agent can do things a feature would otherwise have to. This is the clearest case of it in the product.