{"record":{"id":"78b44fd46a806b7c","repo":"wavetermdev/waveterm","slug":"opening-fs-w","errorCode":null,"errorMessage":"opening fs: %w","messagePattern":"opening fs: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/util/migrateutil/migrateutil.go","lineNumber":29,"sourceCode":"\n\t\"github.com/golang-migrate/migrate/v4\"\n\t\"github.com/golang-migrate/migrate/v4/source/iofs\"\n\n\tsqlite3migrate \"github.com/golang-migrate/migrate/v4/database/sqlite3\"\n)\n\nfunc GetMigrateVersion(m *migrate.Migrate) (uint, bool, error) {\n\tcurVersion, dirty, err := m.Version()\n\tif err == migrate.ErrNilVersion {\n\t\treturn 0, false, nil\n\t}\n\treturn curVersion, dirty, err\n}\n\nfunc MakeMigrate(storeName string, db *sql.DB, migrationFS fs.FS, migrationsName string) (*migrate.Migrate, error) {\n\tfsVar, err := iofs.New(migrationFS, migrationsName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"opening fs: %w\", err)\n\t}\n\tmdriver, err := sqlite3migrate.WithInstance(db, &sqlite3migrate.Config{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"making %s migration driver: %w\", storeName, err)\n\t}\n\tm, err := migrate.NewWithInstance(\"iofs\", fsVar, \"sqlite3\", mdriver)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"making %s migration: %w\", storeName, err)\n\t}\n\treturn m, nil\n}\n\nfunc Migrate(storeName string, db *sql.DB, migrationFS fs.FS, migrationsName string) error {\n\tlog.Printf(\"migrate %s\\n\", storeName)\n\tm, err := MakeMigrate(storeName, db, migrationFS, migrationsName)\n\tif err != nil {\n\t\treturn err\n\t}","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/util/migrateutil/migrateutil.go#L11-L47","documentation":"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'.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: migrations not embedded, migrationsName points nowhere\n//go:embed db/migrations\nvar dbFS embed.FS\nmigrateutil.Migrate(\"waveai\", db, dbFS, \"migrations\")\n// after: embed pattern and subdirectory name agree\n//go:embed db/migrations/*.sql\nvar dbFS embed.FS\nmigrateutil.Migrate(\"waveai\", db, dbFS, \"db/migrations\")","handlingStrategy":"validation","validationCode":"func assertMigrationsPresent(migrationFS fs.FS, migrationsName string) error {\n    entries, err := fs.ReadDir(migrationFS, migrationsName)\n    if err != nil { return fmt.Errorf(\"migration dir %q missing: %w\", migrationsName, err) }\n    if len(entries) == 0 { return fmt.Errorf(\"migration dir %q is empty\", migrationsName) }\n    return nil\n}\n// call before: if err := assertMigrationsPresent(migrationFS, migrationsName); err != nil { return err }","typeGuard":"func migrationsFSReady(migrationFS fs.FS, migrationsName string) bool {\n    entries, err := fs.ReadDir(migrationFS, migrationsName)\n    return err == nil && len(entries) > 0\n}","tryCatchPattern":"m, err := migrateutil.MakeMigrate(storeName, db, migrationFS, migrationsName)\nif err != nil {\n    if strings.Contains(err.Error(), \"opening fs:\") {\n        return fmt.Errorf(\"check //go:embed directive and migrationsName=%q: %w\", migrationsName, err)\n    }\n    return err\n}","preventionTips":["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."],"tags":["migrations","embed","iofs","go","database"],"backgroundTag":"migration-file-not-found","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}