Skip to content

feat(linear): add multi-workspace support - #30314

Open
smorin wants to merge 1 commit into
raycast:mainfrom
smorin:feat/linear-multi-workspace
Open

feat(linear): add multi-workspace support#30314
smorin wants to merge 1 commit into
raycast:mainfrom
smorin:feat/linear-multi-workspace

Conversation

@smorin

@smorin smorin commented Aug 18, 2026

Copy link
Copy Markdown

Description

Adds multi-workspace support to the Linear extension: connect any number of workspaces
(including the same workspace under two accounts), switch via a consistent pattern on
every surface (list dropdowns, a form field, a dedicated Create Issue for Myself in
Workspace quick command plus a one-shot workspace argument on Quick Add Comment), a
new Manage Workspaces command, an aggregated menu bar, and workspace-routable AI tools
with a new get-workspaces tool. The quick commands surface their target via a live
root-search subtitle (hidden for single-workspace users).

Single-workspace users see no behavior change: every switcher is invisible until a
second workspace is connected, and an existing login is adopted silently (no re-auth).
The only visible additions are the new Manage Workspaces and Create Issue for Myself
in Workspace commands and an optional workspace argument on Quick Add Comment.

Closes #24297. Supersedes the PAT-based approach in #25646 — this stays entirely on
Raycast's OAuth proxy and token storage.

Approach

  • One OAuth token-storage slot per (workspace, account) pair via per-entry providerIds
    (linear-ws-<orgId>-<userId>); the existing linear slot is adopted unchanged.
  • feat(oauth): allow overriding providerId and extraParameters in built-in OAuthService providers utils#80 adds providerId/extraParameters to OAuthService.linear(); until
    it ships in a release, a small factory (src/api/oauth.ts, AUTH_BACKEND switch)
    constructs the service manually with the same proxy + clientId — the copied constants
    are temporary by design and deleted when the utils option lands.
  • Adding a workspace uses prompt=consent (without it Linear silently skips consent for
    an already-granted app). Linear's consent page has no workspace picker — the grant
    follows the active workspace at linear.app, so the add flow instructs the user to
    switch there first (verified empirically).
  • Background contexts (menu bar) never trigger OAuth flows and use a non-destructive
    token refresh, so a transient network failure can never log a workspace out.
  • Removing a workspace deletes the local token only; Linear-side revocation is opt-in
    (revocation is authorization-scoped and eventually consistent, so an unconditional
    revoke could affect the primary login).
  • AI tools take a per-call workspaceId (each tool call runs in a fresh process, so
    per-call routing is the only reliable design); confirmations resolve the same client
    as executions and display the target workspace.

Testing notes & known limitations

  • The extension declares macOS and Windows support; this change was fully tested on
    macOS only (no Windows hardware available to the author) — flagging for reviewers.
  • Manually verified on macOS across the full design test matrix (two workspaces,
    including the same workspace under two accounts): silent adoption on upgrade, add /
    switch / re-authenticate / log out flows, per-workspace remembered form defaults,
    cross-workspace drafts (draft pins its workspace; the global default is untouched),
    menu-bar aggregation with per-workspace re-auth rows, and AI-tool workspace routing.
  • workspaceId on AI tools is optional by design (absent → active workspace). If the
    model never calls get-workspaces and the user switches the default between a
    confirmation and its execution, routing follows the default; mitigations are the AI
    instructions, a Workspace row on every mutating confirmation, and routing evals.
  • Workspace targeting on the quick commands is a text argument (Raycast argument
    dropdowns require a static list; workspaces are per-user runtime data). It matches
    URL key, account email, or a unique name prefix, and never changes the default
    workspace; Create Issue for Myself is deliberately pegged to the active workspace,
    with the dedicated in-Workspace command for explicit targeting.
  • Pre-existing and deliberately untouched: create-project-update.ts and
    get-initiatives.ts exist undeclared in tools[]; team switch clears the Create
    Issue title via the template-reset effect (upstream behavior, kept for parity).
  • Raycast Settings' extension login-row list does not live-refresh (platform behavior);
    Manage Workspaces is the primary management surface and labels each login row
    (providerName: "Linear — <org>").
  • One narrow known issue kept small on purpose: permanent-vs-transient classification of
    a failed staged-add verification matches on error text ("HTTP 401/403") — an SDK-path
    auth failure in a rare race is treated as transient (retried) rather than permanent.

Screencast

<attach the Manage Workspaces add/switch screencast>

Adds multi-workspace support to the Linear extension. See PR description for the full feature list and design notes. Builds on raycast/utils#80 via a temporary manual OAuth service construction behind a code-level switch.
Copilot AI lite review requested due to automatic review settings August 18, 2026 05:12
@raycastbot raycastbot added extension fix / improvement Label for PRs with extension's fix improvements extension: linear Issues related to the linear extension AI Extension platform: macOS platform: Windows labels Aug 18, 2026
@raycastbot

Copy link
Copy Markdown
Collaborator

Thank you for your first contribution! 🎉

🔔 @thomaslombart @thomaspaulmann @peduarte @pernielsentikaer @clemb8 @jfkisafk @filipszczepanski @JoeBoulton @itsmingjie @xmok @0xdhrv @erikjs14 @alexibuild you might want to have a look.

You can use this guide to learn how to check out the Pull Request locally in order to test it.

📋 Quick checkout commands
BRANCH="feat/linear-multi-workspace"
FORK_URL="https://github.com/smorin/extensions.git"
EXTENSION_NAME="linear"
REPO_NAME="extensions"

git clone -n --depth=1 --filter=tree:0 -b $BRANCH $FORK_URL
cd $REPO_NAME
git sparse-checkout set --no-cone "extensions/$EXTENSION_NAME"
git checkout
cd "extensions/$EXTENSION_NAME"
npm install && npm run dev

We're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 15 business days.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds multi-workspace support for the Linear Raycast extension, enabling multiple concurrent OAuth sessions (including same workspace across different accounts) with consistent workspace switching across UI surfaces, menu bar aggregation, and AI tool routing.

Changes:

  • Introduces a workspace registry + per-(workspace, account) OAuth providerId strategy, plus a new Manage Workspaces command.
  • Adds workspace switchers (list dropdown + form field), per-workspace persisted UI defaults, and menu bar unread notifications aggregated across workspaces.
  • Routes AI tools per call via workspaceId, adds get-workspaces, and updates tool auth to be non-interactive.

Reviewed changes

Copilot reviewed 99 out of 99 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
extensions/linear/src/unread-notifications.tsx Aggregates unread notifications across all connected workspaces in the menu bar.
extensions/linear/src/tools/update-project-milestone.ts Adds optional workspaceId routing + non-interactive tool auth.
extensions/linear/src/tools/update-issue.ts Adds optional workspaceId routing + workspace-aware confirmations.
extensions/linear/src/tools/update-document.ts Adds optional workspaceId routing + workspace-aware confirmations.
extensions/linear/src/tools/update-comment.ts Adds optional workspaceId routing + workspace-aware confirmations and attachment uploads.
extensions/linear/src/tools/resolveToolWorkspace.ts Implements per-call workspace routing for AI tools and non-interactive auth wrapper.
extensions/linear/src/tools/remove-label.ts Adds optional workspaceId routing + workspace-aware confirmations.
extensions/linear/src/tools/get-workspaces.ts New tool to list connected workspaces without triggering OAuth.
extensions/linear/src/tools/get-teams.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/get-projects.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/get-project-updates.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/get-project-statuses.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/get-notifications.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/get-members.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/get-labels.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/get-issue-states.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/get-initiatives.ts Adds optional workspaceId routing and exposes API helper with client override.
extensions/linear/src/tools/get-documents.ts Adds optional workspaceId routing and exposes API helper with client override.
extensions/linear/src/tools/get-document-content.ts Adds optional workspaceId routing and exposes API helper with client override.
extensions/linear/src/tools/get-current-user.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/full-text-search-issues.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/formatConfirmation.ts Makes confirmations workspace-aware via explicit client parameter.
extensions/linear/src/tools/filter-issues.ts Adds optional workspaceId routing for AI tools.
extensions/linear/src/tools/create-project-update.ts Adds optional workspaceId routing + workspace-aware confirmations.
extensions/linear/src/tools/create-issue.ts Adds optional workspaceId routing + workspace-aware confirmations.
extensions/linear/src/tools/create-document.ts Adds optional workspaceId routing + workspace-aware confirmations.
extensions/linear/src/tools/create-comment.ts Adds optional workspaceId routing + workspace-aware confirmations and attachment uploads.
extensions/linear/src/tools/add-label.ts Adds optional workspaceId routing + workspace-aware confirmations.
extensions/linear/src/search-issues.tsx Adds workspace dropdown accessory to list search UI.
extensions/linear/src/search-custom-views.tsx Adds workspace dropdown accessory to custom view search UI.
extensions/linear/src/quick-add-comment-to-issue.ts Adds one-shot workspace argument resolution + subtitle refresh mechanism.
extensions/linear/src/notifications.tsx Adds workspace dropdown accessory and makes menu bar refresh best-effort.
extensions/linear/src/manage-workspaces.tsx New UI to add/switch/re-auth/logout workspaces with staging-token recovery.
extensions/linear/src/hooks/useWorkspaceCachedState.ts New helper to scope persisted UI state by workspace key.
extensions/linear/src/hooks/useUsers.ts Scopes cached users queries by workspace.
extensions/linear/src/hooks/useTeams.ts Scopes cached teams queries by workspace.
extensions/linear/src/hooks/useStates.ts Scopes cached workflow states queries by workspace.
extensions/linear/src/hooks/useSearchIssues.ts Scopes cached issue search by workspace.
extensions/linear/src/hooks/useProjects.ts Scopes cached projects pagination by workspace.
extensions/linear/src/hooks/useProjectUpdates.ts Scopes cached project updates by workspace.
extensions/linear/src/hooks/useProjectStatuses.ts Scopes cached project statuses by workspace.
extensions/linear/src/hooks/usePriorities.ts Scopes cached priority values by workspace.
extensions/linear/src/hooks/useNotifications.ts Scopes cached notifications by workspace.
extensions/linear/src/hooks/useMilestones.ts Scopes cached milestones by workspace.
extensions/linear/src/hooks/useMe.ts Scopes cached viewer by workspace.
extensions/linear/src/hooks/useLabels.ts Scopes cached labels by workspace.
extensions/linear/src/hooks/useIssues.ts Scopes cached issue lists by workspace.
extensions/linear/src/hooks/useIssueTemplates.ts Scopes cached issue templates by workspace.
extensions/linear/src/hooks/useIssueDetail.ts Scopes cached issue detail by workspace.
extensions/linear/src/hooks/useIssueComments.ts Scopes cached issue comments by workspace.
extensions/linear/src/hooks/useInitiatives.ts Scopes cached initiatives by workspace.
extensions/linear/src/hooks/useDocuments.ts Scopes cached documents + content by workspace.
extensions/linear/src/hooks/useCycles.ts Scopes cached cycles queries by workspace.
extensions/linear/src/hooks/useCustomViews.ts Scopes cached custom views + issues by workspace.
extensions/linear/src/hooks/useAllWorkspaceNotifications.ts New hook to fetch notifications per workspace for menu bar aggregation.
extensions/linear/src/helpers/workspaceArgument.ts Implements workspace argument matching + command subtitle updates.
extensions/linear/src/helpers/refreshQuickSubtitles.ts Adds best-effort background launches to refresh quick-command subtitles.
extensions/linear/src/helpers/createIssueForMyself.ts Extracts “create issue for myself” into a reusable helper supporting client overrides.
extensions/linear/src/favorites.tsx Adds workspace dropdown and scopes favorites caching by workspace.
extensions/linear/src/created-issues.tsx Adds workspace dropdown accessory.
extensions/linear/src/create-project.tsx Adds draft-time workspace pinning and loading gating for project creation flow.
extensions/linear/src/create-issue.tsx Adds draft-time workspace pinning and loading gating for issue creation flow.
extensions/linear/src/create-issue-for-myself.ts Adds workspace subtitle refresh + workspace-aware auth wrapper.
extensions/linear/src/create-issue-for-myself-in-workspace.ts New quick command to create an issue explicitly in a named workspace.
extensions/linear/src/components/docs/DocumentList.tsx Adds workspace switch section to entity dropdown + per-workspace persisted entity selection.
extensions/linear/src/components/WorkspaceDropdown.tsx New list/form workspace dropdown components + embedded section for existing dropdowns.
extensions/linear/src/components/WorkspaceContext.tsx New context/provider handling workspace switching and remount boundary.
extensions/linear/src/components/View.tsx Wraps commands in WorkspaceProvider and updates auth handling for multi-workspace.
extensions/linear/src/components/ProjectList.tsx Adds workspace switch section to initiative dropdown + per-workspace persisted initiative.
extensions/linear/src/components/ProjectIssues.tsx Scopes milestone filter persistence by workspace.
extensions/linear/src/components/EditIssueForm.tsx Removes storeValue from milestone dropdown (workspace-scoped persistence now handled elsewhere).
extensions/linear/src/components/CreateProjectForm.tsx Adds workspace form field + per-workspace persisted defaults (status/lead).
extensions/linear/src/components/CreateMilestoneForm.tsx Adds per-workspace persisted project selection and removes storeValue.
extensions/linear/src/components/CreateIssueForm.tsx Adds workspace form field + per-workspace persisted defaults with readiness semantics.
extensions/linear/src/assigned-issues.tsx Embeds workspace switch section into view dropdown.
extensions/linear/src/api/workspaces.ts New workspace registry (LocalStorage) with migration/adoption of existing single-login slot.
extensions/linear/src/api/withWorkspaceAuth.ts New wrapper reusing withAccessToken but bootstrapping workspace auth.
extensions/linear/src/api/updateNotification.ts Adds optional client override for workspace routing.
extensions/linear/src/api/updateIssue.ts Adds optional client override for workspace routing.
extensions/linear/src/api/oauth.ts Adds providerId-aware OAuth services + staging token flow + non-destructive refresh.
extensions/linear/src/api/linearClient.ts Reworks client bootstrap/caching for multi-workspace + active workspace snapshot.
extensions/linear/src/api/initiatives.ts Adds optional client override for workspace routing.
extensions/linear/src/api/getTeams.ts Adds optional client override for workspace routing.
extensions/linear/src/api/getProjects.ts Adds optional client override for workspace routing.
extensions/linear/src/api/getNotifications.ts Adds optional client override for workspace routing.
extensions/linear/src/api/getMilestones.ts Adds optional client override for workspace routing.
extensions/linear/src/api/getLabels.ts Adds optional client override for workspace routing.
extensions/linear/src/api/getIssues.ts Adds optional client override for workspace routing for all issue queries.
extensions/linear/src/api/getIssueTemplates.ts Adds optional client override for workspace routing.
extensions/linear/src/api/getCustomViews.ts Adds optional client override for workspace routing.
extensions/linear/src/api/favorites.ts Adds optional client override for workspace routing.
extensions/linear/src/api/documents.ts Adds optional client override for workspace routing.
extensions/linear/src/api/deleteNotification.ts Adds optional client override for workspace routing.
extensions/linear/src/api/createIssue.ts Adds optional client override for workspace routing (issue + sub-issue).
extensions/linear/src/api/attachments.ts Adds optional client override to keep uploads routed to correct workspace.
extensions/linear/src/active-cycle.tsx Adds embedded workspace switching + per-workspace persisted selected team.
extensions/linear/package.json Adds new commands/tools and updates AI instructions + evals for workspace routing.
extensions/linear/README.md Documents multi-workspace usage and new management/switching surfaces.
extensions/linear/CHANGELOG.md Adds changelog entry describing multi-workspace support.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +65 to +87
export async function refreshNonDestructive(
service: OAuthService,
): Promise<{ status: "ok"; accessToken: string } | { status: "failed" }> {
const tokens = await service.client.getTokens();
if (!tokens?.refreshToken) return { status: "failed" };
try {
const response = await fetch(`${LINEAR_PROXY}/refresh-token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
client_id: LINEAR_PROXY_CLIENT_ID,
refresh_token: tokens.refreshToken,
grant_type: "refresh_token",
}),
});
if (!response.ok) return { status: "failed" };
const next = (await response.json()) as { access_token: string; refresh_token?: string; expires_in?: number };
await service.client.setTokens(next);
return { status: "ok", accessToken: next.access_token };
} catch {
return { status: "failed" };
}
}
}
}

if (!teamId) {
Comment on lines +100 to +110
{rows.map((row) => (
<MenuBarExtra.Section
key={entryKey(row.entry)}
{...(multi
? {
title:
row.entry.orgName +
(rows.filter((r) => r.entry.orgId === row.entry.orgId).length > 1 ? ` (${row.entry.userEmail})` : ""),
}
: {})}
>
Comment on lines +263 to +279
async function logOutAll() {
const confirmed = await confirmAlert({
title: "Log Out of All Workspaces?",
message:
"Removes every workspace login from Raycast (tokens are deleted locally; each access token expires at Linear on its own within 24 hours).",
primaryAction: { title: "Log Out of All", style: Alert.ActionStyle.Destructive },
});
if (!confirmed) return;
for (const row of rows) {
const service = getServiceForProviderId(row.entry.providerId, undefined, `Linear — ${row.entry.orgName}`);
await service.client.removeTokens();
await removeWorkspaceEntry({ orgId: row.entry.orgId, userId: row.entry.userId });
}
await showToast({ style: Toast.Style.Success, title: "Logged out of all workspaces" });
refreshQuickCommandSubtitles(); // fire-and-forget: workspace membership changed, subtitles listing "others" are now stale
await reload();
}
@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds multi-workspace OAuth registration and routing throughout the Linear extension.

  • Introduces workspace management, switching, migration, and per-account OAuth clients.
  • Routes commands, forms, quick actions, and AI tools to selected workspaces.
  • Aggregates menu-bar notifications across all connected workspaces.
  • Adds workspace-aware command metadata, documentation, and release notes.

Confidence Score: 3/5

The PR should not merge until complete multi-workspace notification payloads are kept out of the unencrypted cache or stored using an appropriately protected mechanism.

The new menu-bar hook serializes private notification content from every connected Linear account into plaintext cache storage and leaves no removal-time invalidation path.

Files Needing Attention: extensions/linear/src/hooks/useAllWorkspaceNotifications.ts and extensions/linear/src/manage-workspaces.tsx

Security Review

The new menu-bar aggregation persists complete notification payloads from every connected Linear account through a cache explicitly documented as unencrypted. This expands plaintext-at-rest exposure to private issue and comment content from additional workspaces and does not clear that data when a workspace is removed.

Important Files Changed

Filename Overview
extensions/linear/src/hooks/useAllWorkspaceNotifications.ts Adds all-workspace notification aggregation but caches complete OAuth-protected notification payloads in storage documented as unencrypted.
extensions/linear/src/api/linearClient.ts Introduces provider-specific client creation, token migration, activation, and caching for workspace/account pairs.
extensions/linear/src/api/workspaces.ts Adds the persistent workspace registry, legacy-login migration, entry identity, and active-workspace management.
extensions/linear/src/components/WorkspaceContext.tsx Provides workspace state and remounts command content after switching to prevent stale workspace-bound UI state.
extensions/linear/src/tools/resolveToolWorkspace.ts Resolves explicit per-call AI workspace identifiers and consistently supplies matching clients to checked confirmations and executions.
extensions/linear/src/manage-workspaces.tsx Adds workspace add, switch, reauthentication, and logout workflows, but logout does not clear cached notification data.
extensions/linear/package.json Declares the new workspace commands and AI tool while extending existing quick-command and AI routing interfaces.
extensions/linear/CHANGELOG.md Adds a correctly positioned multi-workspace release entry using the required merge-date placeholder.
Prompt To Fix All With AI
### Issue 1
extensions/linear/src/hooks/useAllWorkspaceNotifications.ts:23
**Notification payloads enter plaintext cache**

When users connect additional workspaces, `useCachedPromise` serializes each workspace's complete notification rows into the unencrypted cache, exposing private issue details, comment bodies, organization metadata, and account emails to a local cache reader even after the corresponding workspace is removed. **How this was verified:** The hook returns full `NotificationResult` arrays through a cache explicitly documented here as unencrypted, while the workspace-removal flow contains no cache invalidation or redaction.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "feat(linear): add multi-workspace suppor..." | Re-trigger Greptile

// background-safe token access only (never authorize(), §4.3). One row per ENTRY —
// two entries can share an orgId (D10), so grouping is by entryKey.
export default function useAllWorkspaceNotifications() {
const { data, isLoading, mutate } = useCachedPromise(async (): Promise<WorkspaceNotificationRow[]> => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Notification payloads enter plaintext cache

When users connect additional workspaces, useCachedPromise serializes each workspace's complete notification rows into the unencrypted cache, exposing private issue details, comment bodies, organization metadata, and account emails to a local cache reader even after the corresponding workspace is removed. How this was verified: The hook returns full NotificationResult arrays through a cache explicitly documented here as unencrypted, while the workspace-removal flow contains no cache invalidation or redaction.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/linear/src/hooks/useAllWorkspaceNotifications.ts
Line: 23

Comment:
**Notification payloads enter plaintext cache**

When users connect additional workspaces, `useCachedPromise` serializes each workspace's complete notification rows into the unencrypted cache, exposing private issue details, comment bodies, organization metadata, and account emails to a local cache reader even after the corresponding workspace is removed. **How this was verified:** The hook returns full `NotificationResult` arrays through a cache explicitly documented here as unencrypted, while the workspace-removal flow contains no cache invalidation or redaction.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@0xdhrv 0xdhrv self-assigned this Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Extension extension fix / improvement Label for PRs with extension's fix improvements extension: linear Issues related to the linear extension platform: macOS platform: Windows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linear Extension: Unable to switch between organizations

4 participants