Changes and upgrade notes

v0.1.14

This page lists the releases of Mishka Installer in the 0.1 line, newest first, up to 0.1.14. Each entry says what changed and what to do when you upgrade past it. A change that can break code you already run is marked Breaking.

The entries follow the CHANGELOG file of the project, and each breaking change was checked against the code of its release. The releases 0.1.2 to 0.1.5 and 0.1.7 have no CHANGELOG entry, so their entries come from the code. The 0.0 releases came before the rewrite and are not covered.

How to upgrade

  1. Find the release you run. Application.spec(:mishka_installer, :vsn) returns it as a charlist, such as ~c"0.1.14".
  2. Read each entry between that release and the one you want. Do what its "When you upgrade" list says.
  3. Run mix deps.update mishka_installer, then your tests.
  4. Restart the node. The library rebuilds its events on boot.

Only the entry for 0.1.6 changes what the library stores in Mnesia.

Breaking changes at a glance

ReleaseWhat breaksWhat to do
0.1.14The installer refuses a version that is not a semantic version.Use MAJOR.MINOR.PATCH versions. Delete the extra records of one application.
0.1.13An event that cannot run answers your data, not an error tuple.Remove the code that matches the error. Pass cannot_run: where the answer must refuse.
0.1.12req must be ~> 0.7, and the lowest versions of phoenix_pubsub, plug and guarded_struct went up.Raise those requirements in your own mix.exs.
0.1.11restart on a stopped plugin is an error. Registering a plugin again updates its row. Plugins that depend on another change status on their own.Use enable to switch a stopped plugin on.
0.1.6The installer takes only pre-built packages, remote installs are closed until you allow them, the Installer table has new columns, and the helper modules moved.Follow every step of the entry.
0.1.3Hook.call/3 answers your data, not an error, for an event that has no module.Remove the code that matches {:error, :undefined_function_error}.
0.1.2Elixir 1.17 or later is required.Upgrade Elixir.

0.1.14

Released on 2026-10-17. The installer checks versions as semantic versions and keeps one record for each application.

When you upgrade

  • Give every library you install a semantic version. 0.1.7, 1.0.0-rc.1 and 1.0.0+build.5 pass. 1.0 and v1.0.0 do not.
  • Records that are already stored are left as they are. A stored record with the version 1.0 is read back unchanged.
  • If you upgraded libraries with 0.1.13 or earlier, the registry can hold several records for one application. List them with Installer.get/0 and keep the newest record of each application:
elixir
alias MishkaInstaller.Installer.Installer

Installer.get()
|> Enum.group_by(& &1.app)
|> Enum.each(fn {_app, records} ->
  [_newest | older] = Enum.sort_by(records, & &1.inserted_at, :desc)
  Enum.each(older, &Installer.delete(:id, &1.id))
end)

Fixed

  • An install no longer raises Version.InvalidVersionError for a version such as "1.0" when an application of that name is loaded. With none loaded, such a version was installed as it was. A version that is not semantic is now refused with action: :semver. A loaded application whose own version is not semantic is refused with action: :compare_version_with_installed_app and field: :app. If the installer installed that copy, uninstall it first, then install again.
  • An upgrade updates the record of the application instead of writing a second one. Before, each upgrade left another record, so Installer.get(:app, app) could answer either one and Installer.uninstall/1 removed only one.

The installer reference documentation lists the error shapes, and the install a library documentation shows how to fix each.

0.1.13

Released on 2026-10-12. An event whose plugins cannot run hands your data back, and plugins whose code is gone are cleaned up on boot.

When you upgrade

  • Delete the code that matches that error. Every caller had to tell it apart from the answer of the plugins before it could go on without them.
  • Pass cannot_run: where "no plugin ran" must not look like "no plugin objected", such as a gate that decides access:
elixir
# 0.1.12 and earlier
case Hook.call("auth:gate:check", gate) do
  {:error, [%{action: :call, field: :event}]} -> %{gate | allowed?: false}
  answer -> answer
end

# 0.1.13 and later
Hook.call("auth:gate:check", gate, cannot_run: %{gate | allowed?: false})

An event whose plugins all run ignores the option. The call an event documentation explains it in full.

Changed on boot

After the run-time libraries are loaded and before the events start, the library unregisters every plugin whose application is loaded, no longer lists the plugin's module, and whose module is not loaded either. It logs a warning for each one and emits the [:mishka_installer, :event, :unregistered_gone] telemetry event, with :plugin, :event and :extension. Whatever depended on that plugin is held, as Event.unregister/3 holds it. The same pass runs each time the events start, so it also runs when the store announces that it is ready again.

Before, a plugin that a newer version of its library removed or renamed stayed registered. Its event then ran none of its plugins until you unregistered the record by hand. A plugin whose application is not loaded is left as it was, because that library may still be on its way in. Compiling such an event now logs an error that names the plugins it waits for, once for each compile.

You change nothing. The plugins in installed libraries documentation covers what an upgrade or an uninstall leaves behind for a library that ships plugins.

