Skip to content

[pull] main from erikdarlingdata:main#12

Merged
pull[bot] merged 54 commits into
ehtick:mainfrom
erikdarlingdata:main
May 20, 2026
Merged

[pull] main from erikdarlingdata:main#12
pull[bot] merged 54 commits into
ehtick:mainfrom
erikdarlingdata:main

Conversation

@pull
Copy link
Copy Markdown

@pull pull Bot commented May 20, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

erikdarlingdata and others added 30 commits May 4, 2026 20:07
GitHub will force JS actions to Node 24 on June 2nd, 2026, and remove
Node 20 from runners on September 16th, 2026. Bumps the four actions
called out in the runner deprecation warning:

- actions/checkout v4 → v5
- actions/setup-dotnet v4 → v5
- actions/upload-artifact v4 → v6
- signpath/github-action-submit-signing-request v1 → v2

Matches the bump PerformanceStudio just made for the same reason.
dorny/paths-filter@v3 not in the warning, left as-is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI: bump actions to Node 24-compatible majors
PR #905 wired config.collector_database_exclusions into the 8 collectors
that iterate sys.databases, but missed default_trace_collector and
trace_analysis_collector because they filter trace events by
DatabaseID/DatabaseName rather than looping sys.databases.

- 29_collect_default_trace.sql: NOT EXISTS against
  config.collector_database_exclusions joined to sys.databases to
  translate the exclusion list (by name) into ids matching ft.DatabaseID.
- 31_collect_trace_analysis.sql: NOT EXISTS directly on
  trc.DatabaseName (trace exposes the name here).

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

Honor collector_database_exclusions in default trace and trace analysis collectors (#887 follow-up)
The in-memory DuckDB connections used for parquet compaction had a 4 GB
memory_limit pragma but no temp_directory, so the cap acted as a hard
wall — DuckDB had nowhere to spill and OOM'd the moment it was hit.

Co-locate the spill dir with the archive folder so the writes land on
the same volume as the parquet files. Verified end-to-end: 4-server
HammerDB load, second 512 MB reset triggered ArchiveAllAndResetAsync,
all 21 groups went through the multi-file pair-merge path with two
sources each, completed in ~3.5s with no OOM.

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

Lite: fix compaction OOM by setting DuckDB temp_directory (#933)
#936)

Servers that crossed 2.4 -> 2.5 before PR #828's USE-statement fix shipped
in v2.7.0 ran the widen script as a no-op against master and advanced
their installer_version past 2.5. The upgrade filter (ToVersion > current)
then permanently skipped the now-fixed script, leaving sql_server_version
and sql_server_edition stuck at nvarchar(255) -- which truncates SQL 2022+
@@Version strings (~260 chars) on every install attempt.

Re-applies the same idempotent widen in 2.10.0-to-2.11.0 so any server
upgrading to the next release picks it up. The IF EXISTS guards on
max_length = 510 mean already-widened servers skip both ALTERs.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
)

PR #919 / #921 / #922 addressed the WPF Popup wedge but missed the
underlying bug: MemoryContent, ResourceMetricsContent, and
QueryPerformanceContent all called DisposeChartHelpers() from their
per-control Unloaded event handler. WPF fires Unloaded on every
TabControl tab switch, not just on destruction, so switching away from
the Memory tab unsubscribed every chart's MouseMove handlers — which
were never re-registered when the user came back. The popup-wedge fixes
were running inside helpers that had already torn themselves down.

ServerTab_Unloaded had the same comment warning future maintainers,
but the inner UserControls didn't follow that rule.

Changes:
- MemoryContent / ResourceMetricsContent / QueryPerformanceContent:
  drop DisposeChartHelpers() (and ThemeManager unsubscribe) from the
  tab-switch Unloaded handler. Move the ThemeManager unsubscribe into
  DisposeChartHelpers() so it runs only on real cleanup.
- SystemEventsContent: same pattern — add a DisposeChartHelpers()
  method that disposes the 19 hover helpers, unsubscribes filter-popup
  events, and unsubscribes ThemeManager. Empty out OnUnloaded.
- ServerTab.CleanupOnClose: add SystemEventsContent.DisposeChartHelpers()
  to the cleanup chain (its hovers leaked on tab close before this).

Final disposal still happens correctly via ServerTab.CleanupOnClose,
which only fires when a server tab is actually removed. Lite is
unaffected — its Unloaded handler never disposed hovers.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When monitor.duckdb exceeds 512 MB, ArchiveAllAndResetAsync deletes the
file and reinitializes empty tables. config_mute_rules was not in
ArchivableTables, so all mute rules — including permanent rules with
expires_at_utc = NULL — were silently lost.

Export config_mute_rules and dismissed_archive_alerts to a temp Parquet
dir before the reset and re-import after. Parquet roundtrip keeps this
schema-agnostic. On restore failure, the temp dir is retained for
manual recovery.

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

Fix #938 — preserve mute rules across size-triggered DB reset
#935 added temp_directory so DuckDB could spill, but on wider workloads
the working set still blew past the 4 GB cap before spill caught up
(reporter saw OOM at 3.7 GiB compacting 15 query_snapshots files).
Three knobs combined to feed that:

- memory_limit = 4 GB was too high — DuckDB held off spilling until late
- threads defaulted to N cores, multiplying per-thread row-group buffers
- ROW_GROUP_SIZE 122880 buffered up to 122k wide-VARCHAR rows per group

Drop memory_limit to 1 GB, cap threads to 2, and shrink ROW_GROUP_SIZE
to 8192. On 1.7 M rows of real query_stats data this drops peak working
set from 1236 MB → 166 MB (87% reduction) at a 31% wall-time cost.
Memory now plateaus instead of growing with row count, which is the
load-bearing change for issue #933.

Adds tools/CompactionRepro — a standalone reproducer that splits a real
monthly parquet file into N per-cycle-shaped chunks and runs the same
pair-merge logic with the tuning knobs exposed on the command line.

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

Fix #933 — bound compaction memory so wide-row tables don't OOM
Replaces the standard non-interactive Windows toast for the seven fired
alerts (CPU, Blocking, Deadlocks, Poison Wait, Long-Running Query,
TempDB Space, Long-Running Job) with a custom WPF balloon that has
"Snooze 15m / 1h / 4h" + "Dismiss" buttons. Snoozing creates a temporary
mute rule scoped to ServerName + MetricName with the chosen expiration,
which the existing alert-fire path already honors for both email and
Teams/Slack webhook delivery.

Status notifications (Online/Offline + 7 Cleared/Resolved) keep using
the standard non-interactive balloon since there's nothing to snooze.

Addresses the firefighting workflow described in #943.

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

Fix #944 — one-click snooze from alert tray popup
Adds a one-line discoverability footer to alert delivery channels other
than the desktop popup, since neither email nor webhooks can round-trip
a click back to a desktop process:

  "To silence this alert: open Performance Monitor Lite →
   Settings → Manage Mute Rules"

Placed in the email HTML footer (under the cooldown line), the
plain-text body tail, the Teams MessageCard (a small section after the
facts), and the Slack context block at the bottom of the message.
Suppressed on test deliveries so the test card stays minimal.

Pairs with the snooze popup from #946 to close out #944.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…in-email-webhook

Fix #944 — snooze hint in email and Teams/Slack payloads
Lite went from 190 warnings to 0, Dashboard from 3 to 0, Installer was
already clean. Total fixes:

- Add repo-root .editorconfig suppressing CA1873 ("potentially expensive
  logging") with rationale. This rule fires on every non-literal
  argument to a logging method including trivial field accesses, which
  accounts for ~97% of Lite's warning count and is pure noise — the
  cost the rule warns about is consistently negligible. Proper fix is
  migrating to LoggerMessage source generators, tracked separately.

- Lite/Controls/CorrelatedTimelineLanesControl.xaml.cs: guard against
  null .Result on completed comparison-data tasks before passing to
  .Select() (CS8604 ×2). The author already wrote `?.Count ?? 0` for
  the same fields elsewhere, so null was anticipated; the .Select calls
  just missed the guard.

- Lite/Mcp/McpAnalysisTools.cs: tighten FormatBaselineContext return
  type from object? to Dictionary<string, object>?, removing the
  unnecessary boxing (CA1859).

- Dashboard/ServerTab.Plans.cs: discard the bool returns from three
  int.TryParse calls with `_ =`, making the "ignore failure, use
  default 0" intent explicit (CA1806 ×3). Behavior unchanged —
  TryParse already sets the out param to default on failure.

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

Fix #945 — clean up build warnings (Lite 190→0, Dashboard 3→0)
The standalone InstallerGui was retired in #755 — its functionality
folded into the Dashboard's Add Server dialog and the new Installer.Core
shared library. The .sln no longer references it, but the source
directory was left on disk.

Cleaning that up: removes the dead source, eliminates a stray CS8604
warning that was only surfaced when building the project directly, and
keeps the repo lean. CHANGELOG already documented the retirement; no
docs changes needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Delete retired InstallerGui project directory
The Blocking, Memory, and IO branches in the comparison overlay
section of CorrelatedTimelineLanesControl were missing the
`&& Result != null` guard the CPU and Wait branches already had.
Apply uniformly so a null result from `_dataService.Get*TrendAsync`
can't NRE on the subsequent .Select / .GroupBy.

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

Add nullability guards to comparison overlay tasks
#942 lowered the cap to 1 GB on the theory that a tight memory_limit plus
temp_directory would force DuckDB to spill earlier and keep peak working
set down. That validation ran against query_stats (narrow, ~1.7M rows) and
showed peak 1236 MB → 166 MB. The reporter's actual failure is on
query_snapshots, which carries query_text + query_plan + live_query_plan
per row. With the 1 GB cap, the nightly logs show OOM at "906/953 MiB
used" before any merge progress.

The standalone reproducer (tools/CompactionRepro) confirms the cause:
parquet COPY in DuckDB v1.5.2 makes allocations that bypass the buffer
manager and can't be spilled. The cap acts as a hard ceiling for those,
not a spill trigger. Spill on disk = 0 MB across every configuration we
tested (memory_limit 1/2/4 GB, accumulator vs tournament merge, threads
1 vs 2, :memory: vs file-backed DB). The same failure reproduces in
standalone DuckDB CLI v1.5.2, so it's an engine issue — see upstream
issues duckdb#16482 and discussion#10084.

DuckDB's own OOM guide explicitly warns about this case and recommends
memory_limit at 50-60% of system RAM, not a tight cap. 4 GB sits well
inside that range for typical workstation/server hosts and leaves real
headroom on top of the un-spillable allocations.

Reporter's actual file sizes (15-25 chunks of 2-6 MB plus a 35-45 MB
monthly file per group) are well below the level where 4 GB has any
trouble. The reproducer confirms 4 GB succeeds on a synthetic
query_snapshots-shaped dataset of ~1.5 GB with peak working set of
~400 MB; the reporter's data is ~143 MB at worst.

Also updates the stale comment about spilling — temp_directory was set
per #935 but the buffer-manager-bypassing allocations don't use it. The
comment now describes what actually happens.

The tools/CompactionRepro changes add --strategy {accumulator|tournament},
--db-mode {memory|file}, --merge-files, --synthetic data generation, and
--cycles for leak testing. These are kept so a future regression in this
area can be reproduced and diagnosed quickly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
config.data_retention only purged rows from collect.* tables, leaving
the .trc files written by collect.trace_management_collector to
accumulate forever in the SQL Server error log directory. Reporter saw
~7 GB / 106 files going back 2 months on a busy server.

Adds a trace file cleanup block to config.data_retention that:
- reads retention_days from config.collection_schedule for
  trace_management_collector (30-day fallback), honoring the same
  @retention_days override semantics as the table cleanup above it
- derives the error log directory via SERVERPROPERTY('ErrorLogFileName')
  the same way collect.trace_management_collector does
- pre-checks via xp_dirtree to skip xp_delete_file when no matching
  files exist, suppressing the "Msg 22049" noise xp_delete_file prints
  on empty matches
- calls master.dbo.xp_delete_file to delete files older than the cutoff
  (active trace file stays locked open, so it survives)
- TRY/CATCH treats Msg 22049 as benign (no files needed cleanup) on
  older SQL versions where it surfaces as a catchable error
- logs a separate config.collection_log row so trace cleanup is auditable

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ion-memory-limit

Fix #933: raise compaction memory_limit to 4 GB
…leanup

Fix #951 — clean up Monitor_LongQueries_*.trc files in data_retention
CompactParquetFiles detected CompactionExcludeColumns once, globally,
across the union schema of every source file in a group. It then applied
that "* EXCLUDE (col)" clause to each pair in the pairwise merge.

query_plan_text was added to query_store_stats in migration v13
(2026-02-23). A reporter's archive contains both pre-v13 files (no
column) and post-v13 files (column present). The global DESCRIBE saw
the column in the newer files, so every merge step ran with
"* EXCLUDE (query_plan_text)" — including the steps that merged two
pre-v13 files, which fail with:

  Binder Error: Column "query_plan_text" in EXCLUDE list not found
  in FROM clause

