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.
linear_source() connector instead — it needs no extra and no OAuth app.
Ships in 1.5.4. The connector landed after 1.5.3 and is first tagged in v1.5.4, so cognee>=1.5.4 carries it — no dev checkout is needed. See the changelog.
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. Theapp:assignableandapp:mentionablescopes are what let members delegate issues to it and @mention it. Requested scopes areread,write,app:assignable,app:mentionable. - Answers agent sessions from memory. A mention or a delegation opens an agent session; Cognee runs a
HYBRID_COMPLETIONsearch 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
- Cognee
>= 1.5.4— 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, 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 → create a new application and configure:
Then collect the app’s Client ID and Client secret.
2. Configure Cognee
Copy the app’s values into your backend’s.env (the same block is in .env.template):
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 aggregateGET /api/v1/integrations/status reports the provider generically, but there is no Connect button yet. Drive the flow through the API:
/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.
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, namedlinear_<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 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.
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:
Both the backfill and per-issue webhook syncs run with
self_improvement=False, so they never trigger an 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.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:
- Posts a
thoughtactivity immediately — “Searching cognee memory…” — before any search or LLM work. Linear requires an activity within 10 seconds of acreatedevent or it marks the session unresponsive, and aHYBRID_COMPLETIONsearch routinely takes longer than that. - Builds the query. Linear’s own
promptContext(its digest of the issue and prior comments) and workspace-levelguidanceare prepended to the user’s question, so retrieval is grounded in what the session is about. On apromptedevent the question is the new message; on acreatedevent it is the triggering comment, or — for a delegation, which has no comment — the issue’s title and description. - Searches memory with
HYBRID_COMPLETIONacross every dataset the connecting user can read, keeping the top three answers and discarding per-chunk completions that are really refusals (“no relevant information…”). - Posts a
responseactivity with the answer, which completes the turn.
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 toPOST /api/v1/integrations/linear/events drive everything after the install. Coverage is deliberately narrow in this first cut:
Everything else — issue deletions included — is logged and dropped. Deleting indexed data on a webhook would be a silent, destructive surprise, so
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.
Token Lifetime and Refresh
A Linear access token lasts about 24 hours, and Linear rotates the refresh token: every refresh hands back a new one and kills the one just spent. Cognee handles both — there is nothing to configure and no reconnect to schedule.- Refreshed just in time, not on a timer. Every Linear API call — answering an agent session, syncing an issue — goes through one code path that refreshes the stored token when it is within 5 minutes of expiring. The margin is there so a sync that takes a while doesn’t expire halfway through its own issue list, and the refresh itself has a short timeout so it fits inside the agent session’s 10-second acknowledgement budget.
- One refresh at a time per workspace. Concurrent callers who all find the token expiring don’t each spend the same refresh token: they serialize behind one refresh, and the write back to storage is a compare-and-swap against the stored token, so a second backend process or a reconnect landing at the same moment cannot be overwritten.
- The stored refresh token is replaced on every refresh. The response’s new refresh token is what gets saved — carrying the old one forward would make the second refresh fail and disconnect the workspace about two days after it connected.
- A Linear
401also triggers a refresh. The stored expiry can be wrong, or Linear can end a token early, so a rejected token forces a refresh even when the expiry looks fine, and the call is retried once. A token that a forced refresh minted less than a minute ago and Linear still rejects is not rotated again — nothing a new token can fix is wrong. - A transient failure falls back to the token that still works. Inside the 5-minute margin the stored token is still valid, so a refresh that times out or hits a gateway error keeps using it, and one more attempt runs about a minute later (which also recovers a rotation Linear performed but Cognee lost). Callers arriving in the next few seconds reuse that recorded failure instead of each sending their own doomed request.
- A rejected refresh token is terminal. Linear answering
invalid_grantmeans the stored refresh token is dead and only a reconnect brings it back, so Cognee revokes the credential locally rather than leaving a connection that reads as healthy while every session fails. See the troubleshooting entries below.
Webhook Security
Every delivery is authenticated by a hex HMAC-SHA256 over the raw request body, keyed withLINEAR_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_SECRETtogether and restart the backend. Because the same secret also signs the OAuthstate, an install that is mid-flight during a rotation lands on?linear=error_invalid_state; just mint a fresh authorize URL.
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 tokens without touching the app install, so unlike the GitHub connector this is actually called. Both stored tokens are revoked — the refresh token first, then the access token — since Linear doesn’t document whether revoking one ends the other. It posts the tokens it already holds rather than refreshing first (by disconnect time the access token has usually expired anyway, and a refresh would only add a way for this to fail). 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
OAuthApprevokedwebhook, which revokes the credential server-side — the same end state, reached from the other side.
linear_<url_key> dataset stays exactly as it is in both cases. To remove the indexed data itself, use forget().
Troubleshooting
POST /authorizereturns 503 “linear integration is not configured on this server”:LINEAR_CLIENT_ID,LINEAR_REDIRECT_URI, orLINEAR_WEBHOOK_SECRET(it signs the state) is unset. The backend log names the exact variable. A missingLINEAR_FRONTEND_BASE_URLsurfaces later, as the same 503 from the callback instead.- Redirected to
?linear=error_invalid_state: the install didn’t start from a freshly minted/authorizeURL, more than 10 minutes passed before approving it, orLINEAR_WEBHOOK_SECRETchanged in between. Mint a new one. - 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: mismatchedLINEAR_CLIENT_ID/LINEAR_CLIENT_SECRET, aLINEAR_REDIRECT_URIthat doesn’t exactly match the app’s registered callback URL, a missingINTEGRATION_CREDENTIALS_KEY, or the identity query failing (Linear token response carries no organization id). - Redirected to
?linear=error_already_connected: this workspace is bound to another Cognee user. Disconnect it there first. - Redirected to
?linear=cancelled: the consent screen was cancelled, or Linear rejected the request. Nothing was stored. - 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>, orhas no issues to sync. It covers the 50 most recently updated issues only; everything older arrives when those issues are next touched. - Webhook deliveries return 401: either
LINEAR_WEBHOOK_SECRETdoesn’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 signatureversusStale Linear webhook timestamp. - 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. - 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. - 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.
- The connection flipped to disconnected on its own: Linear rejected the stored refresh token (
invalid_grant), which is terminal — the backend log carriesLinear rejected the refresh token for organization <id>; local credential revoked, the workspace must reconnect. Reconnect from/authorize; nothing else recovers it. The log also distinguishes a credential that holds no refresh token at all (holds no refresh token; the workspace must reconnect) — a connection whose install returned no refresh token. That credential is not revoked, so it still reads as connected: it keeps working until its access token expires and then needs the same reconnect. - Every refresh fails right after a
LINEAR_CLIENT_SECRETrotation: Linear is rejecting the app’s own credentials (invalid_clientand friends), logged asLinear rejected the app's own credentials refreshing the token for organization <id>. These are never retried — no retry can fix a wrong secret — and the failure is remembered for 5 minutes so every caller doesn’t resend the same doomed request. Put the current Client ID and Client secret in.envand restart the backend: they’re read at start-up, so an edit alone changes nothing. - Occasional
using the token that is still validwarnings: a refresh timed out or the token endpoint answered with an error, inside the 5-minute margin where the stored token still works. For a timeout or gateway error this is the designed fallback, not a failure — one retry runs about a minute later. The same warning is logged when the refresh fails on a configuration error or a missing refresh token, which are not retried, so act on a run of them that comes with the entry 11 or entry 12 log lines.
Related
Slack
Ask and save memory from a conversation, sharing the same credential store and connect flow.
GitHub
The other first-party webhook connector — an org’s repositories into the code graph.