Skip to content

[Bug] Reconcile stale batch-update records when an installed script changes #1777

Description

@cyfung1031

Summary

When an installed script is actually changed after a Batch Update check has already produced a cached result, reconcile that script's existing batch-update record immediately from local data.

This should cover changes such as:

  • updating the script from a separate install/update page;
  • editing or pasting new script code in the editor and saving it;
  • importing/replacing the script through another install path;
  • any other successful path that changes the installed script code and/or author metadata.

The reconciliation should not perform another network update check. The Batch Update cache already contains the previously fetched target code and metadata, so the affected entry can be refreshed locally.

Non-script-content changes such as:

  • reordering scripts;
  • changing GM values / script storage;
  • other data that does not change the installed script itself;

must not trigger this reconciliation.


Background

The Batch Update service keeps an in-memory record of the most recent update check.

For each pending update, that record contains data such as:

  • the local script snapshot at check time;
  • the local code at check time;
  • the fetched target code;
  • target metadata/version;
  • code-similarity result;
  • new-@connect information;
  • site information.

The current implementation already has two important protections:

  1. During an update check, before the result is committed, the service re-reads the current installed script and avoids writing a stale pending-update row if the script changed while the network check was running.
  2. When an update is performed through the Batch Update action itself, a successful item is removed from the batch-update cache.

Those protections are correct, but they leave another lifecycle gap:

the update check has already finished, the batch record is cached, and then the script is changed through some other path.

In that case, the existing batch record can continue describing the old local script state.


Example

  1. Script A is installed at 1.0.0.
  2. Batch Update checks the network and finds 2.0.0.
  3. The cached record now describes:
    • local: 1.0.0
    • remote: 2.0.0
  4. The user does not update from the Batch Update page.
  5. Instead, the user:
    • opens the separate update/install page and installs 2.0.0; or
    • manually pastes/saves code that is already 2.0.0.
  6. The Batch Update record still contains the previous 1.0.0 → 2.0.0 snapshot.

The user can therefore return to the Batch Update page and still see an update that is no longer applicable, or see risk/version information calculated against code that is no longer installed.

The page can eventually detect some stale situations when the user tries to act on the record, but that is too late. The record should stay coherent when the underlying script changes.


Why this should be local reconciliation, not another network check

The necessary remote-side data is already available in the batch record.

A pending record already contains the fetched target:

  • newCode;
  • newMeta;
  • target version;
  • other derived update information.

After the local script changes, the system only needs to compare the new local state against that cached target.

No additional fetch is required.

This has several advantages:

  • immediate consistency;
  • no unnecessary traffic;
  • no update-check rate/latency cost;
  • no change to the global "last checked" timestamp;
  • no surprise background network operation merely because the user edited a script.

Desired behavior

After a successful mutation that changes an installed script, check whether the Batch Update cache contains an entry with the same uuid.

If there is no cached entry, do nothing.

If there is a cached entry, reconcile only that entry against the newly stored local script/code.

Case A — cached target is no longer an update

For example:

  • cached target = 2.0.0;
  • installed script changed from 1.0.0 to 2.0.0 or newer.

The old pending-update record should no longer be shown.

The implementation can either remove the entry or convert it to the repository's existing non-update representation, whichever better preserves current totalChecked semantics.

The important user-visible result is:

the stale 1.0.0 → 2.0.0 update must disappear without another network check.

Case B — the script changed locally, but the cached target is still newer

Example:

  • cached check originally saw 1.0.0 → 3.0.0;
  • user manually edits/replaces the local script and it is now 2.0.0;
  • cached target remains 3.0.0.

The pending update is still valid, but the cached local side is stale.

Refresh the entry using the new local state:

  • current script snapshot;
  • current local code;
  • old/current version;
  • code similarity against the already cached target code;
  • new-@connect difference against current local metadata;
  • other locally derived fields that depend on the old/current script.

Do not re-fetch the target.

Case C — local code changes without a version change

A user can manually edit/paste code while leaving @version unchanged.

This is still a real script change.

The cached record must not continue using the previous local code for similarity/risk calculations merely because the version string stayed the same.

Therefore reconciliation must not be based only on:

oldVersion !== newVersion

The trigger should be a successful mutation of the actual installed script/code, and the cached entry should be recomputed from the newly persisted state.


What should trigger reconciliation

The cleanest boundary is the successful path that commits a new/replaced installed script.

Current installation/update flows already converge through the script installation service for many cases, including normal install/update and editor save paths.

Reconciliation should happen only after the new script state has successfully persisted.

Relevant examples include:

  • update/install page installs a new version;
  • silent update;
  • editor save after changing/pasting code;
  • import/restore that replaces the active script;
  • VS Code or other supported external update path if it ultimately commits through the same install/update service.

Centralizing this at the actual script-write boundary is preferable to adding Batch Update-specific calls in every UI.


What must NOT trigger reconciliation

This issue is specifically about the cached snapshot becoming stale because the installed script itself changed.

The following should not cause a Batch Update record refresh by themselves:

Change Reconcile batch record? Reason
Script code changed Yes Cached local code/risk snapshot is stale
Author metadata changed through script replacement/save Yes Cached update comparison may be stale
Installed version changed Yes Cached version relation may be stale
Script reordered No Update content did not change
GM value / storage changed No Values are runtime/user data, not update content
UserConfig value changed No, unless it rewrites the actual installed script Does not change fetched update comparison
Last-run/runtime status changed No Not part of update content
Check timestamp changed No Does not change the script being compared
UI-only selection/filter state changed No No underlying script mutation

