Source connectors

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, keep CTXPIPE_PUBLIC_APP_URL on 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.app over HTTPS.

Connector state and backups

Linear scope remains reviewable in Git:

  • Draft scope is linear/config.yaml on 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 connections row.
  • 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/callback

Linear 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/linear

Copy 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

  1. Open Connectors → Add connection → Linear.
  2. 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.
  3. Save the app, then continue to Connect Linear workspace.
  4. 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.

VariableRequiredPurpose
LINEAR_CLIENT_IDOptional; backend + worker if usedShared OAuth application client ID
LINEAR_CLIENT_SECRETOptional; backend + worker if usedShared OAuth code exchange and token refresh
LINEAR_REDIRECT_URIBackend; optionalExact callback override; defaults to {AUTH_BASE_URL}/api/v1/integrations/linear/callback
LINEAR_WEBHOOK_SECRETOptional; backend if usedShared 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 worker

Railway

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

  1. Confirm GET /.status succeeds on the public backend origin.
  2. Confirm the worker is running and shares the backend encryption key.
  3. Open Connectors → Add connection → Linear.
  4. If LINEAR_* is unset, complete Register Linear OAuth app, then Connect.
  5. Complete OAuth and confirm the expected Linear workspace appears.
  6. Choose a GitHub repository and scope. New empty repositories are supported: ctx| creates the initial branch before opening the configuration pull request.
  7. Merge the generated linear/config.yaml pull request.
  8. Confirm the GitHub App delivers the merge push to {AUTH_BASE_URL}/api/v1/webhook/github/<connectionId>.
  9. Confirm the connector reaches Connected and generated files appear under linear/.
  10. Edit a mirrored Linear issue and confirm POST /api/v1/webhook/linear succeeds and the corresponding Git file changes.
  11. 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.