Extract the schema detection into BuildSelectClause(table, paths) and
call it per merge-set instead of once globally — with the actual pair
in the pairwise path, and with all sources in the small-group path. A
pair that doesn't carry an exclude-column now merges with a plain "*".

Verified against DuckDB CLI v1.5.2: DESCRIBE of an [old, old] pair
correctly omits the column, and "* EXCLUDE (query_plan_text)" on that
pair reproduces the reporter's exact Binder Error. Cost is one extra
DESCRIBE per merge step — parquet footer reads, not data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#933's titled complaint is "Memory usage on client": Lite holds ~2.7-2.9 GB
after 10 minutes with 4 servers. The compaction OOMs everyone has been
chasing in this thread are a downstream symptom — by the time compaction
runs the app already holds 2.7 GB, leaving little headroom on the reporter's
16 GB / ~1.6 GB-free machine.

Root cause: the main DuckDB ConnectionString set no memory_limit, so the
buffer pool ran at the DuckDB default of 80% of system RAM (~12.8 GB on a
16 GB box). With archive parquet files accumulating on disk, every UI query
over an archive view caches pages and the buffer pool grows freely.

The fix has to navigate one wrinkle: parquet COPY in DuckDB v1.5.2 hits a
buffer-manager-bypass pre-reservation that needs ~2-4 GB headroom. Capping
the main connection at 1 GB statically would break ExportToParquet and the
two COPY paths in ArchiveAllAndResetAsync. So:

