fix(integrations): Linear and GitHub sync need Pro; Linear plugin gets its own connection
What & why
Two support reports about Linear "not connecting". They had different causes, and both left the user without an explanation.
Free orgs looped on reconnect. Since #7947, the hourly suspend cron revokes Linear and GitHub for orgs outside the sync policy. It has revoked 1,334 Linear connections so far. Connecting was still open, though. A free org could connect, get revoked at the next hour, and then see "Linear authorization expired. Reconnect to resume syncing." Since the cron started, 5 of the 8 new Linear connections were revoked within an hour.
linear/connectandgithub/installsend an org thatorganizationSyncsNowrejects to/settings/billing, so it never reaches Linear or GitHub. This is the same predicate the cron uses, so the two can't disagree.- New
integration.syncAllowedquery. Its callers are in #8048, which merges after this is live: the web pages redirect free orgs to billing, desktop's Settings → Integrations opens the Pro dialog, and the CLI checks before connecting.
A paid org's Linear workspace was already linked to a coworker's separate org. The callback returned workspace_already_linked, but the Linear page had no message for it.
- The Linear callback now passes
ownerand the page says "…already connected by {owner}. Ask them to disconnect first.", as Slack does.
Two older bugs found while checking the above:
- No integration callback toast has ever shown.
IntegrationErrorHandlerpublishes in the page's effect. That runs before the root layout's<Toaster>subscribes, and sonner 2.0.7 drops anything published before then. The toast is now deferred one task (not cancelled on cleanup) and has a stable id so StrictMode can't show it twice. - The
{owner}/{detail}messages on Slack and Teams rendered the name blank, because Lingui filled the placeholder with nothing on the server. The placeholder is now passed through as its own value.
The Linear plugin gets its own personal connection. The plugin and Tasks sync shared one per-person linear row, which uses our Linear OAuth app. Grants from that app turn on webhook deliveries. So #7947's revoke broke the plugin for free orgs, reconnecting through the plugin turned webhooks back on until the next revoke, and uninstalling the plugin disconnected sync.
- New
linear_mcpconnector: a dynamic client againstmcp.linear.app, likenotion_mcp/sentry_mcp. It uses the client-metadata document on https and registration on localhost. It also offers a Personal API key option, since Linear's MCP server accepts keys as bearer tokens. Identity comes from the server'sget_user("me"). These tokens belong to Linear's MCP app, so no webhooks follow and the cron never touches them. The plugin stays free. plugins/linear1.5.3 points atlinear_mcp. Existing installs follow throughinstallConnector, and each person signs in once.- The suspend cron revokes only OAuth
linearrows. An API key starts no webhooks, so revoking it only deleted the person's key. - After merge: tag
linear@1.5.3(git tag linear@1.5.3 <merge sha> && git push --tags).
Not in this PR: the pricing page still lists "GitHub integration" under Free. Whether it moves to Pro is an open decision.
How I tested it
Local API and web on this workspace's Neon branch, signed in as the seeded dev admin, with temporary subscriptions and connections rows (deleted afterwards).
-
Free org: both connect URLs return 302 to
/settings/billing. -
Pro org:
linear/connectreturns 302 tolinear.app/oauth/authorize, andgithub/installreturns 302 to the dev GitHub App install page. Unchanged. -
Toasts in headless Chrome: the Linear conflict toast names the owner, the Slack and Teams owner toasts include the email, and
oauth_deniednow shows. One toast each. -
bun testin apps/api integrations (57 pass), typecheck for api/web/trpc/cli, biome,check:i18n. -
Plugin, end to end against the real
mcp.linear.appwith a local API:/api/connectors/linear_mcp/connectregistered a client, then Linear consent and workspace picker (Superset). The callback saved alinear_mcprow keyed on the Linear user withread/writeand a refresh token. Thentools/call get_workspacethrough/mcp/plugins/superset/linearreturned the Superset workspace. After that I disconnected, uninstalled and revoked the key. -
check:plugins, 174 api integration tests, 84 connector/plugin tests.
Not tested: the Personal API key option on linear_mcp (no key available; Linear's docs say the MCP server accepts keys), the production client-metadata-document path (localhost always registers), a real Linear OAuth round-trip that hits the conflict (the page was checked by loading the callback's redirect URL directly), the CLI change beyond typecheck, and non-English rendering of the new strings.
Checklist
- PR title follows conventional commits (
type(scope): subject) -
bun run lintandbun run typecheckpass (CI fails on lint warnings too) - "Allow edits from maintainers" is checked on fork PRs
Summary by cubic
Linear and GitHub sync now require Pro: on free orgs, the connect routes redirect to billing instead of allowing a connection the hourly suspend cron would revoke. The Linear conflict toast now names the workspace owner, and callback toasts show correctly after being deferred past the root layout's <Toaster> subscription.
The Linear plugin and Tasks sync no longer share one linear connection row. The plugin uses a new linear_mcp connector on mcp.linear.app (OAuth or personal API key) that starts no webhooks, so the suspend cron now revokes only OAuth linear rows, and existing installs pick up the connector automatically. A new integration.syncAllowed query lets clients check the sync policy before connecting.
Summary by CodeRabbit
- New Features
- Added support for connecting to Linear through its MCP endpoint using OAuth or a personal API key.
- Linear connection-conflict messages now identify the existing owner or organization, with translations in supported languages.
- Bug Fixes
- GitHub and Linear connections now redirect to billing settings when synchronization isn’t enabled.
- Linear workspace conflicts now return users to integration settings with a clear message.
- Linear suspension checks no longer target active connections that don’t use OAuth.
- Chores
- Updated the Linear plugin version.