Skip to content

[BUG] Permanent mute rules (expires_at = NULL) disappear after application restart #938

@alimic

Description

@alimic

Component

Lite

Performance Monitor Version

2.10

SQL Server Version

SQL Server 2019

Windows Version

Windows 11 25H2

Describe the Bug

Mute rules created via the GUI with Expires After: Never (permanent) are silently lost across application restarts. The
same behavior reproduces consistently — three independent samples observed over a 3-day window, each verified before and
after restart via the MCP server's get_mute_rules tool.

This affects any user who relies on mute rules to suppress recurring alerts: rules must be re-created after every restart,
even though the UI lets the user pick "Never" as the expiration.

Steps to Reproduce

  1. Open PerformanceMonitorLite GUI.
  2. Right-click an alert → Add Mute Rule (or use the Mute Rules dialog).
  3. In Edit Mute Rule, fill in match criteria (e.g. server, metric, database) and select Expires After: Never (permanent).
  4. Save the rule.
  5. Verify the rule exists — either via the GUI's mute-rule list, or via the MCP get_mute_rules tool. Confirm
    expires_at_utc: null.
  6. Close the application (Exit from system tray) and reopen it.
  7. Query the mute rules again — the permanent rule is gone.

Expected Behavior

A mute rule with expires_at_utc = NULL should persist indefinitely, including across restarts. This is what the "Never
(permanent)" UI option implies.

Actual Behavior

The rule is missing after restart. This happens regardless of how recently the rule was created (we observed it for rules
that were minutes old, hours old, and 2 days old — all permanent, all gone after restart).

Error Messages / Log Output

## Evidence — 3 Independent Samples

  All times below are Asia/Taipei (UTC+8). All `get_mute_rules` queries were issued via the bundled MCP server
  (`http://localhost:5151/`).

  | # | Rule | Created (TST) | Verified Existed (TST) | Restart(s) Between | Verified Missing (TST) |
  |---|------|---------------|------------------------|---------------------|------------------------|
  | 1 | server=`192.168.122.166`, metric=`Blocking Detected`, database≈`tempdb`, expires=NULL | 2026-05-04 13:25:49 |
  2026-05-04 ~14:00 (`get_mute_rules` returned the rule) | At least 3 restarts | 2026-05-06 14:46 (`get_mute_rules` returned
  `total_count: 0` for this rule) |
  | 2 | server=`192.168.123.200`, metric=`Long-Running Job`, job≈`GOP2_DAILY`, expires=NULL | 2026-05-06 14:53:04 | 2026-05-06
  ~15:00 (`get_mute_rules` returned the rule) | 1 restart (associated with binary directory move from D:\ to C:\) | 2026-05-06
  18:09 (`get_mute_rules` returned `total_count: 0`) |
  | 3 | (new permanent rule, GUI screenshot taken before restart) | 2026-05-07 ~01:00 | Verified in GUI | 1 restart | Missing
  immediately after restart |

  In every case, `expires_at_utc` was explicitly `null` (verified in `get_mute_rules` JSON output for samples 1 and 2). These
  were not 24-hour-default rules expiring naturally.

  ## Hypothesis

  Each restart appears to perform a heavy `monitor.duckdb` operation. We observed:

  - 2026-05-06 14:24 restart: `monitor.duckdb` shrank from 562 MB → 143 MB (-75%)
  - 2026-05-06 18:07 restart (after binary move): shrank to 55 MB (-91% from peak)

  This is consistent with a vacuum, schema rebuild, or table re-creation step on startup. The most plausible mechanism is that
  the startup path (re)creates the `mute_rules` table or its parent without re-loading existing rows from a persistent source.

  A simpler alternative — that mute rules are kept in memory and only lazily flushed — also fits the symptom but doesn't
  explain rules surviving for hours/days before restart.

  ## Environment

  - **PerformanceMonitorLite**: built from binaries dated 2026-04-29 to 2026-05-05 (file modification times in the install dir;
   I don't see a version string surfaced in the GUI — please advise where to find one)
  - **Bundled MCP server**: enabled, port 5151
  - **OS**: Windows 11 Pro 10.0.26200
  - **Install paths observed**:
    - Original: `D:\PerformanceMonitorLite\` (HDD)
    - After move: `C:\PerformanceMonitorLite\` (SSD)
    - Behavior is identical at both paths.
  - **Data dir**: `%LOCALAPPDATA%\PerformanceMonitorLite\` containing `monitor.duckdb`, `archive\`, `logs\`, `config\`,
  `alert_state.json`
  - **Monitored servers**: 8 SQL Server instances (mix of SQL 2008 R2, 2016, 2019, 2022)

Screenshots

Image Image

Additional Context

Additional Notes

  • settings.json has mute_rule_default_expiration: "24 hours", but this only sets the GUI default — the bug reproduces
    even when the user explicitly overrides this in the dialog.
  • alert_state.json contains SilencedServers and AcknowledgedAlerts but not the mute rules themselves, so they are not
    stored as JSON.
  • I have GUI screenshots of sample Add check for updates feature #3 from before restart if useful.

Workaround Currently in Use

None — every restart requires manually re-creating any mute rules. A possible workaround on the user side is backing up
monitor.duckdb while the daemon is stopped and restoring after a known restart, but this is fragile and loses any data
collected between backup and restore.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions