---
title: Linear (in development)
---

# Linear (in development)

## What it is

> **Status:** experimental / in-development. Hidden by default. Turn it on in
> **Settings → Lab → "Linear board"** (or set `AMC_SHOW_LINEAR_BOARD=1` in dev).

A first-party Omniscio integration that brings your Linear team board into the app so
you can work issues without opening the Linear website. It surfaces as a
**"Linear" row in the projects sidebar** (between Job Monitor and Marketplace)
that opens a full-pane board view.

It is gated exactly like the Jira board / Pull Requests tab — through the
unreleased-feature registry (`linear-board`), **not** a plain feature flag — so it
stays invisible until a developer flips its status to `shipped`.

## Where to find it

Once enabled, a **Linear** row appears in the projects sidebar (between Job Monitor and Marketplace); clicking it opens the full-pane board view. The enable switch is **Settings → Lab → "Linear board"**.

## How it behaves

### How to use it

1. **Enable it:** Settings → Lab → toggle **Linear board** on. A "Linear" row
   appears in the sidebar.
2. **Connect (in the panel):** open the **Linear** sidebar row and click
   **Set up Linear** (or the header **gear**, which carries a status dot). In the
   drawer, paste a **personal API key** — create one at **linear.app → Settings →
   Security & access → Personal API keys**. (Unlike Jira there is no site URL or
   email — Linear has one fixed API endpoint and the key is the only credential.)
3. Click **Save**. A green dot + "Connected as …" confirms it works. The gear stays
   in the header to update the key later.
