windmill-labs/windmill · error

Variable at path ${path} already exists. Delete it or pick a

Error message

Variable at path ${path} already exists. Delete it or pick another path.

What it means

The MCP secret-variable store keeps a variable at `path`; if it exists with a different description than the one being saved, the code cannot tell whether it is the same connection's variable and throws rather than overwriting unrelated data. Same description means it is the same connection and the save proceeds (update/rotate).

Source

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

	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 })
	}

	await VariableService.createVariable({
		workspace,
		requestBody: { path, value, is_secret: true, is_oauth: isOauth, account, description }
	})

View on GitHub (pinned to e474e8803c)

Solutions

  1. Choose a new variable `path` for this connection.
  2. Or delete the existing variable (and its connection) if it is obsolete, then save.
  3. If it IS the same connection, ensure the description matches the existing one so the upsert path continues.
  4. List variables in the workspace to identify the occupier before saving.

Example fix

// before
await upsertSecretVariable({ workspace, path: 'mcp_secret', description: 'conn A', ... })
// after
await upsertSecretVariable({ workspace, path: 'mcp_secret_conn_a', description: 'conn A', ... })
Defensive patterns

Strategy: validation

Validate before calling

const exists = await VariableService.existsVariable({ workspace, path })
if (exists) {
  const cur = await VariableService.getVariable({ workspace, path, decryptSecret: false })
  if (cur.description !== description) alert('Variable path belongs to another connection')
}

Try / catch

try { await upsertSecretVariable(args) } catch (e) { if (String(e.message).includes('Variable at path')) promptNewVariablePath(); else throw e }

Prevention

When it happens

Trigger: Saving a connection whose variable path collides with an existing variable that has a different description — i.e. the path belongs to a different connection.

Common situations: Renaming a connection without changing its description; reusing a variable path chosen earlier for another integration; imported config pointing at an existing variable path.

Related errors


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