Fixed

  • An event whose state is not a list, a map, {:ok, _} or {:error, _} comes back as its plugins left it. This covers a LiveView answer such as {:noreply, socket} or {:ok, socket, layout: ...}, and a binary. Before, every call raised inside the chain, logged plugin pipeline raised and answered the state it was given, so what the plugins returned was thrown away.

Added

  • The cannot_run: option of Hook.call/3 and Hook.call!/3.
  • Event.unregister_gone/1.
  • The [:mishka_installer, :event, :unregistered_gone] telemetry event. See the telemetry documentation.

0.1.12

Released on 2026-08-28. The dependency requirements moved up.

When you upgrade

  • If your own mix.exs holds req, phoenix_pubsub, plug or guarded_struct below those versions, raise it. Then run mix deps.update mishka_installer req.
  • The library uses only Req.request!/1 and a %Req.Response{} match, and nothing it uses changed in Req 0.7. If you set :downloader_req_options or :proxy, those options go to Req as they are, so check them against Req 0.7.

Fixed

  • Two advisories in the dependency tree: a denial of service in hpax, fixed in 1.0.4, and issues in html_sanitize_ex, fixed in 1.5.5. Both are fixed only by updating. Run mix deps.update hpax html_sanitize_ex, then mix hex.audit.

0.1.11

Released on 2026-08-03. Plugins that depend on each other change status together, and status changes survive a crash.

When you upgrade

  • Switch a stopped plugin on with Event.enable/2, or enable/0 on the plugin. It is the only way out of :stopped. The error for restart reads "This plugin is stopped, use `enable/2` to switch it back on." and has action: :plugin_status.
  • The fields you declare in code, :priority, :extra and :depends, are applied again on every boot. A value you changed in the registry with Event.write/3 is replaced by the value in code. The stored :id, :status and :inserted_at are kept.
  • If you write statuses yourself with Event.write/3, call Event.sync_dependents/2 afterwards.

Added

  • Event.enable/2, and enable/0 on a plugin. A plugin can override it like the other generated functions.
  • Event.dependents/1 and Event.sync_dependents/2. Nothing keeps a list of dependents: the library reads them from the stored depends.

Fixed

  • A status change is durable. Lifecycle writes use Transaction.durable_transaction/1, which commits synchronously and flushes the log. See the helper modules documentation. Before, a plugin that an operator disabled came back enabled when the node died within about three minutes.
  • A plugin that leaves an event, because it is stopped, held or unregistered, rebuilds the chain at once. Before, it ran until the next boot.
  • Event.restart/3 checks every condition before it writes, so a failed restart leaves no status behind.
  • Event.stop/3 accepts a :held plugin, so an operator can switch off a plugin that a missing dependency blocks.
  • A disabled plugin is never brought back by one of its dependencies starting.
  • A plugin held at boot because a dependency on another event had not started is released when that dependency starts. Before, it stayed dark until the next restart.

The plugin statuses documentation explains the five statuses, and the manage plugins documentation shows enable in use.

0.1.10

Released on 2026-07-03.

Fixed

  • A registration that fails at boot is logged as an error. Before, only a :register_error message was broadcast, and nothing listens to it by default, so a plugin with a priority outside 0 to 100 was rejected without a trace and sat with no record.

When you upgrade

  • After the upgrade, look in the log for failed to register lines. Each names the plugin, the event and the reason.

0.1.9

Released on 2026-07-03.

Fixed

  • A plugin that is stuck in :registered is started again at boot. That status means its very first start never finished, for example because the boot was interrupted between the two Mnesia writes. A :stopped or :held record is still respected, so the switch of an operator survives restarts.

When you upgrade

  • Nothing to change. Plugins that were stuck start on the next boot.

0.1.8

Released on 2026-06-24.

Added

  • The meta: option of Hook.call/3. It hands read-only context to the plugins that define call/2. A plugin that defines call/1 runs as before, and use MishkaInstaller.Event.Hook accepts a plugin that defines either. See the call an event documentation and the write a plugin documentation.

When you upgrade

  • Nothing to change.

0.1.7

Released on 2026-06-20. This release has no CHANGELOG entry. The code did not change, only its documentation text. The guarded_struct requirement moved from ~> 0.1.0-beta.8 to ~> 0.1.0.

When you upgrade

  • Nothing to change.

0.1.6

Released on 2026-06-20. The installer was rebuilt to load pre-built packages, the Mnesia store can form a cluster, and the helper modules moved into the library.

Breaking

  1. The installer takes only pre-built packages. It never runs mix or compiles. A package is an ebin folder with an <app>.app file. The release removed the compile code, the Installer.Collect module and Installer.uninstall/2. 0.1.5 accepted the type values :hex, :github, :github_latest_release, :github_latest_tag, :url and :extracted. 0.1.6 accepts :path, which is the default, :url, :github_tag and :github_latest_release. The fields release, branch, custom_command, dependency_type and compile_type are gone, and asset and checksum are new. What to do: build the ebin package yourself and change the maps you give to Installer. See the install a library documentation.
  2. Remote installs are closed until you allow them. 0.1.5 had no allowlist. Now a :url install needs the host in url_hosts, and a :github_tag or :github_latest_release install needs the repository in github_repos. An empty or missing list blocks them. A :path install is not affected. The library also refuses to install over, or to uninstall, an application in protected_apps, which holds mishka_installer by default. What to do: set config :mishka_installer, :allowlist. See the configuration documentation.
  3. The stored Installer table has new columns. The library creates a table that is missing and never changes one that exists. A node that stored installs with 0.1.5 or earlier keeps the old table. Installer.get/0 then returns [], Installer.get(:app, app) returns a record with its fields in the wrong places, and Installer.write/1 fails with {:aborted, {:bad_type, ...}} and action: :database. The plugin table, Event, has the same fields as before. What to do: delete the old table once, restart the node, and install your libraries again. The library creates the table with the new columns on boot:
    elixir
    :mnesia.delete_table(MishkaInstaller.Installer.Installer)
  4. The helper modules moved. The library used MnesiaAssistant and MishkaDeveloperTools.Helper.Extra and UUID from the mishka_developer_tools package. It now has its own MishkaInstaller.Helper.MnesiaAssistant, MishkaInstaller.Helper.Extra and MishkaInstaller.Helper.UUID, and it no longer depends on mishka_developer_tools. What to do: if your code calls the old modules through this library, change the names. If you use mishka_developer_tools itself, add it to your own deps. See the helper modules documentation.
  5. The dependency requirements changed. req is ~> 0.6.1, up from ~> 0.5.17. guarded_struct is ~> 0.1.0-beta.8, up from ~> 0.0.5. libgraph ~> 0.16 is new, and mishka_developer_tools is gone. What to do: raise req and guarded_struct in your own mix.exs if you pin them.

Added

  • A downloader built on Req for the :url and :github_* types, and an optional SHA-256 checksum: that is checked before extraction.
  • Replay on boot: Installer.CompileHandler loads and starts the installed libraries again from the disc and from Mnesia, also in the embedded code-loading mode of a release.
  • Plugin health checks: the optional health_check/0 callback, and Hook.plugin_health/2, Hook.event_health/2 and Hook.health/1.
  • The optional on_dependency_error/1 callback, and Hook.profile/2, which measures the run time of each plugin in a compiled chain.
  • Dynamic cluster membership in the Mnesia store: :cluster_nodes as :auto or a list of nodes, disc copies, and handling of nodes that go up and down. See the deploy to production documentation.
  • A configurable :extensions_path and Mnesia directory, so they can sit on a volume.
  • The helper modules Helper.Extra, Helper.UUID and QueueAssistant.

Changed

  • The plugin chain of an event is unrolled when the event module is compiled, so each call is a direct call. The contract is the same: {:reply, state} continues and {:reply, :halt, state} halts. The map from an event name to its module is kept in :persistent_term.
  • The use MishkaInstaller.Event.Hook macro only delegates, and the library loads the modules of an installed application explicitly, so it works in a release.

Fixed

  • A held plugin never started on its own when the dependency it waited for became active, because the lifecycle broadcasts carried no data.
  • An unknown aborted-tuple reason sent Helper.MnesiaAssistant.Error.error_description/2 into infinite recursion.
  • Path traversal: an app or version whose package path leaves the extensions directory is refused before anything is removed, renamed or extracted, on install and on uninstall.
  • Tar-slip: an archive member with an absolute path or a .. path is refused before extraction.
  • Compiler warnings from @impl, deprecations and generated code.

0.1.5 and 0.1.4

0.1.5 was released on 2026-04-19. 0.1.4 was tagged in git on the same day, but it was not published to Hex. Neither changed the code of the library. These releases have no CHANGELOG entry.

Changed

  • 0.1.4 raised the dependency requirements to phoenix_pubsub ~> 2.2, req ~> 0.5.17, plug ~> 1.19 and telemetry ~> 1.4.
  • 0.1.5 raised guarded_struct to ~> 0.0.5 and mishka_developer_tools to ~> 0.1.10.

0.1.3

Released on 2025-09-13. This release has no CHANGELOG entry.

When you upgrade

  • Remove the code that matches {:error, :undefined_function_error}. An event with no plugins hands your data back. See the call an event documentation.

Changed

  • mishka_developer_tools moved to ~> 0.1.9.

0.1.2

Released on 2025-09-12. This release has no CHANGELOG entry.

Fixed

  • A problem with Elixir 1.18. The Hook macro now keeps only the basic, serializable values of Mix.Project.config(): :app, :version, :elixir, :deps, :name, :source_url, :homepage_url and :description.

Changed

  • Dependency requirements: req ~> 0.5.15, plug ~> 1.18.1 and guarded_struct ~> 0.0.4.

0.1.1

Released on 2024-11-25.

Added

  • Callbacks and a behaviour for the MishkaInstaller.Event.Hook macro.
  • The guarded_struct dependency.

Changed

  • Dependencies updated and tested with Elixir 1.17.2.

0.1.0

Released on 2024-07-03. The whole project was rewritten on Erlang's built-in database, Mnesia. The CHANGELOG states that earlier versions are not supported, so there is no upgrade path from the 0.0 releases. Start from the get started documentation.