Workers SDK Issue Reports

← Back to Dashboard

#6256 `wrangler d1` commands shouldn't read wrangler.toml if executing remotely

Recommendation:KEEP OPEN
Difficulty:Bug confirmed: getDatabaseByNameOrBinding() trusts config database_id without API validation for remote commands
Reasoning:

Implement API validation for remote D1 commands

Suggested Action:

Yes

Analysis Report

Issue Review: cloudflare/workers-sdk#6256

Summary

D1 commands executing remotely should query the D1 API for the actual database UUID by name rather than blindly trusting the database_id in wrangler.toml, which can become stale if the database is deleted and recreated.

Findings

  • Created: 2024-07-12
  • Updated: 2025-10-30
  • Version: "latest" (at time of report) -> 4.60.0 (current)
  • Component: D1 (wrangler)
  • Labels: bug, d1
  • Comments: 0

Key Evidence

  1. No fix found in changelog or merged PRs - Searched for issue #6256, no references found
  2. Related issue #3438 still open - The issue references #3438 (D1 - deleting and re-creating a DB conflicts) which is also still open
  3. Code review confirms the bug exists - In packages/wrangler/src/d1/utils.ts, the getDatabaseByNameOrBinding() function prioritizes config over API:
export const getDatabaseByNameOrBinding = async (
	config: Config,
	accountId: string,
	name: string
): Promise<Database> => {
	const dbFromConfig = getDatabaseInfoFromConfig(config, name);
	if (dbFromConfig) {
		return dbFromConfig;  // <-- Returns stale config data without API verification
	}

	const allDBs = await listDatabases(config, accountId);
	const matchingDB = allDBs.find((db) => db.name === name);
	// ...
};
  1. All remote D1 commands are affected - execute --remote, info, delete, migrations apply --remote, etc. all use getDatabaseByNameOrBinding() which trusts the config file

Recommendation

Status: KEEP OPEN

Reasoning: The bug is confirmed to still exist in the codebase. When executing remote commands, wrangler trusts the database_id from wrangler.toml without validating it against the actual D1 API. This causes silent failures when a database is deleted and recreated with the same name but a new UUID.

Action: Implement API validation for remote commands.


Root Cause Analysis

The root cause is in packages/wrangler/src/d1/utils.ts:20-32:

export const getDatabaseByNameOrBinding = async (
	config: Config,
	accountId: string,
	name: string
): Promise<Database> => {
	const dbFromConfig = getDatabaseInfoFromConfig(config, name);
	if (dbFromConfig) {
		return dbFromConfig;  // BUG: Returns immediately without API verification
	}
	// Only queries API if config doesn't have the database
	const allDBs = await listDatabases(config, accountId);
	// ...
};

When a user:

  1. Creates a D1 database (e.g., my-db with UUID abc123)
  2. Adds it to wrangler.toml with database_id = "abc123"
  3. Deletes the database via CLI or dashboard
  4. Creates a new database with the same name my-db (now UUID def456)
  5. Runs wrangler d1 execute my-db --remote

The command uses the stale UUID abc123 from config instead of the current UUID def456.

Proposed Solution

Modify getDatabaseByNameOrBinding() to verify the config's database_id against the API when performing remote operations:

export const getDatabaseByNameOrBinding = async (
	config: Config,
	accountId: string,
	name: string,
	options?: { validateRemote?: boolean }
): Promise<Database> => {
	const dbFromConfig = getDatabaseInfoFromConfig(config, name);
	
	if (dbFromConfig) {
		// For remote operations, validate that the database_id still matches the name
		if (options?.validateRemote) {
			const allDBs = await listDatabases(config, accountId);
			const apiDb = allDBs.find((db) => db.name === dbFromConfig.name);
			
			if (apiDb && apiDb.uuid !== dbFromConfig.uuid) {
				throw new UserError(
					`Database '${dbFromConfig.name}' exists but has a different ID than configured.\n` +
					`  Config ID: ${dbFromConfig.uuid}\n` +
					`  Actual ID: ${apiDb.uuid}\n` +
					`Update your wrangler.toml with the correct database_id, or remove it to use the API-resolved ID.`
				);
			}
			
			if (!apiDb) {
				throw new UserError(
					`Database '${dbFromConfig.name}' with ID '${dbFromConfig.uuid}' not found.\n` +
					`The database may have been deleted. Check your wrangler.toml configuration.`
				);
			}
		}
		return dbFromConfig;
	}

	const allDBs = await listDatabases(config, accountId);
	const matchingDB = allDBs.find((db) => db.name === name);
	if (!matchingDB) {
		throw new UserError(`Couldn't find DB with name '${name}'`);
	}
	return matchingDB;
};

Alternative (simpler but more API calls): For remote commands, always query the API and ignore the config's database_id:

export const getDatabaseByNameOrBinding = async (
	config: Config,
	accountId: string,
	name: string,
	options?: { validateRemote?: boolean }
): Promise<Database> => {
	const dbFromConfig = getDatabaseInfoFromConfig(config, name);
	const dbName = dbFromConfig?.name ?? name;
	
	if (options?.validateRemote) {
		// For remote operations, always get the current UUID from API
		const allDBs = await listDatabases(config, accountId);
		const matchingDB = allDBs.find((db) => db.name === dbName);
		if (!matchingDB) {
			throw new UserError(`Couldn't find DB with name '${dbName}'`);
		}
		// Preserve config settings like migrations path, but use API's UUID
		return dbFromConfig 
			? { ...dbFromConfig, uuid: matchingDB.uuid }
			: matchingDB;
	}
	
	if (dbFromConfig) {
		return dbFromConfig;
	}

	const allDBs = await listDatabases(config, accountId);
	const matchingDB = allDBs.find((db) => db.name === name);
	if (!matchingDB) {
		throw new UserError(`Couldn't find DB with name '${name}'`);
	}
	return matchingDB;
};

Implementation Difficulty

Medium

Justification:

  • The fix requires modifying a shared utility function used by multiple commands
  • Need to add a parameter to distinguish local vs remote execution context
  • All callers in remote contexts need to be updated to pass the new option
  • Testing requires both unit tests and integration tests with mock API responses
  • Risk of breaking existing behavior if not carefully implemented

Files to Modify

  1. packages/wrangler/src/d1/utils.ts - Core fix: add remote validation logic
  2. packages/wrangler/src/d1/execute.ts - Update executeRemotely() to pass validation option
  3. packages/wrangler/src/d1/info.ts - Update to pass validation option
  4. packages/wrangler/src/d1/delete.ts - Update to pass validation option
  5. packages/wrangler/src/d1/export.ts - Update to pass validation option
  6. packages/wrangler/src/d1/insights.ts - Update to pass validation option
  7. packages/wrangler/src/d1/migrations/apply.ts - Update remote path to pass validation option
  8. packages/wrangler/src/d1/migrations/list.ts - Update remote path to pass validation option
  9. packages/wrangler/src/d1/migrations/helpers.ts - May need updates for migration helpers

Testing Recommendations

  1. Unit Tests:

    • Test getDatabaseByNameOrBinding() with mismatched config ID and API ID
    • Test error messages are clear and actionable
    • Test that local mode still works without API calls
  2. Integration Tests:

    • Create a D1 database, add to config, delete via API, recreate with same name
    • Verify remote commands fail with clear error about ID mismatch
    • Verify local commands still use config ID (expected behavior)
  3. Edge Cases:

    • Database exists in config but not in API (deleted)
    • Database name in config doesn't match any API database
    • Preview database ID mismatch
    • Network failures during API validation

Suggested Comment

This issue is still relevant and confirmed in the current codebase (wrangler 4.60.0).

Root cause: The getDatabaseByNameOrBinding() function in packages/wrangler/src/d1/utils.ts prioritizes the database_id from wrangler.toml without validating it against the D1 API for remote operations.

Proposed fix: Add an optional validateRemote parameter that, when true, verifies the config's database_id matches the actual database returned by the API. If there's a mismatch, throw a clear error telling the user to update their config.

Would be happy to see a PR implementing this fix. The related issue #3438 would also be resolved by this change.

Notes & Feedback (0)

No notes yet.

Add Note