Linear OAuth + Actor Authorization

v2026.09.25

This skill should be used when implementing Linear OAuth 2.0, OAuth actor authorization, or file-storage authentication. Activates on "linear oauth", "linear auth", "actor token", "linear-actor-token", "file storage".

GitHub
Install command
npx skhub add thelobbi/linear-oauth-actor-authorization
Markdown
SKILL.md

Linear OAuth + Actor Authorization

References:

OAuth 2.0 Flow

1. Register app

Settings → API → Applications → New application. Capture:

  • LINEAR_OAUTH_CLIENT_ID
  • LINEAR_OAUTH_CLIENT_SECRET
  • Redirect URI

2. Authorization redirect

https://linear.app/oauth/authorize?
  client_id=<id>&
  redirect_uri=<uri>&
  response_type=code&
  scope=read,write,issues:create,comments:create,admin&
  state=<csrf>&
  actor=user            # optional — request actor mode

3. Token exchange

POST https://api.linear.app/oauth/token
Content-Type: application/x-www-form-urlencoded

code=<code>&redirect_uri=<uri>&client_id=<id>&client_secret=<secret>&grant_type=authorization_code

Returns:

{
  "access_token": "lin_oauth_...",
  "token_type": "Bearer",
  "expires_in": 315360000,
  "scope": "read,write"
}

Linear OAuth tokens are long-lived (10 years!). Refresh tokens are not issued — re-auth on revoke.

Actor authorization

The actor parameter on the authorize URL decides who the app's writes are attributed to.

actorToken owned byActions appear as
omitted / userthe authorising userthat user
appthe applicationthe app user (the agent)

Agent integrations want actor=app. Combined with app:assignable and app:mentionable, that is what makes the app appear in the workspace as an agent teammate that can be delegated issues and @mentioned.

https://linear.app/oauth/authorize
  ?client_id=<id>
  &redirect_uri=<uri>
  &response_type=code
  &scope=read,write,app:assignable,app:mentionable
  &state=<csrf-token>
  &actor=app

Always send and verify state — without it the callback is open to CSRF. buildAuthorizeUrl() in lib/oauth.mjs refuses to build a URL without one.

Corrected in 2.0.0. Earlier versions described a Linear-Actor-Token header carrying a 5-minute JWT "minted by your backend", handled by a lib/auth.ts mintActorToken() helper. No such header, token type, or file exists. Attribution is chosen by the actor parameter at authorization time, not per call.

File-Storage Authentication

Linear's file storage (S3-backed) uses pre-signed URLs:

  1. Upload: call fileUpload mutation → receive uploadUrl + headers
  2. PUT bytes to uploadUrl with the returned headers (don't add your Linear token there)
  3. Download: GET the asset URL with the same Linear token in Authorization header
const res = await fetch(assetUrl, {
  headers: { Authorization: `Bearer ${apiKey}` }
});

If you proxy assets to end users, mint a short-lived signed URL on your side rather than handing out your Linear token.

Rotation

  • API keys: rotate quarterly
  • OAuth client secret: rotate yearly or on suspected leak
  • Revoke tokens on uninstall and offboarding (revokeToken in lib/oauth.mjs)
  • On rotation, support old + new key for 24h overlap to avoid race conditions

Scope selection guide

ScopeRequired for
readAll read queries
writeAll mutations except admin
issues:createNarrow scope: only creating issues
comments:createNarrow scope: comments only
adminWorkflow / team / webhook config
app:assignableThe app can be delegated issues, like a teammate
app:mentionableThe app can be @mentioned in comments and descriptions
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.25

Published

Sep 25, 2026

Category

Uncategorized

License

MIT

Source path

plugins/delivery-orchestrator/skills/linear-oauth

Default branch

main

Latest commit

2f1269c

Tree SHA

629e050