Skip to content

Linear adapter

Linear adapter (@agent-detective/linear-adapter)

Section titled “Linear adapter (@agent-detective/linear-adapter)”

Bundled plugin that receives Linear webhooks, matches issues to local repos via labels (same idea as the Jira adapter), and fans out code analysis tasks or the PR pipeline when @agent-detective/pr-pipeline is installed.

How webhooks, OAuth, and trigger phrases fit together

Section titled “How webhooks, OAuth, and trigger phrases fit together”

People often mix these up because all three appear in setup at once. They solve different problems:

PieceDirectionRole
Workspace webhook (Linear → you)Linear POSTs to your serverDelivers issue/comment events. Uses webhookSigningSecret / Linear-Signature, not the OAuth access token. You still register this URL in Linear even if you use OAuth for API calls.
OAuth (you → Linear)Your server calls Linear’s GraphQL APIAuthenticates comment/issue reads and writes (and refresh). Replaces a long-lived personal API key (PAT) for production-style installs.
#agent-detective analyze / pr (or your phrases)Inside your appAfter a Comment webhook arrives, the adapter parses the comment body and decides analyze vs PR. OAuth does not add @mentions or remove the need for trigger text—that would be a separate product change.

So: webhooks bring work in; OAuth (or PAT) lets the server act on Linear; trigger phrases decide what to do with a comment event.

  • local-repos-plugin must be enabled and configured with repos[] whose name values match Linear issue labels (case-insensitive).
  • @agent-detective/pr-pipeline is optional; without it, PR trigger comments still run label matching but receive a Linear comment explaining that the PR workflow is not loaded.

In config/default.json or config/local.json, add a plugins entry:

{
"plugins": [
{
"package": "@agent-detective/linear-adapter",
"options": {
"enabled": true,
"mockMode": true,
"apiKey": "lin_api_…"
}
}
]
}

Set enabled: true and provide credentials (see below). With mockMode: true, comment writes are logged instead of sent to Linear; reads (issue/labels, etc.) still use the API where the graph does not short-circuit them.


  1. Linear → SettingsAPIPersonal API keys → create a key.
  2. Set apiKey or LINEAR_API_KEY. No OAuth fields required.
  3. Comments and mutations are attributed to that Linear user (your avatar and name).

Good for local dev. For teams, OAuth is usually preferable (no human PAT on the server).


OAuth application in Linear (developer console)

Section titled “OAuth application in Linear (developer console)”

Create an app under Linear’s OAuth / API applications UI (exact navigation can change; look for OAuth2 or Applications).

Fields you will see (typical “Create application” form)

Section titled “Fields you will see (typical “Create application” form)”
FieldWhat to put
Application nameAny user-visible name (e.g. Agent Detective).
Developer name / URL / descriptionInformational; not used for auth mechanics.
Application iconShown for the app in Linear; use at least 256×256 if Linear asks.
Callback URLsRequired. One full URL per line. Must match exactly what this host uses: {oauthRedirectBaseUrl}/plugins/agent-detective-linear-adapter/oauth/callback where oauthRedirectBaseUrl is only the origin (e.g. https://abc.ngrok-free.app, no trailing slash). For prod + tunnel, register both callback URLs.
GitHub usernameOptional; used when Linear ties GitHub activity to actor=app. Not a substitute for actor=app or for trigger phrases.
PublicLeave off unless you want other workspaces to install the app from a directory-style flow.
Client credentialsLeave off unless you explicitly need the client_credentials grant; this adapter uses authorization code + refresh token by default.
Webhooks (on the OAuth app)This toggle is for app-level webhooks in Linear’s UI. Your adapter still uses workspace webhooks you configure separately, pointing at POST …/webhook/linear. You can leave app webhooks off and still use workspace webhooks.

After Create, copy Client ID and Client secret into oauthClientId / oauthClientSecret (or LINEAR_OAUTH_CLIENT_ID / LINEAR_OAUTH_CLIENT_SECRET — see plugin env whitelist).


These steps assume enabled: true and oauthRedirectBaseUrl set to the same public origin users and Linear will hit (ngrok, prod hostname, etc.).

1. Register OAuth routes (no access token yet)

Section titled “1. Register OAuth routes (no access token yet)”

Set oauthClientId, oauthClientSecret, oauthRedirectBaseUrl. The plugin mounts GET /plugins/agent-detective-linear-adapter/oauth/start and GET …/oauth/callback as soon as those three are present—you do not need a PAT or refresh token only to open the install URL (so the first install is not blocked).

Open:

https://<your-public-host>/plugins/agent-detective-linear-adapter/oauth/start

You are redirected to Linear, approve the app, then land on /oauth/callback?code=…&state=…. The response is JSON (Cache-Control: no-store) with access_token, often refresh_token, and sometimes expires_in.

The server does not write local.json or environment variables for you. You copy:

From callback JSONInto config / env
access_tokenapiKey or LINEAR_API_KEY
refresh_token (if present)oauthRefreshToken or LINEAR_OAUTH_REFRESH_TOKEN

Keep client id, secret, and oauthRedirectBaseUrl as they are. Restart the process so it reloads config.

If Linear rotates the refresh token later, logs warn you—update the stored refresh token yourself; nothing auto-persists.

  • Before you have a refresh token in config: you still need some way to call the API after install—either paste access_token into apiKey / LINEAR_API_KEY, or paste refresh_token and use oauthClientId + oauthClientSecret + oauthRefreshToken with empty apiKey so the adapter runs one refresh_token grant at startup to obtain the first access token.
  • After refresh is configured: you can run refresh-only (no apiKey) if you prefer.

Still configure Linear to POST to:

https://<your-public-host>/plugins/agent-detective-linear-adapter/webhook/linear

with webhookSigningSecret (unless you use skipWebhookSignatureVerification in dev only).


ModeConfig / envLinearClient
PATapiKey or LINEAR_API_KEYapiKey
OAuthapiKey / LINEAR_API_KEY = access token, plus oauthClientId, oauthClientSecret, oauthRefreshToken (or env)accessToken + refresh on expiry / auth errors

Default OAuth uses Linear’s actor=user: the access token acts as the user who clicked Authorize—comments look like your user (same symptom as a PAT).

To post as the application (app branding from the Linear developer app):

  1. Set oauthActor to "app" (or LINEAR_OAUTH_ACTOR=app).
  2. Run /oauth/start again and complete consent so Linear issues a token bound to actor=app (OAuth actor authorization).
  3. Optional: oauthAppCommentDisplayName / oauthAppCommentDisplayIconUrl for Linear’s “User (via Application)” label + public HTTPS avatar URL (createAsUser / displayIconUrl on commentCreate). If you only set an icon, the adapter supplies a default display name so the API stays valid.

If you set oauthActor: "app" but still authenticate with a PAT, comments stay as you—the server logs a warning until you use OAuth tokens from the install flow, not a PAT.


I use OAuth—why do I still configure a webhook and still type #agent-detective …?

Section titled “I use OAuth—why do I still configure a webhook and still type #agent-detective …?”
  • Webhook: Linear must push events to your server; OAuth does not replace that.
  • Trigger phrases: They are how this adapter interprets a comment after it arrives. OAuth does not add @MyApp-style invocations; that would require different detection logic or Linear’s Agents product (out of scope for this adapter’s v1 design).

Not from OAuth alone. You could extend the adapter to treat @SomeName in the comment body as a trigger if Linear includes it in the webhook payload—that is custom work, not enabled by the OAuth app record.

How does the refresh token “get into” config—is it automatic?

Section titled “How does the refresh token “get into” config—is it automatic?”

No. Only the browser JSON from /oauth/callback contains the refresh token. You copy it into oauthRefreshToken / LINEAR_OAUTH_REFRESH_TOKEN (or local.json) and restart. Rotated refresh tokens: same manual update when logs tell you.

Checklist:

  1. Plugin enabled: true and oauthClientId, oauthClientSecret, oauthRedirectBaseUrl all non-empty (routes are omitted if any is missing).
  2. oauthRedirectBaseUrl matches the same host you use in the browser (e.g. ngrok vs prod); mismatch often means nothing is listening on the host you think.
  3. Tunnel points at the correct port for agent-detective.
  4. After a bare /oauth/callback with no code, expect 400 JSON (“Missing code or state”), not 404—404 usually means wrong path or plugin not mounted.

You are on actor=user (default) or still using a PAT. Switch to oauthActor: "app", re-run /oauth/start, store the new tokens, restart.

What does Linear-Delivery / webhookDeliveryDedupWindowMs do?

Section titled “What does Linear-Delivery / webhookDeliveryDedupWindowMs do?”

Linear may retry the same HTTP delivery. Linear-Delivery identifies the attempt; the adapter skips duplicates within webhookDeliveryDedupWindowMs (default 10 minutes, 0 disables).


  • URL: POST https://<your-host>/plugins/agent-detective-linear-adapter/webhook/linear
  • Signing: set webhookSigningSecret (or LINEAR_WEBHOOK_SIGNING_SECRET). The adapter verifies Linear-Signature on the raw body and webhookTimestamp when verification is on.
  • Local dev: skipWebhookSignatureVerification: true allows unsigned payloads (trusted networks only).
  • Canonical events: linear:Issue:create and linear:Comment:create. Configure under webhookBehavior like Jira.
  • Actions: analyze, acknowledge, ignore.
  • Triggers: retryTriggerPhrase / prTriggerPhrase in comment bodies; loop protection via stamped footer + botActorIds.
  • Threading: linearReplyParentId for analyze replies; PR fan-out can reply under the PR trigger comment when Linear sends a comment id.
  • TASK_COMPLETED: posts results back to the issue when the task came from this plugin.