- ConnectionString: memory_limit=1GB (caps resting buffer pool — addresses
  the actual complaint by stopping the archive-page cache from growing
  unbounded).
- Around each parquet COPY on the main connection: SET memory_limit='4GB',
  run the COPY, SET back to '1GB'. Factored into a WithRaisedCopyMemoryLimit
  helper so the three call sites stay consistent (ExportToParquet, and the
  two COPYs in ArchiveAllAndResetAsync).
- Compaction connections (separate :memory: instances) keep their 4 GB cap
  from #952.

Verified against DuckDB CLI v1.5.2 with synthetic query_snapshots-shaped
data:
- COPY table→parquet at 256MB/512MB/1GB: OOMs (pre-reservation, matches the
  read_parquet→parquet path we saw in #952 testing).
- COPY table→parquet at 2GB/4GB: succeeds, peak RSS well under cap.
- INSERT (Appender) and SELECT (including GROUP BY across 11k rows) work
  fine at 256MB cap — confirms collectors and UI queries don't have the
  pre-reservation behavior and aren't affected by the resting cap.

Tradeoff: the resting cap forces buffer-pool eviction of cached archive
parquet pages. Long-range historical UI queries that re-scan many parquet
files will do more disk I/O. Live/recent-data queries against the hot DB
are unaffected (hot DB is small enough to fit in 1 GB easily).

Plus the per-merge-step BuildSelectClause from the previous commit fixes
the separate query_store_stats Binder Error on archives that span the
v13 schema change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…inder-and-adaptive-memory

Fix #933: cap main DuckDB memory_limit, per-pair compaction exclude detection
Lite has had no internal record of its own memory usage. Bug reporters
(see #933) had to read it off Task Manager, and we had no historical
trace for diagnosing growth patterns. After every collection cycle —
which is also after archival and retention run, so it captures the
quiescent state — log:

  Process memory: WS=XXX MB, Private=XXX MB, GC heap=XXX MB

WS is Working Set (what Task Manager shows). Private is private bytes
(unique to this process, the more honest "actual RAM cost" number).
GC heap is .NET-managed memory only — together with WS-Private this
splits managed vs native vs shared.

One INFO log line per minute. Three property reads — negligible
overhead. Errors swallowed at DEBUG level (don't ever break the
collection loop because we couldn't read memory stats).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
erikdarlingdata and others added 24 commits May 15, 2026 10:44
…ry-per-cycle

Log process memory at the end of each collection cycle
After PR #955 the main connection cap and Binder fix landed, but the
reporter's next nightly hit a different failure mode: query_snapshots
compaction OOM'd at "failed to pin block of size 102.9 MiB
(3.6 GiB/3.7 GiB used)" with a 72-file backlog. Not the pre-reservation
bug — DuckDB has legitimately consumed nearly the full 4 GB cap doing
real work. Wide-VARCHAR plan XML expands ~10x on read; merging 72 files
into a single COPY needs more than 4 GB in memory.

The existing pairwise merge accumulates: by step 70 of 72, the
accumulator is the merge of 71 files combined being read alongside one
more. Bounded if file count is bounded; unbounded otherwise.

This change caps a merge by total on-disk input bytes, not file count:

- BuildSizeBudgetedBatches greedily groups smallest-first-sorted paths
  into batches whose total bytes don't exceed MaxBatchInputBytes (200 MB).
- A group that fits in one batch keeps the existing
  YYYYMM_table.parquet output name — fully backward compatible.
- A group that needs multiple batches produces YYYYMM_table_ptNNN.parquet
  part files (numbered from 001). Archive views already glob
  *_table.parquet, so readers see all parts as one logical month.
- New regex case in the file-recognition pass recognizes _ptNNN suffixes
  so subsequent compactions round-trip part files correctly.
- MergeBatchToFile factors out the single-pass-vs-pairwise logic so each
  batch is a standalone call; the cleanup orchestration (delete
  originals, promote temps) runs once after all batches succeed.

For the reporter's specific 72-file backlog at ~5 MB each (~360 MB
total), this produces roughly two batches of ~36 files each. Memory
demand of each batch is half what the un-batched merge needed, well
within the 4 GB compaction cap.

Naturally adaptive: narrow tables (wait_stats, perfmon_stats) fit
hundreds of files per batch; wide tables (query_snapshots,
query_store_stats with plan XML) get fewer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump TargetFramework to net10.0 / net10.0-windows across all 7
projects, update CI workflows and build scripts, and refresh
Microsoft.Extensions.* / System.Text.Json / ModelContextProtocol /
Microsoft.NET.Test.Sdk to their current versions.

Removes the explicit System.Text.Json reference from Lite since
.NET 10 ships it in-runtime (NU1510). packages.lock.json files
regenerated via --force-evaluate; transitive dependency lists
shrank ~30% thanks to .NET 10 package pruning.

Build: 0 warnings, 0 errors. Lite.Tests: 260/260, Installer.Tests
fast filter: 27/27.

.NET 8 LTS support ends 2026-11-10; .NET 9 already ended 2026-05-12.
.NET 10 is LTS through 2028-11.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ize-budget-batching

Compact parquet in size-budgeted batches (#933 followup)
Tester feedback: after running the bare-ZIP "install" the app didn't
appear in Start Menu / Apps & Features, and a second DBA on the same
machine couldn't see the server list the first DBA had configured.

Installer:
- Drop the Dashboard/Lite portable ZIPs from CI and local build scripts.
  Velopack Setup.exe stays as the only Dashboard/Lite release artifact;
  its defaults already create Start Menu + Desktop shortcuts and register
  the app under HKCU\...\Uninstall (so it shows in Apps & Features).
- Keep the Installer ZIP (it bundles the CLI installer + SQL scripts —
  a different artifact).
- Update README Quick Start to direct users at Setup.exe with a note
  about shortcuts and Apps & Features registration.

Shared server list:
- Move servers.json from %APPDATA%/%LOCALAPPDATA% to
  %ProgramData%\PerformanceMonitor{Dashboard,Lite}\ so every Windows
  user on the machine sees the same server list.
- On first directory creation, grant Authenticated Users Modify with
  container+object inheritance so other users can edit the file.
- One-time migration: if a per-user servers.json exists and no shared
  one does, copy old -> new. The legacy file is left in place as a
  backup.
- Other config stays per-user — Lite settings.json, schedules,
  collector preferences, the DuckDB database, and the Parquet archive
  all remain in %LOCALAPPDATA% (collection data is single-instance and
  Velopack-managed).
- Credentials remain per-user in Windows Credential Manager: each DBA
  will need to re-enter SQL passwords on first connect. Windows Auth
  works without re-entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Setup.exe (Velopack) remains the README-promoted install path, but the
portable ZIPs are still produced in both the local build scripts and
the CI release pipeline (including the signing path) so advanced and
air-gapped users have an explicit fallback. The README change made in
the previous commit still steers end users at Setup.exe — only the
release artifacts and local build outputs change here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
servers.json now lives under %ProgramData% (shared across users); the
other Lite config files stay in %LOCALAPPDATA% per-user. Add a Location
column to the config table and a short note about the cross-user
behavior so the storage split matches the code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ared-server-list

Ship via Setup.exe only and share servers.json across users
Today's nightly build on main failed restoring Installer with NU1004:
'Microsoft.NET.ILLink.Tasks version has changed from [10.0.8, ) to
[10.0.7, )'. ILLink.Tasks is bundled with the SDK, so its version
tracks whatever SDK the runner image happens to ship. The lockfiles
were generated against SDK 10.0.204 (runtime 10.0.8); the runner now
ships SDK 10.0.107 (runtime 10.0.7), and `--locked-mode` refuses to
proceed against the mismatch.

global.json tells the SDK host (and actions/setup-dotnet) to use 10.0.204
or higher within the same major.minor — so CI installs the SDK that
matches the lockfile instead of inheriting whatever the runner image
came with. Local dev and CI now agree on the SDK version, and the only
way to bump it is to update global.json and regenerate lockfiles in the
same commit.

Verified locally: `dotnet --version` → 10.0.204, `dotnet restore
--locked-mode` succeeds.

Note: dev's nightly.yml is already on 10.0.x. main's still has the
.NET 8.0 leftover but that'll clear on the next dev → main release.

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

Pin .NET SDK to 10.0.204 via global.json
Bumps Dashboard, Lite, Installer, and Installer.Core to 2.11.0.
Drafts the v2.11.0 changelog entry covering the .NET 10 upgrade,
Setup.exe-only distribution, shared servers.json location, Lite
compaction memory tuning (#933), one-click snooze (#944), trace
collector exclusions (#887 follow-up), and the silent rewiden of
installation_history version columns for pre-v2.7.0 no-op upgrades
(#828). Stamps v2.10.0 with its actual release date (2026-05-04).

Tested:
- Build clean (0/0)
- 46 adversarial Installer.Tests pass
- Fresh install on sql2016, upgrades on sql2017/2019/2022/2025
  with data survival verified; idempotent re-run on sql2022
- Uninstall path verified (DB dropped, jobs + XE sessions removed)
- Cloud: Lite collecting from Azure SQL DB and AWS RDS for 30 min
  with zero errors; Dashboard installed PerformanceMonitor on RDS
  via Add Server flow (148 runs, 0 errors)
- New process-memory-per-cycle log lines observed in Lite

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Important section claimed portable ZIP artifacts were dropped and
that Setup.exe is the only Dashboard/Lite release artifact. Commit
13f7aef restored the portable ZIP build steps in both CI and local
build scripts, so the ZIPs still ship as a fallback — only the
README-promoted install path changed. The v2.11.0 release assets
include the *-Portable.zip files, confirming the original wording was
wrong.

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

Correct v2.11.0 changelog: portable ZIPs were not dropped
Correct v2.11.0 changelog wording
A documentation-only dev->main PR (e.g. a CHANGELOG correction) does
not bump the version, so check-version-bump failed and required an
admin override to merge. The workflow now detects whether any non-*.md
file changed via dorny/paths-filter and only runs the version
comparison when it did. Documentation-only PRs hit a "Skip notice"
step instead, so the job still completes and the required check
reports success rather than being skipped (which would leave it stuck
pending).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…or-doc-only-prs

Skip version-bump check for documentation-only PRs
Skip version-bump check for documentation-only PRs
A documentation-only change (CHANGELOG, README, etc.) triggered a full
~15-minute compile of Dashboard, Lite, and the Installer for no reason.
The paths-filter step now also computes a 'code' output (true when any
non-*.md file changed), and the build/test/publish steps are gated on
it. Documentation-only PRs and pushes run only checkout + the filter,
so the required 'build' check still reports — green — without the
workflow being skipped outright.

Release builds are unaffected: the filter step does not run on release
events, leaving 'code' empty, and the gate (code != 'false') treats an
empty value as "run".

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

Skip build/test/publish for documentation-only PRs
Skip build/test/publish for documentation-only PRs
@pull pull Bot locked and limited conversation to collaborators May 20, 2026
@pull pull Bot added the ⤵️ pull label May 20, 2026
@pull pull Bot merged commit 4230f5b into ehtick:main May 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant