From 7008f8961d6992166e7818b74aa1bed5f28ef2c9 Mon Sep 17 00:00:00 2001 From: Faur Ioan-Aurel Date: Wed, 24 Jun 2026 00:12:26 +0300 Subject: [PATCH] fix: IDE launch fails when opening a URI targeting a workspace owned by a different user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a Coder URI pointed to a workspace owned by a user other than the one currently authenticated, the IDE would fail to launch with a misleading error claiming no matching IDE version was available. The real problem was that the workspace poller only fetched the authenticated user's own workspaces, so the target workspace never made it into the plugin's environment registry. When the IDE launch tried to inspect that workspace, Toolbox reported it as non-existent, which cascaded into empty IDE lists and the confusing version mismatch error. The fix intercepts URI handling — for both the same-deployment and new-deployment flows, and regardless of whether token, mTLS, or OAuth2 authentication is in use — and scopes the workspace filter to owner: name: before the protocol handler runs. This ensures the poller fetches exactly the target workspace and registers it with Toolbox in time for the IDE launch to succeed. When the URI targets the authenticated user's own workspace, the filter is reset to the default owner:me instead, which also cleans up any leftover non-owned filter from a previous URI. - resolves DEVEX-372 - resolves DEVEX-374 --- .../com/coder/toolbox/CoderRemoteProvider.kt | 30 +++++++++++++++---- .../toolbox/util/WorkspaceFilterQuery.kt | 5 ++-- .../coder/toolbox/views/NewEnvironmentPage.kt | 11 +++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt index 7336725..fe1ee4f 100644 --- a/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt +++ b/src/main/kotlin/com/coder/toolbox/CoderRemoteProvider.kt @@ -14,12 +14,14 @@ import com.coder.toolbox.util.DialogUi import com.coder.toolbox.util.TOKEN import com.coder.toolbox.util.URL import com.coder.toolbox.util.WebUrlValidationResult.Invalid +import com.coder.toolbox.util.owner import com.coder.toolbox.util.toQueryParameters import com.coder.toolbox.util.toURL import com.coder.toolbox.util.token import com.coder.toolbox.util.url import com.coder.toolbox.util.validateStrictWebUrl import com.coder.toolbox.util.withPath +import com.coder.toolbox.util.workspace import com.coder.toolbox.views.Action import com.coder.toolbox.views.CoderDelimiter import com.coder.toolbox.views.CoderSettingsPage @@ -385,7 +387,7 @@ class CoderRemoteProvider( } else { this.client!! to this.cli!! } - linkHandler.handle(params, newUrl, activeSession.first, activeSession.second) + handleLink(params, newUrl, activeSession.first, activeSession.second) } finally { coderHeaderPage.isBusy.update { false } } @@ -662,10 +664,28 @@ class CoderRemoteProvider( } /** - * Returns a [SuspendBiConsumer] that handles the given link parameters. - * Runs in a background coroutine so it doesn't block the connect step's - * post-connection flow. + * Applies the appropriate workspace filter for the URI then delegates to [linkHandler]. + * Scopes the filter to the target workspace when it is owned by a different user so the poll + * fetches and registers it before the IDE launch runs. For own workspaces, resets to the + * default filter, clearing any leftover non-owned filter from a previous URI. */ + private suspend fun handleLink( + params: Map, + url: URL, + client: CoderRestClient, + cli: CoderCLIManager, + ) { + val uriOwner = params.owner() + val uriWorkspace = params.workspace() + if (!uriOwner.isNullOrBlank() && !uriWorkspace.isNullOrBlank() && uriOwner != client.me.username) { + coderHeaderPage.setFilter("owner:$uriOwner name:$uriWorkspace") + } else { + coderHeaderPage.resetFilter() + } + linkHandler.handle(params, url, client, cli) + } + + /** Returns a [SuspendBiConsumer] that handles the given link parameters in a background coroutine. */ private fun deferredLinkHandler( params: Map, deploymentUrl: URL, @@ -673,7 +693,7 @@ class CoderRemoteProvider( context.cs.launch(CoroutineName("Deferred Link Handler")) { coderHeaderPage.isBusy.update { true } try { - linkHandler.handle(params, deploymentUrl, client, cli) + handleLink(params, deploymentUrl, client, cli) } catch (ex: Exception) { context.logAndShowError( "Error handling deferred link", diff --git a/src/main/kotlin/com/coder/toolbox/util/WorkspaceFilterQuery.kt b/src/main/kotlin/com/coder/toolbox/util/WorkspaceFilterQuery.kt index 75d793f..239c88f 100644 --- a/src/main/kotlin/com/coder/toolbox/util/WorkspaceFilterQuery.kt +++ b/src/main/kotlin/com/coder/toolbox/util/WorkspaceFilterQuery.kt @@ -74,5 +74,6 @@ val WORKSPACE_FILTER_PRESETS = listOf( ) /** The preset name whose query exactly matches this query, or [CUSTOM_WORKSPACE_FILTER_NAME] if none. */ -fun String.presetNameForQuery(): String = - WORKSPACE_FILTER_PRESETS.firstOrNull { it.query == trim() }?.name ?: CUSTOM_WORKSPACE_FILTER_NAME +fun String.presetNameForQuery(): String { + return WORKSPACE_FILTER_PRESETS.firstOrNull { it.query == this.trim() }?.name ?: CUSTOM_WORKSPACE_FILTER_NAME +} diff --git a/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt b/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt index 41ede64..6877d5e 100644 --- a/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt +++ b/src/main/kotlin/com/coder/toolbox/views/NewEnvironmentPage.kt @@ -177,6 +177,17 @@ class NewEnvironmentPage( reportFilterError(null) } + /** + * Sets the filter to an arbitrary query, e.g. when a URI targets a workspace owned by a + * different user. Updates both the visible search field and the backing query so the poll + * immediately uses the new value. + */ + fun setFilter(query: String) { + workspaceSearchField.contentState.value = query + mutableWorkspaceSearchQuery.value = query + reportFilterError(null) + } + /** * Shows the server's validation [message] under the search bar, or clears it when [message] is * null. Called by the provider when a workspace query is rejected or succeeds.