Skip to content

Commit 4b7026c

Browse files
committed
fix: migration status showing 0/0 — store LatestVersion in health registry
LatestMigrationVersion() re-scans migration files from disk using relative paths, which fail at runtime when the binary's CWD differs from the project root. Fix by computing LatestVersion once at startup (when CWD is correct) and storing it in both PluginMigrationResult and PluginHealthRegistry. GetMigrationStatus() now reads LatestVersion from the registry instead of re-scanning disk, making it work correctly in all environments. https://claude.ai/code/session_01QJLkgjQDu5qohzJKGV4hj9
1 parent 3134747 commit 4b7026c

4 files changed

Lines changed: 34 additions & 31 deletions

File tree

cmd/server/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func main() {
6060
pluginHealth := database.NewPluginHealthRegistry()
6161
pluginResults := database.RunPluginMigrations(db, database.RegisteredPlugins())
6262
for _, r := range pluginResults {
63-
pluginHealth.Register(r.Slug, r.Healthy, r.Error, r.Version)
63+
pluginHealth.Register(r.Slug, r.Healthy, r.Error, r.Version, r.LatestVersion)
6464
}
6565
if degraded := pluginHealth.DegradedPlugins(); len(degraded) > 0 {
6666
slog.Warn("some plugins are degraded — features disabled",

internal/database/plugin_health.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ type PluginHealth struct {
2020
// Version is the highest applied migration version for this plugin.
2121
Version int
2222

23+
// LatestVersion is the highest available migration version from disk.
24+
LatestVersion int
25+
2326
// UpdatedAt is when this status was last set.
2427
UpdatedAt time.Time
2528
}
@@ -41,14 +44,15 @@ func NewPluginHealthRegistry() *PluginHealthRegistry {
4144

4245
// Register records the health status of a plugin. Called once per plugin
4346
// during startup after migrations complete (or fail).
44-
func (r *PluginHealthRegistry) Register(slug string, healthy bool, err error, version int) {
47+
func (r *PluginHealthRegistry) Register(slug string, healthy bool, err error, version int, latestVersion int) {
4548
r.mu.Lock()
4649
defer r.mu.Unlock()
4750

4851
h := PluginHealth{
49-
Healthy: healthy,
50-
Version: version,
51-
UpdatedAt: time.Now(),
52+
Healthy: healthy,
53+
Version: version,
54+
LatestVersion: latestVersion,
55+
UpdatedAt: time.Now(),
5256
}
5357
if err != nil {
5458
h.Error = err.Error()

internal/database/plugin_schema.go

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ type PluginSchema struct {
3131
// PluginMigrationResult holds the outcome of running a single plugin's
3232
// schema migrations.
3333
type PluginMigrationResult struct {
34-
Slug string
35-
Healthy bool
36-
Error error
37-
Version int
34+
Slug string
35+
Healthy bool
36+
Error error
37+
Version int
38+
LatestVersion int
3839
}
3940

4041
// pluginMigration represents a single numbered migration for a plugin.
@@ -130,15 +131,19 @@ func runSinglePluginMigrations(db *sql.DB, plugin PluginSchema) PluginMigrationR
130131
}
131132

132133
if len(migrations) == 0 {
133-
return PluginMigrationResult{Slug: plugin.Slug, Healthy: true, Version: 0}
134+
return PluginMigrationResult{Slug: plugin.Slug, Healthy: true, Version: 0, LatestVersion: 0}
134135
}
135136

137+
// Compute the highest available migration version from files.
138+
latestVersion := migrations[len(migrations)-1].Version
139+
136140
// Get already-applied versions.
137141
applied, err := getPluginAppliedVersions(ctx, db, plugin.Slug)
138142
if err != nil {
139143
return PluginMigrationResult{
140-
Slug: plugin.Slug,
141-
Error: fmt.Errorf("reading applied versions: %w", err),
144+
Slug: plugin.Slug,
145+
LatestVersion: latestVersion,
146+
Error: fmt.Errorf("reading applied versions: %w", err),
142147
}
143148
}
144149
appliedSet := make(map[int]bool, len(applied))
@@ -165,9 +170,10 @@ func runSinglePluginMigrations(db *sql.DB, plugin PluginSchema) PluginMigrationR
165170
// code, so we skip the SQL validator (unlike user extensions).
166171
if err := execPluginMigration(ctx, db, plugin.Slug, m); err != nil {
167172
return PluginMigrationResult{
168-
Slug: plugin.Slug,
169-
Error: err,
170-
Version: highestVersion,
173+
Slug: plugin.Slug,
174+
Error: err,
175+
Version: highestVersion,
176+
LatestVersion: latestVersion,
171177
}
172178
}
173179

@@ -179,9 +185,10 @@ func runSinglePluginMigrations(db *sql.DB, plugin PluginSchema) PluginMigrationR
179185
}
180186

181187
return PluginMigrationResult{
182-
Slug: plugin.Slug,
183-
Healthy: true,
184-
Version: highestVersion,
188+
Slug: plugin.Slug,
189+
Healthy: true,
190+
Version: highestVersion,
191+
LatestVersion: latestVersion,
185192
}
186193
}
187194

internal/plugins/admin/database_service.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -229,23 +229,15 @@ func (e *databaseExplorer) GetMigrationStatus(ctx context.Context) ([]PluginMigr
229229
Healthy: true,
230230
}
231231

232-
// Get health from registry.
232+
// Get health and version info from registry (populated at startup
233+
// when migration files are scanned — avoids re-reading disk at runtime).
233234
if h := e.pluginHealth.Get(p.Slug); h != nil {
234235
status.Healthy = h.Healthy
235236
status.CurrentVersion = h.Version
237+
status.LatestVersion = h.LatestVersion
236238
status.Error = h.Error
237239
}
238-
239-
// Get latest available version from migration files.
240-
latest, err := database.LatestMigrationVersion(p.MigrationsDir)
241-
if err != nil {
242-
slog.Warn("failed to read migration files",
243-
slog.String("plugin", p.Slug),
244-
slog.Any("error", err),
245-
)
246-
}
247-
status.LatestVersion = latest
248-
status.Pending = latest - status.CurrentVersion
240+
status.Pending = status.LatestVersion - status.CurrentVersion
249241
if status.Pending < 0 {
250242
status.Pending = 0
251243
}
@@ -298,7 +290,7 @@ func (e *databaseExplorer) ApplyPendingMigrations(ctx context.Context) ([]databa
298290

299291
// Update the health registry with new results.
300292
for _, r := range results {
301-
e.pluginHealth.Register(r.Slug, r.Healthy, r.Error, r.Version)
293+
e.pluginHealth.Register(r.Slug, r.Healthy, r.Error, r.Version, r.LatestVersion)
302294
}
303295

304296
return results, nil

0 commit comments

Comments
 (0)