Enable/disable and user-only execution-scope overrides should also not force an expensive code-comparison refresh unless they are intentionally part of the Batch Update record semantics. The core trigger should remain a real persisted script/code replacement.


Suggested implementation shape

Introduce a small, explicit reconciliation operation owned by the update-check cache, for example conceptually:

reconcileCachedScript(uuid, currentScript, currentCode)

or an equivalent method on ScriptUpdateCheck.

The exact API is flexible, but it should have these properties:

  1. Find only the matching cached entry.
  2. Return immediately if there is no current cache or no matching entry.
  3. Use the already cached target code/metadata.
  4. Re-read/use the newly persisted local script/code.
  5. Recompute only the affected entry.
  6. Regenerate the lightweight delivery cache via the existing cache serialization path.
  7. Notify open Batch Update views with the existing refreshRecord mechanism when the visible record actually changed.
  8. Perform no network request.

This keeps cache-coherence logic in one place instead of teaching each caller how TBatchUpdateRecord is derived.


Ordering / transaction consideration

Reconciliation must happen after the script mutation succeeds.

Do not update the batch cache before the metadata/code write has committed.

Otherwise a failed install/save could make the Batch Update cache claim the new local state exists when storage still contains the old script.

Preferred ordering:

persist script metadata/code successfully
        ↓
perform normal install/update side effects
        ↓
reconcile matching Batch Update cache entry
        ↓
publish refreshRecord if the cached representation changed

The reconciliation is cache maintenance. Failure to update an in-memory cache should not corrupt or roll back an otherwise successfully installed script.


Interaction with an update check that is currently running

There are two related but different stale-record problems.

Already handled

When a network check is still running and the script changes before that check commits its result, current code re-reads the installed script before writing the final Batch Update record. This prevents the old check snapshot from resurrecting an update for a script whose version changed during the check.

That behavior should remain.

This issue

This issue covers:

network check completes
    ↓
batch cache exists
    ↓
script changes later through another path
    ↓
existing cache entry must be reconciled

The two mechanisms should complement each other.

If a script changes while an update check is concurrently constructing a new cache, final commit ordering must still ensure that the resulting cache reflects the current installed script rather than an older snapshot.


Do not change the meaning of “Check for updates”

Local reconciliation should not pretend that a new network check occurred.

Therefore it should not:

  • advance the global Batch Update checktime;
  • show “checked just now” merely because a local script was saved;
  • replace the cached target with a newly fetched target;
  • update records for unrelated scripts.

The remote information remains from the previous check; only the cached local side is being brought up to date.


UI behavior

If the Batch Update page is already open, it should update naturally through the existing record-refresh mechanism.

Examples:

Script became up to date elsewhere

The stale row should disappear without requiring the user to press “Check for updates”.

Script changed but still has an update available

The row should remain, but its:

  • current version;
  • similarity/risk;
  • new-@connect assessment;
  • other old-vs-new information

should reflect the newly installed local script.

The page should not flash into a full loading state just because one cached entry was reconciled.


Performance considerations

This should be a bounded local operation.

For one changed script:

  • locate one cache entry;
  • read/use one local script/code pair;
  • compare it with the already cached target;
  • recompute derived information for that entry;
  • regenerate delivery data only if needed.

It should not:

  • iterate/fetch every installed script from the network;
  • start checkScriptUpdate();
  • fetch checkUpdateUrl/downloadUrl;
  • recalculate unrelated entries.

The common case where no batch-update cache exists should be essentially a no-op.


Testing

Please add regression tests around the service-level behavior.

Required cases

  • Cached 1.0.0 → 2.0.0; install 2.0.0 through normal installScript; stale pending entry disappears.
  • Same scenario through the separate update/install-page path.
  • Same scenario through editor/manual-code save.
  • Cached 1.0.0 → 3.0.0; local script becomes 2.0.0; row remains but local snapshot/version/risk is refreshed against cached 3.0.0.
  • Local code changes while version stays the same; similarity/risk is recalculated.
  • Local metadata changes relevant to @connect; withNewConnect/new-connect data is recalculated.
  • No matching cache entry: script update succeeds with no Batch Update side effect.
  • No Batch Update cache: script update succeeds with no extra work/error.
  • Script reorder does not invoke cache reconciliation.
  • GM value/storage update does not invoke cache reconciliation.
  • Reconciliation performs no network fetch.
  • Global batch-update checktime is unchanged.
  • An open Batch Update page receives refreshRecord only when the cached visible record changes.
  • Failed script installation/save does not mutate the cached record.

Concurrency regression

  • A script changed while a network check is in flight still cannot be reintroduced as a stale pending update when that check finishes.

Acceptance criteria

  • Any successful change to an installed script's actual code/author metadata reconciles a matching cached Batch Update entry.
  • If the installed script is already at or beyond the cached target version, the stale pending update disappears.
  • If the cached target is still newer, the entry remains but uses the new local script/code as its comparison baseline.
  • Same-version manual code edits are handled; reconciliation is not version-change-only.
  • No network request is made for reconciliation.
  • The prior check timestamp remains unchanged.
  • Unrelated cached entries are untouched.
  • Sorting changes do not trigger reconciliation.
  • GM value / script-storage changes do not trigger reconciliation.
  • An already-open Batch Update page reflects the reconciled entry through the existing refresh flow.
  • Existing in-flight-check stale-snapshot protection continues to work.

Design intent

A Batch Update record is a cached comparison between:

the installed script at the time of comparison and the already-fetched candidate update.

Once the installed script changes, the old local half of that comparison is no longer trustworthy.

The cache should therefore follow actual script mutations, using the target data it already has, instead of waiting for the user to discover the stale record or forcing another network check.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions