#6256 `wrangler d1` commands shouldn't read wrangler.toml if executing remotely
Implement API validation for remote D1 commands
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
- No fix found in changelog or merged PRs - Searched for issue #6256, no references found
- Related issue #3438 still open - The issue references #3438 (D1 - deleting and re-creating a DB conflicts) which is also still open
- Code review confirms the bug exists - In
packages/wrangler/src/d1/utils.ts, thegetDatabaseByNameOrBinding()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);
// ...
};
- All remote D1 commands are affected -
execute --remote,info,delete,migrations apply --remote, etc. all usegetDatabaseByNameOrBinding()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:
- Creates a D1 database (e.g.,
my-dbwith UUIDabc123) - Adds it to wrangler.toml with
database_id = "abc123" - Deletes the database via CLI or dashboard
- Creates a new database with the same name
my-db(now UUIDdef456) - 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
packages/wrangler/src/d1/utils.ts- Core fix: add remote validation logicpackages/wrangler/src/d1/execute.ts- UpdateexecuteRemotely()to pass validation optionpackages/wrangler/src/d1/info.ts- Update to pass validation optionpackages/wrangler/src/d1/delete.ts- Update to pass validation optionpackages/wrangler/src/d1/export.ts- Update to pass validation optionpackages/wrangler/src/d1/insights.ts- Update to pass validation optionpackages/wrangler/src/d1/migrations/apply.ts- Update remote path to pass validation optionpackages/wrangler/src/d1/migrations/list.ts- Update remote path to pass validation optionpackages/wrangler/src/d1/migrations/helpers.ts- May need updates for migration helpers
Testing Recommendations
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
- Test
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)
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 inpackages/wrangler/src/d1/utils.tsprioritizes thedatabase_idfrom wrangler.toml without validating it against the D1 API for remote operations.Proposed fix: Add an optional
validateRemoteparameter that, when true, verifies the config'sdatabase_idmatches 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.