remix-run/remix · error · AssertionError

expected promise to resolve, but it rejected with: ${stringi

Error message

expected promise to resolve, but it rejected with: ${stringify(result.error)}

What it means

The `migrate`, `reset`, `rollback`, and `status` commands all need a migrations directory, supplied by `db.migrations.directory` in `remix.json` or the `--migrations` flag. `resolveDatabaseCommandPlan` throws during planning when neither is set.

Source

Thrown at packages/assert/src/lib/expect.ts:468

  promise: Promise<unknown> | (() => unknown),
  mode: 'resolves' | 'rejects',
  negated: boolean,
): AsyncMatchers {
  async function settle(): Promise<{ resolved: boolean; value?: unknown; error?: unknown }> {
    try {
      let value = await (typeof promise === 'function' ? promise() : promise)
      return { resolved: true, value }
    } catch (error) {
      return { resolved: false, error }
    }
  }

  function buildMatcher(matcher: keyof Matchers) {
    return async (...args: unknown[]) => {
      let result = await settle()
      if (mode === 'resolves') {
        if (!result.resolved) {
          throw new AssertionError({
            message: `expected promise to resolve, but it rejected with: ${stringify(result.error)}`,
            actual: result.error,
            operator: `resolves.${matcher}`,
          })
        }
        let m = createMatchers(result.value, negated) as any
        m[matcher](...args)
      } else {
        if (result.resolved) {
          throw new AssertionError({
            message: `expected promise to reject, but it resolved with: ${stringify(result.value)}`,
            actual: result.value,
            operator: `rejects.${matcher}`,
          })
        }
        // For rejects.toThrow we check the error against the expected matcher.
        if (matcher === 'toThrow') {
          let pass = checkErrorMatch(result.error, args[0])

View on GitHub (pinned to 9696913134)

Solutions

  1. Add `"migrations": { "directory": "./migrations" }` under `db` in remix.json
  2. Or pass it per-run: `remix db migrate --migrations ./db/migrations`
  3. Remember config paths resolve against the config directory, flag paths against the cwd

Example fix

# before
remix db migrate
# after
remix db migrate --migrations ./db/migrations
Defensive patterns

Strategy: validation

Validate before calling

let config = JSON.parse(fs.readFileSync('remix.json','utf8'));
let migrations = config?.db?.migrations?.directory ?? './migrations';
run(['remix','db','migrate','--migrations',migrations])

Type guard

function hasMigrationsDir(config: any): boolean {
  return Boolean(config?.db?.migrations?.directory);
}

Prevention

When it happens

Trigger: Running `remix db migrate|reset|rollback|status` when `migrations === undefined` — no `db.migrations.directory` in remix.json and no `--migrations` CLI flag.

Common situations: Config defines adapter/filename but no migrations section; running migrations before any have been generated; forgetting the flag when testing a config-less invocation.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/f0aafa3bec3c7749. Report an issue: GitHub.