windmill-labs/windmill · error

A connection already exists at ${resourcePath}. Pick another

Error message

A connection already exists at ${resourcePath}. Pick another path.

What it means

upsertSecretVariable backs each MCP connection with a resource at resourcePath. Before writing, it checks ResourceService.existsResource and throws if a connection already occupies that path, to avoid rotating the token of the existing connection and stranding it with a credential meant for the new one.

Source

Thrown at frontend/src/lib/components/mcp/secretVariable.ts:46

 */
export async function upsertSecretVariable(args: {
	workspace: string
	path: string
	value: string
	/** The MCP resource this token belongs to; stamped into the description. */
	resourcePath: string
	isOauth?: boolean
	account?: number
}): Promise<void> {
	const { workspace, path, value, resourcePath, isOauth, account } = args
	const description = mcpTokenDescription(resourcePath)

	// The path picker rejects an occupied path, but it validates on a debounce and
	// this runs on the click: without the check, a fast save would rotate the token
	// of the connection already living there and only then fail to create its
	// resource, leaving that server holding a credential meant for another one.
	if (await ResourceService.existsResource({ workspace, path: resourcePath })) {
		throw new Error(`A connection already exists at ${resourcePath}. Pick another path.`)
	}

	if (await VariableService.existsVariable({ workspace, path })) {
		const current = await VariableService.getVariable({ workspace, path, decryptSecret: false })
		if (current.description !== description) {
			throw new Error(`Variable at path ${path} already exists. Delete it or pick another path.`)
		}
		if (!isOauth) {
			// `is_secret` is inherited from the row when omitted, so it is restated
			// rather than assumed.
			await VariableService.updateVariable({
				workspace,
				path,
				requestBody: { value, is_secret: true }
			})
			return
		}
		await VariableService.deleteVariable({ workspace, path })

View on GitHub (pinned to e474e8803c)

Solutions

  1. Pick a different resourcePath for the new connection and save again.
  2. Delete or rename the existing connection at that path first if it is no longer needed.
  3. Re-open the save dialog so the path picker re-validates with fresh data.
  4. If it is a stale collision, refresh the resource list — the occupier may have been deleted.

Example fix

// before
await upsertSecretVariable({ workspace, path: 'conn', resourcePath: 'mcp_conn' })
// after
await upsertSecretVariable({ workspace, path: 'conn2', resourcePath: 'mcp_conn2' })
Defensive patterns

Strategy: validation

Validate before calling

if (await ResourceService.existsResource({ workspace, path: resourcePath })) alert('Path taken — pick another')

Type guard

function resourcePathIsFree(r) { return r === false } // existsResource returns boolean

Try / catch

try { await upsertSecretVariable(args) } catch (e) { if (String(e.message).includes('already exists at')) promptNewPath(); else throw e }

Prevention

When it happens

Trigger: Saving an MCP connection (via OAuth finish, manual save, or resource creation) with a resourcePath that already has a resource — the debounced path-picker validation missed the collision between validation and click.

Common situations: Two connections configured in quick succession with the same path; renaming a connection to a path taken by another; race between two tabs saving simultaneously.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/61cf73412fec548c. Report an issue: GitHub.