Linear
Register a Linear OAuth application in the product UI, or optionally set a deployment-wide app with LINEAR_* environment variables.
Self-hosted Linear uses an OAuth application that you create in Linear. The
usual path is to paste the client ID, client secret, and webhook signing secret
into the connector wizard. No LINEAR_* environment variables or service
restart are required for that path.
The same routes run on hosted ctx| and on self-host. Hosted ctx| keeps a deployment-wide Linear app in environment variables as a fallback. Self-host operators can use that fallback too if they want one shared Linear application for the whole deployment.
The worker decrypts the saved client secret with the same key the backend uses
(AUTH_SECRET or CONNECTION_SECRETS_ENCRYPTION_KEY). That is not a new Linear
environment variable.
What you need
- A public HTTPS
AUTH_BASE_URL. In production, keepCTXPIPE_PUBLIC_APP_URLon the same origin. - Permission to create a Linear OAuth application.
- A ctx| GitHub App connection with write access to a destination repository.
- The backend and worker able to reach
api.linear.appover HTTPS.
Connector state and backups
Linear scope remains reviewable in Git:
- Draft scope is
linear/config.yamlon the generated configuration pull request branch. - Live scope is the same YAML on the configured target branch after merge.
- The deployment stores encrypted credentials, repository binding, and setup
status on the
connectionsrow. - Signed Linear events update in-scope generated files through durable worker jobs.
Back up PostgreSQL for connection credentials and binding state, and back up the target Git repository for both scope and mirrored content. PostgreSQL contains only credentials and binding state.
1. Create a Linear application
Open Create new application in Linear Settings → API.
Fill Application name and Developer name. In Redirect URIs, paste:
https://<public-origin>/api/v1/integrations/linear/callbackLinear supports several redirect URIs, one per line. Add local, staging, or preview callbacks individually when those environments must run OAuth. The scheme, host, path, and trailing slash must match the URI sent by ctx|.
ctx| requests the read scope with a user actor on the authorize URL. You
do not set those on this Linear form.
Linear cannot deliver webhooks to a private localhost address. End-to-end connector testing requires a public HTTPS deployment or tunnel.
2. Configure webhooks on the same form
Turn Webhooks on and set Webhook URL to:
https://<public-origin>/api/v1/webhook/linearCopy Webhook signing secret from that form. Linear fills it in before you
click Create. ctx| verifies the Linear-Signature HMAC against the raw request
body and rejects stale timestamps. Accepted in-scope events start a durable
update before the endpoint acknowledges delivery.
Under Data change events, tick Issues, Comments, Documents, Projects, Initiatives, and the related update and attachment boxes (including cycles, issue labels, users, and customer requests). Under Authorization events, tick OAuth authorization events.
Then click Create. Linear shows Client ID and Client secret only after that. Copy them for the next step.
One webhook URL per Linear application
A Linear application has one webhook destination. You can register several OAuth redirect URIs, but events from every workspace installation go to that one webhook URL. Use a separate Linear application when a staging or PR environment must receive its own incremental events. Per-organization apps each register the same Webhook URL and store their own signing secret on the connection.
3. Register the app in the product UI
- Open Connectors → Add connection → Linear.
- On Register Linear OAuth app, paste the client ID and client secret Linear shows after Create, plus the webhook signing secret from the create form. The wizard shows the exact Redirect URIs and Webhook URL values for this deployment.
- Save the app, then continue to Connect Linear workspace.
- Complete OAuth, choose a GitHub repository and scope, and merge
linear/config.yaml.
Secrets are stored encrypted on the Linear connections row. List and status
APIs never return them.
The backend and worker must share the encryption key
(AUTH_SECRET / CONNECTION_SECRETS_ENCRYPTION_KEY) so token refresh can use
the saved client secret.
4. Optional deployment-wide environment variables
LINEAR_* remains an optional operator shortcut for a deployment-wide Linear
application (the hosted fallback). When both LINEAR_CLIENT_ID and
LINEAR_CLIENT_SECRET are set, the wizard skips Register and starts at Connect.
| Variable | Required | Purpose |
|---|---|---|
LINEAR_CLIENT_ID | Optional; backend + worker if used | Shared OAuth application client ID |
LINEAR_CLIENT_SECRET | Optional; backend + worker if used | Shared OAuth code exchange and token refresh |
LINEAR_REDIRECT_URI | Backend; optional | Exact callback override; defaults to {AUTH_BASE_URL}/api/v1/integrations/linear/callback |
LINEAR_WEBHOOK_SECRET | Optional; backend if used | Shared signing secret for /api/v1/webhook/linear |
Empty values still parse as unset. Restart or redeploy both services after changing these variables.
Docker Compose
Add the variables to the root .env used by the deploy profile only if you
want a deployment-wide app, then run:
docker compose --profile deploy up -d --build backend workerRailway
Set OAuth variables on the production backend and worker, and the webhook
secret on the backend, only when using the deployment-wide fallback. The
repository's Terraform module currently passes provided LINEAR_* values to
both services. For the root Terraform stack, the corresponding inputs are
TF_VAR_linear_client_id, TF_VAR_linear_client_secret,
TF_VAR_linear_redirect_uri, and TF_VAR_linear_webhook_secret.
PR environments duplicate the production environment. Add each preview callback to the Linear application before testing OAuth. A shared production application still sends webhooks to production; use a dedicated preview application to test incremental delivery safely.
AWS CDK
Pass the four values through connectorSecrets when you want the
deployment-wide fallback:
new CtxPipe(stack, "CtxPipe", {
// other required settings
connectorSecrets: {
linearClientId: cdk.SecretValue.secretsManager("linear/client-id"),
linearClientSecret: cdk.SecretValue.secretsManager("linear/client-secret"),
linearWebhookSecret: cdk.SecretValue.secretsManager("linear/webhook-secret"),
// Optional when AUTH_BASE_URL already produces the registered callback:
linearRedirectUri: cdk.SecretValue.unsafePlainText(
"https://app.example.com/api/v1/integrations/linear/callback",
),
},
});The construct stores these values in the connector secret and injects them into backend and worker task definitions.
5. Verify the connector
- Confirm
GET /.statussucceeds on the public backend origin. - Confirm the worker is running and shares the backend encryption key.
- Open Connectors → Add connection → Linear.
- If
LINEAR_*is unset, complete Register Linear OAuth app, then Connect. - Complete OAuth and confirm the expected Linear workspace appears.
- Choose a GitHub repository and scope. New empty repositories are supported: ctx| creates the initial branch before opening the configuration pull request.
- Merge the generated
linear/config.yamlpull request. - Confirm the GitHub App delivers the merge push to
{AUTH_BASE_URL}/api/v1/webhook/github/<connectionId>. - Confirm the connector reaches Connected and generated files appear under
linear/. - Edit a mirrored Linear issue and confirm
POST /api/v1/webhook/linearsucceeds and the corresponding Git file changes. - Confirm repository ingestion completes.
Linear file uploads and explicit embedded external media are copied into
sibling assets/ directories in the context repository. Ordinary link-only
attachments stay links and are never crawled; GitHub pull requests and commits
remain references. Downloads are limited to 25 MiB per asset and 100 MiB per
Linear entity; unsafe, failed, or oversized assets leave a Linear link or
textual stub. Treat the destination repository as carrying the same sensitivity
as the selected Linear scope.
Initial sync proves OAuth, worker, and GitHub access. The webhook step separately proves Linear webhook delivery and signature configuration.
Troubleshooting
“Linear OAuth is not configured”
Neither the connection nor the deployment has a Linear client ID and secret.
Register the app in the wizard, or set LINEAR_CLIENT_ID and
LINEAR_CLIENT_SECRET on backend and worker if you want a deployment-wide app.
Linear reports a redirect URI mismatch
Compare the authorize request's redirect_uri with the application setting
character for character. Prefer leaving LINEAR_REDIRECT_URI unset so ctx|
derives it from the public AUTH_BASE_URL; use the override only behind a proxy
with a different external callback.
OAuth succeeds but sync later fails
The worker must be able to decrypt the saved client secret (same
AUTH_SECRET / CONNECTION_SECRETS_ENCRYPTION_KEY as the backend) or, when
using the env fallback, have LINEAR_CLIENT_ID and LINEAR_CLIENT_SECRET.
Check outbound HTTPS, worker logs, and whether the application authorization
was revoked in Linear.
Configuration pull request creation fails
Open the connector and choose Retry configuration pull request. If the retry fails, confirm that the worker is running and that the selected GitHub App installation still has write access to the destination repository. This retry does not alter scope already present on the configuration pull request branch. If the pull request was never created, the setup dialog resubmits its local selection when retrying.
Webhook endpoint returns 503
No webhook signing secret is present on the matching Linear connection or in
LINEAR_WEBHOOK_SECRET.
Webhook endpoint returns 401
The signing secret does not match the Linear application that sent the event, a proxy changed the raw body, or the event timestamp is stale. Do not parse and re-serialize the body in a gateway before forwarding it. A secret saved on one connection does not authorize events for another.
Initial sync works but later edits do not appear
This is a webhook problem, not an OAuth problem. Confirm that webhooks are
enabled on the application, the public URL is reachable, the relevant resource
categories are selected, and requests reach /api/v1/webhook/linear.
User guide
See Linear connector for scope, repository layout, review workflow, and user-facing recovery steps.