Skip to content

Modules::refresh() snapshot fast-path can leave a module upgrade stuck on stale info until a second refresh #2289

Description

@adrianbj

Summary

After upgrading a module (e.g. via the ProcessWire Upgrade module), the admin reports the upgrade succeeded but the module's info (version, etc.) stays at the old value. It only corrects itself on a later, separate refresh — so the upgrade appears to "not take" on the first try.

Cause

Commit 5bb7f4f1 ("Add mtime snapshot fast-path to Modules::refresh()") added an optimization: ___refresh() builds a [mtime, size] snapshot of every module file and returns early — skipping the module-info rebuild — when the snapshot matches the previously cached one.

The problem is the ordering of events within a single module-upgrade request:

  1. The upgrade replaces the module's files on disk, then calls $modules->refresh() in that same request (ModulesDownloader::unzipModule()).
  2. For an update of an already-loaded module, the old class is already in memory, so the info rebuilt during this request can still reflect the old code.
  3. That same refresh() then saves a file snapshot that already matches the new files on disk.
  4. On the next request, refresh() sees the snapshot matches → takes the early return → never rebuilds → the stale info persists.

This is why it takes a second attempt/refresh for the upgrade to appear.

Reproduction

  1. Install a module.
  2. In one request: load the module, replace its files with a newer version, then call $modules->refresh() (this is what the upgrade flow does).
  3. In subsequent requests, $modules->getModuleInfo() keeps reporting the old version; even a fresh-process refresh() returns early and never rebuilds.

Verified against a real install with a throwaway module: the reported version stayed stale across every subsequent request until the snapshot cache was manually cleared.

Expected

A module upgrade should be reflected on the next refresh, not require a second manual refresh.

Affected

  • wire/core/Modules/Modules.php___refresh()
  • Introduced in 5bb7f4f1 (2026-06-25).

A fix + regression test is proposed in processwire/processwire PR (linked below).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions