wavetermdev/waveterm · error
opening fs: %w
Error message
opening fs: %w
What it means
MakeMigrate wraps golang-migrate's iofs.New, which opens the embedded (or arbitrary fs.FS) subdirectory holding migration files. iofs.New returns an error (os.ErrNotExist wrapped) when the subdirectory path does not exist within the supplied filesystem, and MakeMigrate surfaces it as 'opening fs: %w'.
Source
Thrown at pkg/util/migrateutil/migrateutil.go:29
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/source/iofs"
sqlite3migrate "github.com/golang-migrate/migrate/v4/database/sqlite3"
)
func GetMigrateVersion(m *migrate.Migrate) (uint, bool, error) {
curVersion, dirty, err := m.Version()
if err == migrate.ErrNilVersion {
return 0, false, nil
}
return curVersion, dirty, err
}
func MakeMigrate(storeName string, db *sql.DB, migrationFS fs.FS, migrationsName string) (*migrate.Migrate, error) {
fsVar, err := iofs.New(migrationFS, migrationsName)
if err != nil {
return nil, fmt.Errorf("opening fs: %w", err)
}
mdriver, err := sqlite3migrate.WithInstance(db, &sqlite3migrate.Config{})
if err != nil {
return nil, fmt.Errorf("making %s migration driver: %w", storeName, err)
}
m, err := migrate.NewWithInstance("iofs", fsVar, "sqlite3", mdriver)
if err != nil {
return nil, fmt.Errorf("making %s migration: %w", storeName, err)
}
return m, nil
}
func Migrate(storeName string, db *sql.DB, migrationFS fs.FS, migrationsName string) error {
log.Printf("migrate %s\n", storeName)
m, err := MakeMigrate(storeName, db, migrationFS, migrationsName)
if err != nil {
return err
}View on GitHub (pinned to a4447c1563)
Solutions
- Check the //go:embed directive includes the migrations directory (e.g. //go:embed migrations) in the package that defines the FS.
- Verify migrationsName matches the actual directory name inside the FS (exact spelling/case).
- List the FS contents (fs.WalkDir) at startup or in a test to confirm the migration files are present.
- Ensure migration files follow the golang-migrate naming convention (0001_init.up.sql etc.) under that directory.
- Rebuild the binary so the embed picks up newly added migration files.
Example fix
// before: migrations not embedded, migrationsName points nowhere
//go:embed db/migrations
var dbFS embed.FS
migrateutil.Migrate("waveai", db, dbFS, "migrations")
// after: embed pattern and subdirectory name agree
//go:embed db/migrations/*.sql
var dbFS embed.FS
migrateutil.Migrate("waveai", db, dbFS, "db/migrations") Defensive patterns
Strategy: validation
Validate before calling
func assertMigrationsPresent(migrationFS fs.FS, migrationsName string) error {
entries, err := fs.ReadDir(migrationFS, migrationsName)
if err != nil { return fmt.Errorf("migration dir %q missing: %w", migrationsName, err) }
if len(entries) == 0 { return fmt.Errorf("migration dir %q is empty", migrationsName) }
return nil
}
// call before: if err := assertMigrationsPresent(migrationFS, migrationsName); err != nil { return err } Type guard
func migrationsFSReady(migrationFS fs.FS, migrationsName string) bool {
entries, err := fs.ReadDir(migrationFS, migrationsName)
return err == nil && len(entries) > 0
} Try / catch
m, err := migrateutil.MakeMigrate(storeName, db, migrationFS, migrationsName)
if err != nil {
if strings.Contains(err.Error(), "opening fs:") {
return fmt.Errorf("check //go:embed directive and migrationsName=%q: %w", migrationsName, err)
}
return err
} Prevention
- Add a //go:embed <migrationsDir>/*.sql directive in the same package that declares the embed.FS.
- Keep migrationsName in sync with the actual embedded directory path; use a constant shared with the embed pattern.
- Add a startup or unit test that walks the FS and asserts migration files exist.
- Follow golang-migrate file naming (NNNN_name.up.sql / .down.sql) so files are recognized.
- Rebuild the binary after adding migration files — embed content is fixed at compile time.
When it happens
Trigger: Calling Migrate/MakeMigrate with a migrationsName subdirectory that is not embedded in the binary (missing //go:embed directive, wrong embed path), a migrationsName that does not match the actual directory name, or an empty/incorrect embed.FS passed as migrationFS.
Common situations: Renaming the migrations folder without updating the migrationsName string or the go:embed pattern; forgetting go:embed so the FS is empty at runtime; embedding the parent dir but passing a child path that doesn't exist; building with an older Go toolchain where embed patterns silently matched nothing.
Related errors
- error getting client: %v
- error getting tab: %w
- error getting client data: %w
- error updating client data: %w
- error getting object: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/78b44fd46a806b7c.
Report an issue: GitHub.