remix-run/remix · error · TypeError

${optionName} values must be strings

Error message

${optionName} values must be strings

What it means

Execution-time backstop in `executeDatabaseCommand`: before loading the seed module it re-checks `plan.seed`. Plans built through `resolveDatabaseCommandPlan` are already validated, so hitting this means a hand-constructed or mutated plan reached the executor — an internal invariant or programmatic misuse, not a CLI typo.

Source

Thrown at packages/assets/src/lib/access.ts:146

      packageRootsDirty = true
    },
    inspect,
    isAllowed(filePath) {
      return inspect(filePath).allowed
    },
  }
}

function normalizePackageNames(
  packageOption: readonly string[] | undefined,
  optionName: 'allowPackages',
): Set<string> {
  let packageNames = new Set<string>()

  for (let packageName of packageOption ?? []) {
    if (typeof packageName !== 'string') {
      throw new TypeError(`${optionName} values must be strings`)
    }

    let normalizedPackageName = packageName.trim()
    if (!isValidPackageName(normalizedPackageName)) {
      throw new TypeError(`${optionName} values must be package names. Received "${packageName}".`)
    }

    packageNames.add(normalizedPackageName)
  }

  return packageNames
}

function validatePackageName(packageName: string, message: string): void {
  if (!isValidPackageName(packageName)) {
    throw new TypeError(message)
  }
}

View on GitHub (pinned to 9696913134)

Solutions

  1. Build plans via the planner (`resolveDatabaseCommandPlan`) instead of object literals
  2. Set `db.seed` in remix.json or pass `--seed` so plans carry the field
  3. Guard programmatically: `if (plan.command === 'seed' && !plan.seed) throw` before executing

Example fix

// before
let plan = { command: 'seed', db }
executeDatabaseCommand(plan)
// after
let plan = await resolveDatabaseCommandPlan(invocation, dbConfig)
executeDatabaseCommand(plan)
Defensive patterns

Strategy: try-catch

Validate before calling

if (plan.command === 'seed' && plan.seed === undefined) {
  throw new Error('Seed plan requires a seed path');
}

Type guard

function isExecutableSeedPlan(plan: any): plan is { command: 'seed'; seed: string } {
  return plan.command === 'seed' && typeof plan.seed === 'string';
}

Try / catch

Wrap executeDatabaseCommand in try/catch; on 'requires db.seed' surface guidance to set --seed or db.seed and rebuild the plan via the planner.

Prevention

When it happens

Trigger: Calling `executeDatabaseCommand`/`runDatabaseCommand` programmatically with a plan where `command === 'seed'` and `seed === undefined`, bypassing `resolveDatabaseCommandPlan`.

Common situations: Scripts or tests constructing plan objects by hand; refactors that drop or rename the seed field; partial plans copied between commands.

Related errors


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