4. Open the **Linear** sidebar row:
   - Pick a **team** from the dropdown (top-left) — a team is your "board".
   - See your **columns** (the team's workflow states: Backlog / Todo /
     In Progress / Done…) with issue **cards** (identifier, title, assignee,
     priority).
   - Click a card to open the **issue detail** drawer on the right — description
     and comments (Markdown, rendered with the app's shared pipeline), state,
     assignee, priority, labels, and a link to open the issue in Linear.

**Working issues (no browser needed):**

- **Create** a new issue with **+ New issue** (top-right) — write a title /
  description, optionally set priority, initial state, and labels.
- From the detail drawer you can **Move** the issue (pick any of the team's
  workflow states — Linear lets you set any state directly), **Assign** it
  (_Assign to me_, search a teammate, or _Unassign_), **Comment** (**Ctrl+Enter**
  to send), and **edit** the title, description, priority, and labels in place.

**Getting around a big board:**

- **Drag a card** to another column to move it (sets the issue's workflow state).
- **Filter** box (top-right) narrows the visible cards by text — identifier,
  title, assignee, or label.
- **Load more** at the bottom pulls in the next page when a team has lots of
  issues (Linear paginates by cursor).

**Stop checking the board — let it come to you:** turn on
**Settings → Lab → "Linear issues assigned to me in Inbox"**. Omniscio then checks
Linear in the background (every few minutes, only while on) and surfaces your
assigned, not-done issues as rows in the unified **Inbox** — and when one of them
_updates_, its row pops back to the top so you notice. Click a row to jump to it on
the board; snooze or dismiss like any inbox item. Requires the Linear board
(above) enabled and connected. Kill switch for power users:
`AMC_DISABLE_LINEAR_INBOX_POLL=1`.

Your API key is **encrypted at rest** on your device and is never shown back to
you or sent to the renderer once saved.

## For agents

### How it works (internals)

- **Auth:** a Linear **personal API key** sent verbatim in the `Authorization`
  header (personal keys are NOT prefixed with "Bearer"). Key auth, not OAuth
  (simplest for an experimental first cut).
- **Credentials:** stored as flat `AppSettings` fields — `linearBoardEnabled`,
  `linearApiKey`, `linearInboxEnabled`. The key is listed in
  `ENCRYPTED_APP_SETTINGS_KEYS` (encrypted at rest) and `CLI_SETTINGS_SENSITIVE_KEYS`
  (stripped from CLI/IPC reads).
- **Client:** `src/main/services/linear/linear-client.ts` — Linear exposes ONE
  GraphQL endpoint (`https://api.linear.app/graphql`), so every call goes through a
  single `linearGraphql` chokepoint that holds the auth header, the
  fixed-host SSRF assertion (`api.linear.app`, https only), a 15s timeout, and the
  error humanization. Inputs travel as GraphQL **variables** — there is no
  per-request path, hence no path-injection surface. Errors are humanized at this
  source; raw HTTP/network detail goes to logs only.
- **IPC:** `linear:auth-status` + `linear:test-connection` (connect); reads
  `linear:list-teams`, `linear:get-board`, `linear:board-issues`,
  `linear:issue-detail`; writes `linear:transition-issue`, `linear:move-to-column`,
  `linear:add-comment`, `linear:assignable-users`, `linear:assign-issue`,
  `linear:create-meta`, `linear:create-issue`, `linear:edit-issue`. None return the
  key; all humanize failures.
- **Rich text:** Linear stores descriptions and comments as **Markdown natively**,
  so they render straight through the app's shared markdown pipeline — there is no
  ADF-style conversion step like Jira needs.
- **Board model:** a "board" is a **team**; the team's **workflow states** are the
  columns; moving an issue is a direct `issueUpdate(stateId)` (no transition
  model). Priority is Linear's fixed 0–4 enum; labels are referenced by id.
- **UI:** credentials are entered in-panel via the shared `IntegrationConnectionDrawer` (a `RailShell`), opened from `LinearBoardView`'s gear / "Set up Linear" button (config: `linear-connection-config.ts`);
  the board lives in the `linear-board` feature folder (`LinearBoardView` columns +
  `LinearIssueDrawer` + `LinearCreateIssueModal` + `linear-board-store`). The
  sidebar row is gated via `isUnreleasedFeatureVisibleInRenderer('linear-board', …)`
  in Dashboard's `visibleProjects` filter.
- **My Issues (Phase 12):** a top-level `Board | My Issues | Views` switch in the
  panel header. **My Issues** is a cross-team personal view with three tabs —
  Assigned to me / Created by me / Subscribed — grouped by status category
  (Todo / In Progress / Done). It reuses the search IPC with no team filter
  (`assignee.isMe` / `creator.isMe` / `subscribers.isMe`); cards open the same
  issue drawer (`LinearMyIssuesView`).
- **Saved Views (Phase 12):** save the current List-view filter set as a named,
  reloadable view. Stored **locally** in Omniscio's SQLite (`linear_saved_views`) — Omniscio
  does not sync Linear's own customViews. Channels
  `linear:list-saved-views|create-saved-view|delete-saved-view` are local CRUD with
  no credentials gate (`LinearSavedViewsView`).
- **Projects (Phase 13):** a `Projects` surface in the same header switch — a list
  (name · health · status · progress · lead · target date) → a detail panel (lead,
  members, dates, milestones, and the project's issues, which open the issue
  drawer). Create + edit via one modal (`LinearProjectModal`): name, team (create
  only), description, lead, priority, start/target dates (members + milestone writes
  deferred). Channels `linear:list-projects|project-detail|create-project|
update-project|workspace-users` are creds-gated Linear API calls (`LinearProjectsView`).
- **File / image upload (Phase 14):** the issue drawer's Attachments section has an
  **Upload** button (beside Add link) — pick a file and it's uploaded to Linear via
  the two-step `fileUpload` flow (signed URL + PUT) and attached to the issue
  (10 MB cap). Channel `linear:upload-file` (creds-gated).
- **Linear-style nav sidebar (Phase 15):** the panel now has a real left sidebar (a
  faithful take on Linear's own) — a workspace header + switcher, Inbox, My Issues,
  Drafts, a Workspace section (Members · Projects · Teams · Views), and a "Your teams"
  section listing every team you're in (each opening its Issues / Projects / Views),
  plus a search box. A team's Projects/Views are **team-scoped** (not the
  workspace-wide ones). The old in-header tab strip is now mobile-only.
- **Project depth + collaboration (Phase 16):** opening a project gives you
  **Overview / Activity / Issues** tabs. **Activity** lets you post a project update
  with a health (on-track / at-risk / off-track) that updates the project's health,
  and shows the feed of past updates. The project editor adds a **summary** line,
  **labels**, **members**, and **status**, and you can **create milestones**.
  Comments support real **@mentions**, and a **Cmd/Ctrl+K command palette** + keyboard
  shortcuts speed up navigation.
- **Power features (Phase 17):** the things real-Linear users expect.
  - **Bulk actions** — in the List view, tick the checkbox on several issues and a
    bar appears to set their **status, assignee, or priority** all at once.
  - **Triage** — for teams that turn on Linear's Triage, each team gets a **Triage**
    entry: a queue of incoming issues you **Accept** (into a real status) or
    **Decline** (cancel) one tap at a time.
  - **Active Cycle** — for teams that use cycles (sprints), a **Cycle** entry shows
    the current sprint with a **progress bar** and its issues grouped by status.
  - **Keyboard shortcuts** — `c` new issue, `/` search, `g` then a letter to jump
    between sections, and in the List view `j`/`k` to move, `o`/Enter to open, `x` to
    select. Press **`?`** for the full cheat-sheet. (Triage + Cycle only show for
    teams that have those Linear features enabled.)
  - **React to an issue** — like a comment, you can add an emoji reaction to the
    **issue itself** (under its description), and toggle it off.
  - **Reply to comments** — comments can be **threaded**: hit **Reply** on a comment
    and your note nests under it.
  - **Issue templates** — if your team has issue templates in Linear, the New Issue
    form shows a **Template** picker that pre-fills the title, description, priority,
    and labels for you.
  - **Initiatives** — a workspace **Initiatives** view lists your initiatives (goals that
    group projects), each with its health, status, target date, and clickable project
    chips that jump straight into the project.
  - **SLA badge** — issues that carry an SLA show a header badge — **breached** (red),
    **at risk** (amber), or **on track** (green) with a countdown like "SLA in 3h".
- **Before you connect:** until you paste a working API key, the Linear sidebar shows just
  a short "Connect Linear" prompt instead of a full menu of dead links — nothing to click
  into until you're set up.
- **Live-verified:** every read query and write mutation (the My Issues filter shapes,
  the project read/create/update input shapes, the file-upload flow, and the project-
  update / health shapes) were checked against a real Linear workspace, not just
  mocked tests.
  - **Members** — the Workspace → Members page is a real table (name · role · joined ·
    teams · last seen), and you can **invite** teammates by email (with optional teams)
    right from the ⁺ button.
  - **Views** — a saved view is a real Linear **custom view**: when you save one you pick
    **Personal** or **Workspace**, and a Workspace view shows up for your teammates in
    Linear too (a lock = personal, globe = shared).
- **Not yet built:** OAuth "Connect Linear" login (would replace pasting a key and enable
  multi-workspace switching — needs a one-time Linear OAuth-app registration); the **GitHub
  integration** and **Automations** — both are set up through Linear's own website (they use
  a GitHub/OAuth app install an org owner enables), which an API key can't do; project
  resources (docs/links) and a roadmap/timeline view.

### CLI routes (headless / agent access)

The CLI control server exposes full CRUD so AI agents can work Linear issues
without the board UI. All routes require the `linear-board` feature to be visible
and a valid bearer token. Mutations are rate-limited (10/min); reads are not.

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/linear/teams` | List all teams in the workspace |
| GET | `/linear/states?teamId=` | Workflow states for one team |
| GET | `/linear/issues` | Search/filter issues (query params: `teamId`, `text`, `assignedToMe`, `stateId`, `labelId`, `priority`, `after`) |
| GET | `/linear/issues/:id` | Full issue detail (description, comments, states, relations) |
| POST | `/linear/issues` | Create issue (body: `{ teamId, title, description?, priority?, stateId?, labelIds? }`) |
| PATCH | `/linear/issues/:id` | Edit issue fields (body: any subset of editable fields) |
| POST | `/linear/issues/:id/comment` | Add comment (body: `{ body, parentId? }`) |
| POST | `/linear/move-to-column` | Drag-and-drop move (body: `{ issueId, stateIds }`) |

Source: `src/main/services/cli/cli-server-linear-routes.ts`.

### Workflow engine nodes

Linear issues can be used in Omniscio workflow automations. The trigger polls every
2 minutes; actions execute immediately.

| Node type | Kind | What it does |
|-----------|------|-------------|
| `trigger.linear_issue` | Trigger | Fires on issue created / transitioned / updated (optional team/state filter) |
| `action.linear_create_issue` | Action | Creates an issue (team + title required) |
| `action.linear_transition` | Action | Moves an issue to a workflow state |
| `action.linear_comment` | Action | Adds a comment to an issue |

The trigger watcher (`linear-issue-trigger.ts`) polls recently-updated issues,
diffs against an in-memory snapshot, and fires matching workflows. First tick
baselines only (never replays on restart). Kill switch:
`AMC_DISABLE_LINEAR_WORKFLOW_TRIGGER=1`.

Source: `src/main/services/workflow-engine/nodes/linear-*-node.ts` (auto-discovered).

## Related

- [jira-board.md](jira-board.md) — the closest analog; Linear is a 1:1 port of the
  same gating → service → IPC → store → UI shape (adapted from Jira's REST+ADF to
  Linear's GraphQL+Markdown).
- Contract: `.claude/memory/contracts/linear-board-contract.md` — the invariants and
  the tests that lock them.
