multica-ai/multica · error

invalid direction %q (want \"up\" or \"down\")

Error message

invalid direction %q (want \"up\" or \"down\")

What it means

The migrate command rejected its direction option before touching the database: runMigrations only accepts "up" or "down". This is pure input validation on opts.Direction, typically populated from a CLI flag or a programmatic call into the migrate package.

Source

Thrown at server/cmd/migrate/main.go:340

}

// runMigrations applies (direction="up") or rolls back (direction="down")
// the given file list against the supplied pool, serialized through a
// Postgres session-level advisory lock so multiple concurrent runners
// (multi-replica startup, scale-up, manual migrate overlap) take turns
// instead of racing each other.
//
// It is safe to invoke concurrently from multiple goroutines or
// processes against the same database with the same options: every
// caller blocks on pg_advisory_lock, and once it is their turn the
// already-applied EXISTS check turns each finished migration into a
// no-op skip. See GitHub multica-ai/multica#3647 / MUL-2923.
func runMigrations(ctx context.Context, pool *pgxpool.Pool, opts runOptions) error {
	switch opts.Direction {
	case "up", "down":
		// ok
	default:
		return fmt.Errorf("invalid direction %q (want \"up\" or \"down\")", opts.Direction)
	}

	table := opts.SchemaMigrationsTable
	if table == "" {
		table = defaultSchemaMigrationsTable
	}
	tableIdent, err := quoteQualifiedIdentifier(table)
	if err != nil {
		return fmt.Errorf("invalid schema migrations table %q: %w", table, err)
	}
	lockKey := opts.AdvisoryLockKey
	if lockKey == 0 {
		lockKey = migrationAdvisoryLockKey
	}

	// pg_advisory_lock is scoped to a single session, so we must pin one
	// *pgxpool.Conn for the whole run — calling pool.Exec would attach the
	// lock to a random connection that pgxpool could hand back out before

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Pass exactly up or down: ./migrate -direction=up
  2. If the value comes from env/CI variables, default it and assert non-empty before invoking
  3. Check for surrounding whitespace in the flag value

Example fix

# before
migrate -direction="$MIGRATE_DIR"   # unset -> ""

# after
MIGRATE_DIR=${MIGRATE_DIR:-up}
migrate -direction="$MIGRATE_DIR"
Defensive patterns

Strategy: validation

Validate before calling

switch direction {
case "up", "down":
default:
    return fmt.Errorf("invalid direction %q", direction)
}

Type guard

func isValidDirection(s string) bool { return s == "up" || s == "down" }

Prevention

When it happens

Trigger: Passing -direction=upward, an empty string from an unset flag/env, or typo like "Up" (case-sensitive) when invoking server/cmd/migrate.

Common situations: Scripts with a variable that expands to empty; CI job parameter misspelled; calling runMigrations programmatically with an unvalidated string.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/d4dd92d1b5a8b00e. Report an issue: GitHub.