> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognee.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Linear

> Install Cognee as a Linear agent that answers @mentions and delegated issues from memory, and index the workspace's issues.

Connect a Linear **workspace** to Cognee by installing a Linear OAuth app into it as an **agent**. Workspace members can then @mention the agent or delegate an issue to it and get an answer drawn from your Cognee memory, while the workspace's issues flow into a dataset of their own and stay fresh through webhooks.

<Info>
  This integration is for self-hosted Cognee deployments — you create and own the Linear OAuth app, so it lives in your own Linear workspace settings and talks only to your own Cognee backend.
</Info>

**Ships on `dev`.** The connector landed after `1.5.3`, so it is not in a tagged release yet — run a `dev` checkout (`git checkout dev && uv sync`) until the next release.

## What It Does

* **Installs as an agent, not a bot user.** The authorize URL carries `actor=app`, which puts an *app user* — the agent's identity — into the workspace. The `app:assignable` and `app:mentionable` scopes are what let members delegate issues to it and @mention it. Requested scopes are `read,write,app:assignable,app:mentionable`.
* **Answers agent sessions from memory.** A mention or a delegation opens an agent session; Cognee runs a [`HYBRID_COMPLETION`](/python-api/search-type) search across **every dataset the connecting user can read** — not just the Linear one — and replies inside the session.
* **Indexes the workspace's issues.** Issue creates and updates are remembered as plain text in one dataset per workspace, plus a backfill of recent issues at install time.
* **One workspace, one Cognee user.** The credential is owned by whoever completed the install, and answers come from that user's memory.

## Prerequisites

* A Cognee `dev` checkout — see the version note above.
* A running Cognee backend reachable over **HTTPS from the public internet**: Linear delivers webhooks and the OAuth redirect to it and will not call `localhost`. For local development use a tunnel (ngrok or similar) — see the [Slack integration's ngrok section](/integrations/slack-integration#local-development-ngrok-tunnel), which applies verbatim; for production this is your deployment's normal public domain.
* Permission to create an OAuth application in the target Linear workspace.

## Setup

### 1. Create the Linear OAuth app

Go to [linear.app/settings/api/applications](https://linear.app/settings/api/applications) → create a new application and configure:

| Setting                    | Value                                                                                                                                                                           |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Callback URL**           | `https://<public-host>/api/v1/integrations/linear/callback` — must match `LINEAR_REDIRECT_URI` exactly.                                                                         |
| **Agent capabilities**     | **Enabled.** Cognee installs with `actor=app`, so the app must be allowed to install an app user. Without it the `app:assignable` / `app:mentionable` scopes are not grantable. |
| **Webhook URL**            | `https://<public-host>/api/v1/integrations/linear/events`                                                                                                                       |
| **Webhook events**         | **Agent session events** (the point of the integration), **Issues**, and **App revoked (OAuth)**.                                                                               |
| **Webhook signing secret** | Generated by Linear — this becomes `LINEAR_WEBHOOK_SECRET`.                                                                                                                     |

Then collect the app's **Client ID** and **Client secret**.

<Warning>
  Create a **separate app per environment** (local dev, staging, production). One app has one webhook URL, so pointing a shared app at a laptop routes another environment's deliveries there.
</Warning>

### 2. Configure Cognee

Copy the app's values into your backend's `.env` (the same block is in `.env.template`):

| Variable                      | Value                                                                                                                                                                                                                                                                                                                                                                                   |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LINEAR_CLIENT_ID`            | From the app's settings page.                                                                                                                                                                                                                                                                                                                                                           |
| `LINEAR_CLIENT_SECRET`        | From the app's settings page.                                                                                                                                                                                                                                                                                                                                                           |
| `LINEAR_WEBHOOK_SECRET`       | The app's webhook signing secret. It verifies every inbound delivery's `Linear-Signature` **and** signs the OAuth `state` parameter — both server-side-only uses of the same secret.                                                                                                                                                                                                    |
| `LINEAR_REDIRECT_URI`         | The callback URL registered on the app, e.g. `http://localhost:8000/api/v1/integrations/linear/callback`. It is sent on both the authorize request and the token exchange, so it must match the app's registered value exactly.                                                                                                                                                         |
| `LINEAR_FRONTEND_BASE_URL`    | Your frontend's origin, e.g. `http://localhost:3000` — where the callback redirects the browser when the install finishes.                                                                                                                                                                                                                                                              |
| `INTEGRATION_CREDENTIALS_KEY` | Not Linear-specific — the AES-256-GCM key (32 raw bytes, base64-encoded) the integrations framework encrypts stored credentials under. Unlike GitHub, Linear stores a real access token in that payload, so this key is what protects it at rest. Generate one with `openssl rand -base64 32`. `INTEGRATION_CREDENTIALS_KEYS` (a JSON keyring) is the preferred, rotation-capable form. |

Every one of these is checked at use time, not at boot: a deployment with Linear unconfigured still starts normally, and the first call that needs a missing value fails with `LINEAR_<NAME> is not configured` rather than a confusing downstream error. `POST /authorize` turns that into a **503** (`linear integration is not configured on this server`) instead of a 500.

Restart the backend after any `.env` change.

### 3. Install and connect

The backend ships without frontend wiring for Linear in this release — the aggregate `GET /api/v1/integrations/status` reports the provider generically, but there is no **Connect** button yet. Drive the flow through the API:

```bash theme={null}
# Mint the authorize URL (authenticated as the Cognee user who will own the data)
curl -X POST https://<public-host>/api/v1/integrations/linear/authorize \
  -H "Authorization: Bearer <your-cognee-token>"
# {"authorizeUrl":"https://linear.app/oauth/authorize?...&actor=app&scope=read,write,app:assignable,app:mentionable"}
```

Open that URL in a browser, pick the workspace, and approve. Linear redirects back to `/api/v1/integrations/linear/callback`, which binds the workspace to your Cognee account and then sends the browser to `<LINEAR_FRONTEND_BASE_URL>/integrations?linear=connected`.

<Warning>
  **Install from the minted URL.** The signed `state` is what binds the install to a Cognee account, and it expires **10 minutes** after `/authorize` returns. An install started without it (or after it expires) lands on `?linear=error_invalid_state` and stores nothing.
</Warning>

The callback does one extra round trip before it redirects: Linear's token response says nothing about *which* workspace authorized the app, but every webhook delivery routes by its `organizationId`, so the fresh token is immediately spent on a GraphQL query for `viewer` (the newly installed app user) and `organization`. The organization id becomes the credential's account id; the organization name becomes its display label.

The initial issue backfill is kicked off **detached**, after the redirect — the browser returns immediately.

## What Gets Indexed

Issues land in **one dataset per workspace**, named `linear_<url_key>`: the workspace's Linear URL key (the slug in `linear.app/<url_key>`) lowercased, with every run of characters outside `A-Z a-z 0-9 _` collapsed to `_`. A workspace at `linear.app/acme-corp` becomes `linear_acme_corp`. One dataset per workspace rather than per team or per issue means [backend access control](/core-concepts/multi-user-mode/multi-user-mode-overview) isolates at the workspace boundary — per-issue datasets would mean one isolated database per issue.

Each issue is stored as a deterministic plain-text rendering — identifier, title, URL, state, description, and nothing volatile — so re-syncing an unchanged issue produces byte-identical text instead of churning the graph.

```
Linear issue ENG-42: Rate limiter drops bursts
URL: https://linear.app/acme-corp/issue/ENG-42
State: In Progress
Description: ...
```

At install time Cognee backfills the workspace's **50 most recently updated issues** in a single [`remember()`](/python-api/remember) call — webhooks only cover changes from then on, so this is what gives the agent something to recall from on day one. Because it is ordinary text ingestion, the content is reachable through the normal search types, and you can query the dataset directly:

```python theme={null}
import cognee

results = await cognee.recall(
    "What is the status of the rate limiter work?",
    datasets=["linear_acme_corp"],
)
```

<Note>
  Both the backfill and per-issue webhook syncs run with `self_improvement=False`, so they never trigger an [`improve()`](/python-api/improve) pass. Whole-graph enrichment carries LLM cost that is far too heavy to fire on every issue edit — it stays a human or scheduled decision.
</Note>

## Answering Agent Sessions

An agent session opens when a member **@mentions** the agent (`AgentSessionEvent` / `created`) or **delegates an issue** to it, and continues when they reply inside the session (`prompted`). For each turn Cognee:

1. **Posts a `thought` activity immediately** — "Searching cognee memory…" — before any search or LLM work. Linear requires an activity within **10 seconds** of a `created` event or it marks the session unresponsive, and a `HYBRID_COMPLETION` search routinely takes longer than that.
2. **Builds the query.** Linear's own `promptContext` (its digest of the issue and prior comments) and workspace-level `guidance` are prepended to the user's question, so retrieval is grounded in what the session is about. On a `prompted` event the question is the new message; on a `created` event it is the triggering comment, or — for a delegation, which has no comment — the issue's title and description.
3. **Searches memory** with `HYBRID_COMPLETION` across every dataset the connecting user can read, keeping the top three answers and discarding per-chunk completions that are really refusals ("no relevant information…").
4. **Posts a `response` activity** with the answer, which completes the turn.

Every failure path ends in an **`error` activity** rather than silence, so a session never sits showing the agent as working forever. Two cases answer politely instead of erroring: no question could be found in the session, and nothing relevant in memory ("No relevant information found in cognee memory.").

## Keeping It in Sync

Webhook deliveries to `POST /api/v1/integrations/linear/events` drive everything after the install. Coverage is deliberately narrow in this first cut:

| Event                                       | Behavior                                                                                                                                                     |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `AgentSessionEvent` (`created`, `prompted`) | Answers the session from memory — see above.                                                                                                                 |
| `Issue` (`create`, `update`)                | Remembers the issue's current text into the workspace dataset.                                                                                               |
| `OAuthApp` (`revoked`)                      | Revokes the stored credential: the workspace removed the app on Linear's side, so the local token stops being used. Idempotent, so a redelivery is harmless. |

Everything else — **issue deletions included** — is logged and dropped. Deleting indexed data on a webhook would be a silent, destructive surprise, so [`forget()`](/python-api/forget) stays a human decision.

A delivery for a workspace Cognee doesn't know (or has revoked) is logged and dropped rather than erroring — Linear gives no ordering guarantee, so a delivery racing ahead of the callback storing the credential is a normal, self-healing state.

### Webhook Security

Every delivery is authenticated by a hex **HMAC-SHA256 over the raw request body**, keyed with `LINEAR_WEBHOOK_SECRET` and compared against the `Linear-Signature` header with a constant-time comparison. That signature is the entire auth model for the route — it is unauthenticated by design, since Linear cannot send a bearer token. A bad or missing signature is a **401**.

Linear also carries replay protection, which Cognee enforces: the payload's `webhookTimestamp` (Unix milliseconds) must be within **60 seconds** of the server's clock, or the delivery is rejected as `Stale Linear webhook timestamp` — also a **401**. A missing or malformed timestamp is rejected the same as a stale one, and the check runs **only after the HMAC passes**, since a timestamp read from unverified bytes proves nothing.

Two consequences worth planning for:

* **Keep the Cognee host's clock in sync** (NTP). More than a minute of drift rejects every delivery even though its signature is perfect.
* **Rotating the signing secret is a two-sided change.** Deliveries signed with the old secret fail the HMAC, so rotate on Linear and update `LINEAR_WEBHOOK_SECRET` together and restart the backend. Because the same secret also signs the OAuth `state`, an install that is mid-flight during a rotation lands on `?linear=error_invalid_state`; just mint a fresh authorize URL.

Cognee acks the delivery as soon as both checks pass and handles it detached, so Linear's delivery timeout is never in play no matter how long a search takes. The 10-second rule is separate — it is Linear's clock on the *session*, which only the `thought` activity satisfies.

## Disconnect vs. Uninstall

Neither deletes indexed data:

* **Disconnect in Cognee** — `DELETE /api/v1/integrations/linear/connection` — marks the stored credential revoked, so agent sessions and issue syncs stop. It also makes a **best-effort remote revoke**: Linear exposes a cheap token-revoke endpoint that kills only Cognee's own token without touching the app install, so unlike the GitHub connector this is actually called. It is best effort by design — the local revoke is the real cut-off, and a network blip must never block a disconnect.
* **Removing the app in Linear** fires the `OAuthApp` `revoked` webhook, which revokes the credential server-side — the same end state, reached from the other side.

The `linear_<url_key>` dataset stays exactly as it is in both cases. To remove the indexed data itself, use [`forget()`](/python-api/forget).

## Troubleshooting

1. **`POST /authorize` returns 503 "linear integration is not configured on this server"**: `LINEAR_CLIENT_ID`, `LINEAR_REDIRECT_URI`, or `LINEAR_WEBHOOK_SECRET` (it signs the state) is unset. The backend log names the exact variable. A missing `LINEAR_FRONTEND_BASE_URL` surfaces later, as the same 503 from the callback instead.
2. **Redirected to `?linear=error_invalid_state`**: the install didn't start from a freshly minted `/authorize` URL, more than 10 minutes passed before approving it, or `LINEAR_WEBHOOK_SECRET` changed in between. Mint a new one.
3. **Redirected to `?linear=error_exchange_failed`**: the callback never 500s — it catches everything and redirects, and the traceback is in the **backend log**. Usual causes: mismatched `LINEAR_CLIENT_ID`/`LINEAR_CLIENT_SECRET`, a `LINEAR_REDIRECT_URI` that doesn't exactly match the app's registered callback URL, a missing `INTEGRATION_CREDENTIALS_KEY`, or the identity query failing (`Linear token response carries no organization id`).
4. **Redirected to `?linear=error_already_connected`**: this workspace is bound to another Cognee user. Disconnect it there first.
5. **Redirected to `?linear=cancelled`**: the consent screen was cancelled, or Linear rejected the request. Nothing was stored.
6. **Connect succeeds but no issues appear**: the backfill runs detached — check the backend log for `Syncing N Linear issues for organization <id> into dataset linear_<url_key>`, or `has no issues to sync`. It covers the 50 most recently updated issues only; everything older arrives when those issues are next touched.
7. **Webhook deliveries return 401**: either `LINEAR_WEBHOOK_SECRET` doesn't match the app's signing secret (easy to hit when juggling more than one app), or the host clock has drifted more than 60 seconds. The response detail distinguishes the two — `Invalid Linear signature` versus `Stale Linear webhook timestamp`.
8. **Webhook deliveries return 404**: the provider name in the URL is wrong, or the backend is running a build without the Linear connector registered. The route is `/api/v1/integrations/linear/events`.
9. **The agent posts an error activity in the session**: the search or the reply failed — the backend log carries the traceback under `Linear agent session <id>: answering failed`. If the answer instead says the connection "is not fully configured", the Cognee user who installed the integration was deleted; disconnect and reconnect.
10. **The agent replies "No relevant information found in cognee memory."**: the search returned nothing usable for that question. Remember that it searches the installing user's datasets — content ingested by a different Cognee user is not visible to it.

## Related

<CardGroup cols={2}>
  <Card title="Slack" icon="messages-square" href="/integrations/slack-integration">
    Ask and save memory from a conversation, sharing the same credential store and connect flow.
  </Card>

  <Card title="GitHub" icon="github" href="/integrations/github-integration">
    The other first-party webhook connector — an org's repositories into the code graph.
  </Card>
</CardGroup>
