Skip to content

fix(state): track last-migrated version so plugin migrations surface on upgrade (#320)#421

Open
bitwize-music wants to merge 3 commits into
developfrom
fix/320-migration-version-tracking
Open

fix(state): track last-migrated version so plugin migrations surface on upgrade (#320)#421
bitwize-music wants to merge 3 commits into
developfrom
fix/320-migration-version-tracking

Conversation

@bitwize-music

Copy link
Copy Markdown
Collaborator

Summary

Fixes #320 — plugin migration notes never surfaced on upgrade.

The session-start migration check (Step 4.5) compared state's plugin_version against the manifest, but build_state/incremental_update overwrote plugin_version with the installed version on every rebuild. So "stored < current" could never be true and migrations were silently skipped for every upgrading user. Worse, no code actually parsed or surfaced migrations — Step 4.5 was prose with nothing behind it.

What changed

State model (tools/state/indexer.py)

  • Added last_migrated_version — the version through which migrations have been processed. Only advances on explicit acknowledgment; preserved across rebuilds. plugin_version keeps its meaning (installed version, for display).
  • build_state seeds last_migrated_version to the installed version (correct for a brand-new install — nothing historical to surface).
  • New carry_migration_tracking() preserves the prior value across full rebuilds, so a rebuild never silently acknowledges pending migrations. Wired into cmd_rebuild (CLI), StateCache.rebuild() and the _load_from_disk auto-rebuild (MCP).
  • New pure functions parse_migration_file() and get_pending_migrations() compute the pending notes between last_migrated_version and the installed version, sorted ascending.
  • validate_state type-checks the new field (optional, so legacy states still validate).

MCP tools (handlers/health.py, server.py)

  • get_pending_migrations — returns pending notes (parsed + sorted) with a reason (current | upgrade | untracked).
  • acknowledge_migrations — advances last_migrated_version and persists (new thread-safe StateCache.acknowledge_migrations).
  • get_plugin_version's needs_upgrade now reflects actual pending migrations instead of the (clobbered, unreliable) version diff.

Null semantics. A pre-tracking state (last_migrated_version null) now surfaces the full backlog once (reason: "untracked") instead of silently skipping — fixing the reporter's exact case. A fresh install is seeded to the installed version, so it sees nothing (no historical bombardment).

No schema-version bump — deliberately. The live MCP path auto-rebuilds via build_state on a version mismatch, which would erase the very "behind" status the fix depends on. last_migrated_version is added as a backward-compatible optional field instead.

Docsskills/session-start/SKILL.md (Step 4.5), CLAUDE.md, migrations/README.md, reference/state-schema.md, CHANGELOG.md.

Testing

  • New tests/unit/state/test_migration_tracking.py (24 tests): build_state seeding, incremental preservation, carry_migration_tracking, parse_migration_file, get_pending_migrations (including issue bug: plugin_version tracking makes migrations undetectable on upgrade #320's exact acceptance scenario), validate_state.
  • New server tests: get_pending_migrations / acknowledge_migrations tools, the full upgrade loop (behind → surfaced → acknowledged → clear), and StateCache.acknowledge_migrations persistence. Updated two get_plugin_version tests to the corrected semantics.
  • Full gate green: ruff, bandit, mypy (no issues), and the complete pytest suite.

🤖 Generated with Claude Code

bitwize-music and others added 3 commits June 11, 2026 02:32
…on upgrade (#320)

Step 4.5's migration check compared state's plugin_version against the
manifest, but build_state/incremental_update overwrote plugin_version with
the installed version on every rebuild — so "stored < current" was never
true and migrations were silently skipped for every upgrading user. No code
parsed or surfaced migrations either; Step 4.5 was prose with nothing behind
it.

Separate last_migrated_version (advances only on explicit acknowledgment,
preserved across rebuilds) from plugin_version (installed version, display
only). Add get_pending_migrations + acknowledge_migrations MCP tools backed
by pure, tested helpers. A pre-tracking state (null) now surfaces the full
backlog once instead of skipping; a fresh install is seeded to the installed
version. No schema-version bump — bumping would force the live MCP path to
auto-rebuild every state and erase the "behind" status the fix depends on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s reason

Addresses self-review of #320:
- Add integration tests proving StateCache.rebuild() carries
  last_migrated_version over the freshly-built installed seed (and records
  None for pre-field states). The pure helper was tested but its wiring into
  rebuild() was not — removing the carry line previously kept the suite green.
- get_pending_migrations now returns reason "unknown" (not "current") when the
  installed version can't be read, so callers can distinguish "up to date"
  from "couldn't read plugin.json".
- Hoist the per-call indexer import in health.py to module level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…320)

acknowledge_migrations computed `target = version or _read_plugin_version()`
and wrote it unconditionally. With no version argument and an unreadable
plugin.json, target was None, so it wrote last_migrated_version=None — which
silently un-acknowledged the entire backlog and resurfaced it on the next
session, while still reporting success. Now it returns an error and leaves
state untouched when the version can't be determined.

Also cover the previously-untested `"error" in state` short-circuit, and
document the `reason: "unknown"` outcome (plugin.json unreadable -> empty
pending -> no action) in CLAUDE.md Step 4.5 and the session-start skill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant