Skip to content

CI: actually run the kit in smoke tests, on oldest and newest SQL Server - #4047

Merged
BrentOzar merged 18 commits into
devfrom
claude/issue-4046-smoke-tests
Aug 22, 2026
Merged

CI: actually run the kit in smoke tests, on oldest and newest SQL Server#4047
BrentOzar merged 18 commits into
devfrom
claude/issue-4046-smoke-tests

Conversation

@BrentOzar

@BrentOzar BrentOzar commented Aug 22, 2026

Copy link
Copy Markdown
Member

Closes #4046.

The problem

The smoke test installed only the changed sp_*.sql and ran it once, with @VersionCheckMode = 1 — an unconditional early RETURN at sp_Blitz.sql#L75-L78. No check body ever executed, so it proved the script parsed and nothing more.

#4045 is the evidence: two runtime breakages passed it green and had to be caught by human review. Neither was catchable at install time — one was a variable inside a string literal, the other left the outer T-SQL validly balanced.

What this does

Seeds a database, installs all 12 non-deprecated scripts, verifies each procedure actually exists, runs a 39-step matrix, and fails the build on any SQL error. On SQL Server 2017 and 2025.

  • All 12 non-deprecated scripts, through .github/scripts/smoke-test-matrix.sql — the @Output* table paths, sp_BlitzIndex modes 0-4, sp_ineachdb's three-part-name rewrite, sp_BlitzPlanCompare against a real cached plan. Seeded from Documentation/Development/Test in Azure.sql, kept separate so CI can carry setup a hand-run script shouldn't (decision 2B). sp_BlitzUpdate is excluded: it overwrites the procs mid-run (decision 4).
  • Each step runs on its own, split on --#STEP: markers, so a failure names one step and the rest still run — one CI round surfaces every problem, not just the first.
  • sqlcmd -I, so procedures are created with QUOTED_IDENTIFIER ON. Without it sqlcmd's OFF default is captured at CREATE time and the sp_BlitzFirst/sp_BlitzLock XML paths die with Msg 1934 — a configuration no real client uses.
  • Install is verified, not assumed: a script reduced to an empty file still exits sqlcmd 0, so every expected procedure is checked with OBJECT_ID.
  • Real seed state — 2,000 users / 5,000 posts, a heap, duplicate and unused indexes, full and log backups, plan cache activity. An empty server makes most checks bind against empty tables and report nothing.
  • Two engine versions — 2017 (oldest in support; 2016 left extended support 2026-07-14) and 2025, fail-fast: false.

What was removed, and why

An earlier revision of this PR also installed the base branch's copies, ran everything twice, and classified each failure as new or pre-existing — error signatures, step-body comparison, harness-file comparison, replay-on-mismatch. About 155 lines answering one question: is this failure the PR's fault?

It's gone. Three reasons:

  1. It never took a decision. Every run logged harness differs from base; every step must pass on its own merits, because the harness files themselves keep changing. The machinery has not once been the thing that decided an outcome.
  2. Grandfathering only matters when the baseline is dirty. The baseline is clean — 0 failing steps on both engines. When every step passes, "is this pre-existing?" has no instances.
  3. It was where the defects lived. Nearly every problem found while reviewing this harness was in that code, several of them regressions introduced while fixing others.

The runner went from 792 lines to 281, and CI from two passes to one. Git history has a working implementation if a dirty baseline ever makes the question real.

What is deliberately not covered

Stated plainly, because a step whose name implies coverage it lacks is the same defect as the @VersionCheckMode call this replaces:

Bugs this found

All pre-existing on dev, all filed separately:

CI works around #4048 (waits out the uptime window) and #4050 (skips CheckID 106 via @SkipChecksTable, which incidentally exercises that previously untested path). Both carry comments pointing at their issues and should come out when they're fixed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

The smoke test installed only the changed sp_*.sql and ran it with
@VersionCheckMode = 1, which is an unconditional early RETURN. No check body
ever executed, so runtime breakage passed green -- PR #4045 had two such bugs
reach human review.

Now, per issue #4046:

- Runs all 12 non-deprecated scripts through a 39-step parameter matrix
  covering the @output* table paths, sp_BlitzIndex modes 0-4, sp_kill's
  execute path and sp_DatabaseRestore. sp_BlitzUpdate is excluded because it
  rewrites the procs mid-run.
- Installs the base branch's copy of every script, runs the same matrix, and
  compares. SQL errors this branch introduces fail the build; errors already
  present on base are reported and do not. Differences in sp_Blitz findings
  are printed for review and never fail, since changing a finding is often the
  point of the change.
- Seeds a database, indexes, backup history and plan cache activity so the
  scripts have something real to read instead of an empty server.
- Runs against SQL Server 2017 (oldest in support since 2016 aged out on
  2026-07-14) and SQL Server 2025.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR strengthens the SQL Server smoke-test CI by running the full (non-deprecated) First Responder Kit against seeded, realistic state, across both the oldest supported and newest SQL Server versions, and comparing base vs head to only fail on newly introduced SQL errors.

Changes:

  • Expands the GitHub Actions workflow to run a SQL Server version matrix (2017 + 2025) with fail-fast: false and longer timeouts.
  • Adds CI SQL scripts to seed data and run a step-split execution matrix covering the kit’s main procedures and key parameter paths.
  • Reworks the smoke-test runner to install and execute the kit for base and head revisions, diff results, and fail only on newly introduced SQL errors.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
.github/workflows/sql-server-smoke-tests.yml Runs smoke tests across SQL Server 2017/2025 images with full git history for base vs head comparison.
.github/scripts/smoke-test-seed.sql Seeds a realistic database, activity, and backup history for meaningful procedure execution.
.github/scripts/smoke-test-matrix.sql Defines the 39-step execution matrix split into independently attributable steps.
.github/scripts/run-sql-server-smoke-tests.sh Installs/runs base + head kits, splits and executes the matrix, captures findings, and enforces “new errors fail” logic.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated

@BrentOzar BrentOzar left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expanded matrix is a strong direction, but the current successful workflow run still records seven failed head steps on each SQL Server version. I found five correctness gaps that currently leave several advertised paths untested or can mask regressions.

Comment thread .github/scripts/run-sql-server-smoke-tests.sh
Comment thread .github/scripts/smoke-test-matrix.sql Outdated
Comment thread .github/scripts/smoke-test-matrix.sql Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
All six findings from the first run's review:

- Add sqlcmd -I so procedures install with QUOTED_IDENTIFIER ON. Without it
  sqlcmd's OFF default is captured at CREATE time and all three sp_BlitzFirst
  and both sp_BlitzLock steps died with Msg 1934. Every real client has it ON,
  so OFF was testing a configuration nobody runs.

- Compare a normalized error signature per step, not just the failing label.
  Matching on the label alone turned every step failing on base into a
  permanent allowlist entry: a PR could introduce an unrelated error inside one
  and stay green. Server name and line numbers are stripped so a shifted line
  is not mistaken for a new error.

- Install Ola Hallengren's CommandExecute, which sp_DatabaseRestore refuses to
  run without. Fetched in the workflow and checksum-verified, so an upstream
  change fails the build instead of silently running different code.

- Constrain the sp_BlitzPlanCompare lookup to a marker query run in
  FRKSmokeTest's own context and filtered by the plan's dbid. The outer batch
  runs in master and contains the same marker text, which is what made the
  previous lookup pick a plan @DatabaseName could not match. Raises an error
  now if no plan is found, rather than skipping quietly.

- Exclude CheckID 156 from the findings comparison. It embeds GETDATE() in
  Finding, so it reported one removal and one addition on every run.

- Drop BlitzFirst_WaitStats_Categories and sp_BlitzLock's synonyms between
  passes, so the head pass exercises the same create paths the base pass did.

Also: surface the actual Msg lines on failure instead of a blind tail, which
hid errors behind the result sets these procs print; and wait out the
instance's first minute of uptime, because sp_Blitz aborts with a
divide-by-zero inside that window (issue #4048) and it landed on whichever
pass ran first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7
… PRs

The 615fdab run took the baseline from 9 failing steps to 1 and the findings
comparison came back clean (50 findings, identical to base). Two things left
over, both pre-existing bugs rather than CI defects, now filed:

- Issue #4050: CheckID 106 reads the live default trace with fn_trace_gettable.
  A read landing on a torn or rolling file raises Msg 568 and aborts the entire
  sp_Blitz run, not just that check. It hit one of six sp_Blitz calls on the
  2017 job while 2025 passed, so it is a race, and it would mean red builds on
  pull requests that changed nothing. CI now skips 106 through @SkipChecksTable,
  which has the side benefit of exercising the skip-checks path on every run.
  Remove the row once #4050 is fixed.

- Issue #4049: sp_DatabaseRestore's @MoveFiles = 1 path splits the filename off
  PhysicalName with a hardcoded backslash, so on Linux CHARINDEX returns 0 and
  LEFT(..., -1) raises Msg 537. That step is left failing on purpose: it fails
  identically on base and head, so the signature comparison classifies it as
  pre-existing and it does not fail the build. It turns green once #4049 is
  fixed. Only visible now because CommandExecute is installed; before that the
  proc exited earlier on the missing dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

@BrentOzar BrentOzar left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The earlier five findings were addressed, and the new run confirms that -I, the plan-cache lookup, cleanup additions, and volatile-finding filter work. The refreshed CI also exposes five follow-up correctness gaps: the uptime guard is not actually waiting, the restore fixture still lacks a required dependency and cannot handle this Linux path, error signatures discard the diagnostic text, and a transient error in unchanged code is falsely attributed to the PR.

Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/workflows/sql-server-smoke-tests.yml Outdated
Comment thread .github/scripts/smoke-test-matrix.sql Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

.github/scripts/run-sql-server-smoke-tests.sh:363

  • This call reruns the same full sp_Blitz configuration that the matrix may just have recorded as a base failure. Because capture_findings uses sqlcmd -b and the script has set -e, such a pre-existing base error aborts the job here before the head pass and is never handled by the comparison logic. Guard the base capture, track whether findings are available, and skip only the informational findings diff when it fails.
if [[ -n "$BASE_REVISION" ]] && git -C "$REPO_ROOT" rev-parse --verify --quiet "$BASE_REVISION" >/dev/null; then

Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
BrentOzar and others added 4 commits August 22, 2026 16:12
…bution

Second review round, all five findings:

- The uptime guard never waited. run_query only forwards $1, so the -h -1 -W
  flags were dropped and the first output line was a column header, never '0'.
  It returned in ~0.1s and the 2025 base pass then hit the exact Msg 8134
  divide-by-zero it exists to avoid. Added run_scalar for bare values, and the
  result must now parse as a number greater than zero -- an empty result, a
  header or an error means keep waiting rather than assume the best.

- Error signatures now carry the message text, not just number/level/state.
  Msg 50000 is whatever RAISERROR was handed, and CommandExecute raises it for
  many unrelated failures, so keying on the number alone let one Msg 50000 be
  swapped for another unnoticed. Server name, Procedure, Line and quoted
  literals are still stripped, since those move without the error changing.

- Fetch and install CommandLog.sql alongside CommandExecute.sql, checksummed
  the same way. sp_DatabaseRestore always passes @LogToTable = 'Y' and
  CommandExecute refuses that mode without the table, so CommandExecute alone
  never reached a restore.

- A base/head mismatch is re-confirmed before it is blamed on the branch. The
  passes run minutes apart against one moving server, so a transient lands on
  whichever is unlucky -- that turned a PR touching no stored procedure red.
  Mismatches are now retried on head, and anything still failing is re-tested
  against a reinstalled base. Only a failure surviving both is reported.

- Dropped the executing sp_DatabaseRestore step back to @Help. Its dependencies
  are installed now, but it still cannot complete on a Linux fixture: the
  @MoveFiles = 1 path uses a hardcoded backslash both to split the filename off
  PhysicalName (Msg 537) and to join the backup directory to the file name
  ('/var/opt/mssql/data/\\FRKSmokeTest_Full2.bak'). Tracked in #4049. A
  permanently-red step trains everyone to expect red and advertises coverage
  that does not exist; the invocation is left commented for when #4049 lands.

The signature rewrite was caught by its own unit test before pushing: the first
attempt bounded the Line strip with [^,]* and, since the message has no commas,
ate the message text -- collapsing every error to one signature.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7
…a new step

Two more review findings:

- sp_BlitzFirst's logging path creates four views alongside its tables --
  <FileStats>_Deltas, <PerfmonStats>_Deltas, <PerfmonStats>_Actuals and
  <WaitStats>_Deltas (sp_BlitzFirst.sql:164-167). Dropping only the tables left
  the base pass's views behind, so the head pass skipped all four
  view-creation paths. They are dropped now, before their backing tables.

- Both passes execute THIS branch's matrix, so a step the PR adds or edits also
  runs against base's procedures. A defect in the step itself -- a typo'd
  procedure name, a bad parameter -- therefore failed identically on both
  passes and was waved through as pre-existing. On this PR, where base has no
  matrix at all, that meant every step could be broken and the job would still
  pass. A failure is only grandfathered now when base has a step with the same
  label AND a byte-identical body; anything new or edited has to pass on its
  own merits.

Unit-tested the comparison before pushing: unchanged body -> grandfatherable,
edited body -> must pass, label absent from base -> must pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7
capture_findings runs the same full sp_Blitz configuration the matrix just
exercised, through sqlcmd -b under set -e. So whenever sp_Blitz is failing --
including failing on base, before this branch is involved at all -- the job
died here, during the base pass, before the head pass ran. The comparison logic
that exists to classify exactly that failure was never reached.

Both captures are guarded now and tracked separately. A capture that fails
warns, records that side as unavailable, and lets the run continue; the
findings diff is skipped and says which revision it lost. The error
classification is untouched, which is the half that decides the build.

The findings diff is informational and survivable. Losing the error
classification because the informational half failed first is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7
The view cleanup added in 369418e emitted 'DROP VIEW FRKSmokeTest.dbo.[X]',
which SQL Server rejects with Msg 166: 'DROP VIEW' does not allow specifying
the database name as a prefix to the object name. DROP TABLE accepts one, which
is why the table cleanup beside it worked and this did not.

That aborted the run between the two passes on the 2017 job. The statement is
built with two-part names now and executed inside the target database through
its own sp_executesql, and filtered to the dbo schema.

The rest of that run is the first fully clean baseline: the uptime guard waited
properly (7 checks, 1 minute up) and base finished with 0 failing steps, down
from 9 two rounds ago.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

@BrentOzar BrentOzar left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current head is clean on both engines: 38/38 base and head steps pass, and findings are identical. I found four future-regression holes below. The PR description also needs a refresh before merge: it still says 39 steps and implies sp_DatabaseRestore execute-path coverage, while the current matrix has 38 steps and deliberately runs only @Help; it also still says the SQL has not been verified even though both live jobs are now clean.

Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/workflows/sql-server-smoke-tests.yml Outdated
…names

Four more review findings:

- Quoted literals are no longer blanked wholesale. A quoted name is often the
  only difference between two errors -- Msg 208 for 'OldTable' and 'NewTable'
  are different bugs -- so blanking them let a real regression inherit a
  grandfathered signature. Only quoted values containing a path separator are
  normalised now, which covers rotating trace files and per-run backup paths
  and leaves deterministic identifiers intact.

- Each pass starts from absent kit objects. The base pass used to leave its
  procedures in master, so a script deleted on head was silently skipped by the
  install loop, and a script reduced to an empty file still made sqlcmd exit 0
  -- in both cases the head matrix ran the stale base procedure and passed
  without touching the head artifact. Procedures are dropped before each
  install, and every expected one is verified to exist afterwards, because
  installing without error is not proof the procedure exists.

- Grandfathering now also requires the harness to be unchanged. Both passes use
  THIS branch's runner, seed, matrix and workflow, so a PR that breaks the
  harness -- dropping -I, say -- breaks unchanged steps against base and head
  alike, and step-body equality alone would wave that through. Any difference
  in those four files means every head step must pass on its own merits.

- Dropped the CommandLog/CommandExecute downloads. The only sp_DatabaseRestore
  invocation left is @Help = 1, which returns at sp_DatabaseRestore.sql:71,
  long before the CommandExecute check at :255 -- so they were two network
  dependencies and two checksums that could redden every PR while exercising
  nothing. The URL and both hashes are recorded in a comment for when #4049
  lands and the execute-path step comes back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
…tzWho views

Four findings from the latest Copilot review:

- The findings-capture invocation combines @CheckUserDatabaseObjects,
  @CheckServerInfo and table output in a way no matrix step did, and its
  failure path is a warning by design so the informational diff can degrade
  without killing the run. A head-only error in that exact combination could
  therefore never reach the classifier. It is a labelled matrix step now, so a
  new failure there fails the build.

- Re-confirmation replays the whole matrix instead of re-running the failing
  step in place. Steps conditionally create persistent tables and views, so
  replaying one alone could skip the create branch that failed and look
  transient; the base re-test had the same problem, running against
  head-mutated state. Both rounds now reset, reinstall, and run the full matrix
  so ordering and state match the original passes.

- sp_BlitzWho creates <table>_Deltas for whatever table it logs to
  (sp_BlitzWho.sql:158), which means BlitzWho_Deltas via sp_BlitzFirst's
  @OutputTableNameBlitzWho and BlitzWho_Results_Deltas from the direct step.
  Neither was being dropped, so the head pass skipped both view-creation paths.

- run_scalar's result is assigned in a command substitution, so under set -e a
  transient sqlcmd failure aborted the script -- contradicting the uptime
  guard's own contract that an error means keep waiting. Both call sites
  tolerate failure now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

@BrentOzar BrentOzar left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current head 734f71f. Both live jobs are clean: SQL Server 2017 and 2025 each passed all 39 base steps and all 39 head steps, with 49 and 43 findings respectively, identical to base. The latest push addresses the prior install, signature, harness, cleanup, retry-state, and findings-configuration comments. I found two remaining items below. The PR description also needs one final refresh: it still says 38 steps and that the Ola dependencies are installed, and its verification section is anchored to 8ddef6b rather than this 39-step run.

Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/smoke-test-matrix.sql Outdated
…dance

- The matrix step added in 734f71f covered the sp_Blitz call and the table it
  writes, but not the SELECT the comparison reads it back with. A head change
  renaming or dropping CheckID, DatabaseName or Finding therefore failed only
  inside the warning-only capture path: head_findings_ok=0, diff reported as
  Skipped, job green. The readback now runs inside the labelled step, so that
  is a classified failure. The runner's volatile-CheckID filter is deliberately
  not duplicated there -- the step exists to prove the columns, not the filter.

- The comment explaining how to re-enable the sp_DatabaseRestore execute path
  was written before 32e24f9 removed the dependency downloads, so following it
  would fail on the CommandExecute prerequisite. It now says both the workflow
  dependency step and the invocation have to come back together, and points at
  where the URL and hashes are kept.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

Previously missed (3) — in code that hasn't changed since the last review.

.github/scripts/smoke-test-seed.sql:66

  • CHECKSUM(NEWID()) can return -2147483648, and ABS on that INT raises arithmetic overflow. This makes the seed capable of randomly aborting the entire CI job before any smoke test runs; cast to BIGINT before applying ABS.

This issue also appears on line 72 of the same file.

       ABS(CHECKSUM(NEWID())) % 50000,
       DATEADD(DAY, -(ABS(CHECKSUM(NEWID())) % 3000), GETDATE())

.github/scripts/smoke-test-matrix.sql:202

  • This does not exercise the advertised KILL construction/execution path. With a guaranteed-nonmatching application name, sp_kill sets @TotalKills to 0 and exits through the “Nothing to kill” branch; the cursor and EXEC(@KillSQL) path at sp_kill.sql:793-840 are never reached. Hold open a second sqlcmd session and target that session so regressions in the actual kill loop fail CI.
EXEC dbo.sp_kill @ExecuteKills = 'N';

.github/scripts/run-sql-server-smoke-tests.sh:455

  • sqlcmd formats this variable-length expression before it reaches sed: by default it limits variable columns to 256 characters and uses an 80-character screen width. This serialized row can be about 340 characters (CheckID, a 128-character database name, and a 200-character finding), so output can be truncated or wrapped into multiple lines; sort/comm can then miss a changed suffix or compare fragments instead of findings. Request an untruncated, non-wrapping width for this machine-readable output.
  "$SQLCMD" "${SQLCMD_ARGS[@]}" -d FRKSmokeTest -h -1 -W -s '|' -Q "

.github/scripts/smoke-test-seed.sql:74

  • These three random expressions have the same ABS(INT_MIN) failure mode: CHECKSUM(NEWID()) can produce -2147483648, which makes ABS raise arithmetic overflow and flakes the seed. Cast to BIGINT first.
       ABS(CHECKSUM(NEWID())) % 2000 + 1,
       ABS(CHECKSUM(NEWID())) % 100,
       DATEADD(DAY, -(ABS(CHECKSUM(NEWID())) % 1000), GETDATE()),

Comment thread .github/scripts/smoke-test-matrix.sql Outdated
BrentOzar and others added 3 commits August 22, 2026 16:46
- The findings readback is parsed, not read, but ran with sqlcmd's defaults: an
  80-character screen width and variable-length columns clipped at 256. A row
  here reaches roughly 340 characters (CheckID + NVARCHAR(128) DatabaseName +
  VARCHAR(200) Finding), so rows could wrap or clip and sort/comm would compare
  fragments -- silently missing real differences in the half of the harness
  that exists to surface them. Added -y 0 -w 65535.

- CHECKSUM(NEWID()) can return -2147483648, and ABS() of INT_MIN raises an
  arithmetic overflow, so the seed could abort at random and take the whole job
  with it. All five sites widen to BIGINT before ABS.

- The sp_kill step ran with @ExecuteKills = 'Y' against a filter matching no
  session, so sp_kill sets @TotalKills to 0 and leaves through its nothing-to-
  kill branch: the cursor and EXEC(@KillSQL) at sp_kill.sql:793-840 are never
  reached. It covers parameter handling and the filter path, not the kill loop,
  and is now named for that. Real coverage needs a second session held open and
  respawned per matrix run -- tracked in #4051, deliberately not added here
  since it introduces a timing-dependent background process into a path that
  runs up to three times per job.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7
9abba81 added -y 0 -w 65535 to stop sqlcmd clipping the machine-read findings.
Those are the 'unlimited' spellings, and this sqlcmd build rejected them: the
readback failed outright, and because that path only warns, the run reported
'Skipped: sp_Blitz could not produce a findings set on either revision' while
staying green. The previous commit compared 49 findings; this one compared none
and said so quietly.

Two changes:

- Use -y 8000 -w 8000. Both are squarely inside the documented ranges and
  comfortably above the ~340-character maximum a row can reach, so the original
  truncation concern is still addressed without depending on how a particular
  build handles the edge values.

- Stop discarding the readback's stderr. It was going to /dev/null, so a
  failure produced no explanation anywhere -- the reason this took a CI round
  to spot rather than being obvious in the log. It now warns and prints what
  sqlcmd said.

The findings diff is informational by design (issue #4046, decision 1A) and
should not fail the build, but it should never fail quietly either.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7
f2f0842 kept -W alongside -y, and sqlcmd refuses that combination outright:

  Sqlcmd: The y and the W options are mutually exclusive.

So the readback still failed and the findings comparison was still skipped --
the same symptom as 9abba81, a different cause. The warning added in f2f0842
is what made this visible in one CI round instead of another guessing pass;
without it the log said only 'Skipped' with no reason.

-y is the option that lifts the 256-character truncation, so -W goes. Columns
now come back padded to the display width, and the pipeline strips trailing
whitespace before deduplicating.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

.github/scripts/smoke-test-matrix.sql:231

  • This still says the dependencies are installed, but the workflow and the re-enabling instructions below explicitly say they are not. That contradiction can mislead maintainers about what the current help-only step covers; describe the dependencies as deliberately omitted.
Its dependencies are installed (CommandLog + CommandExecute, fetched by the
workflow), so the procedure runs -- but it cannot complete against a Linux

Comment thread .github/scripts/smoke-test-matrix.sql
The sp_DatabaseRestore comment still said its dependencies were installed and
fetched by the workflow, which 32e24f9 removed -- and which the re-enabling
instructions six lines below it directly contradicted. It now says they are
deliberately absent, and records that they were installed briefly and that this
is how #4049 was found: with both present the procedure gets far enough to fail
on Linux path handling instead of stopping at the missing-dependency check.

A comment that overstates coverage is the same defect this PR exists to fix,
just written in prose instead of SQL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

.github/scripts/run-sql-server-smoke-tests.sh:510

  • sp_BlitzLock also creates FRKSmokeTest.dbo.BlitzLockFindings on its table-output path (sp_BlitzLock.sql:869-893). Leaving that table behind means the head pass sees it from the base pass and skips its creation branch, so a regression in that DDL can pass. Include it in the table cleanup list.
WHERE name IN (N'BlitzOutput', N'BlitzCache', N'BlitzFirst', N'BlitzFirst_FileStats',
               N'BlitzFirst_PerfmonStats', N'BlitzFirst_WaitStats',
               N'BlitzFirst_WaitStats_Categories', N'BlitzWho',
               N'BlitzWho_Results', N'BlitzLock', N'BlitzIndex', N'BlitzFindings');

Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
BrentOzar and others added 2 commits August 22, 2026 17:26
…ture

- The reset list has now been wrong four times: sp_BlitzLock's synonyms,
  sp_BlitzFirst's four views, sp_BlitzWho's two, and now sp_BlitzLock's
  BlitzLockFindings. Any proc taking @OutputTableName can create objects
  derived from it, so naming them one at a time is structurally the losing
  approach -- each new output path is a silent gap until someone notices the
  head pass skipped its create branch. Cleanup now enumerates every
  Blitz-prefixed table and view in the test database instead, keeping
  BlitzChecksToSkip (seed-created, needed by both passes) and leaving the
  seed's own non-prefixed tables alone.

- The replay branch compared signatures without requiring either to be
  non-empty, unlike the initial classification. error_signature returns empty
  whenever a failure produces no 'Msg ...' or 'Sqlcmd: Error' line, so two
  unrelated unknown failures compared equal and were written off as
  environmental. It now requires a non-empty signature, matching the initial
  check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7
The value in this harness is executing the scripts at all. That alone found
three real pre-existing bugs (#4048, #4049, #4050) and would have caught both
of PR #4045's runtime breakages. It worked from the first commit and has needed
almost no repair since.

The base-vs-head comparison is the opposite story. Installing the base branch's
copies, running everything twice, and classifying each failure as new or
pre-existing needed error signatures, step-body comparison, harness-file
comparison and replay-on-mismatch -- about 155 lines whose only job was
answering 'is this failure the PR's fault?'. Nearly every defect found while
reviewing this harness lived in that code, and several were regressions
introduced while fixing others.

It also never took a decision. Every run so far logged 'harness differs from
base; every step must pass on its own merits', because the harness files
themselves keep changing. Grandfathering only matters when the baseline is
dirty, and the baseline is clean: 0 failing steps on both engine versions.

So it is gone, along with the findings diff it fed. The runner drops from 792
lines to 281, CI drops from two passes to one, and what remains is: seed a
database, install all 12 non-deprecated scripts, verify each procedure exists,
run 39 labelled steps, fail on any SQL error. Git history keeps a working
implementation of the comparison if a dirty baseline ever makes it necessary.

Unchanged and still earning their place: sqlcmd -I (QUOTED_IDENTIFIER),
per-step attribution so one round surfaces every failure, the uptime wait for
#4048, the CheckID 106 skip for #4050, and both engine versions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

@BrentOzar BrentOzar left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current head 88f5e08. The earlier findings-readback and restore-guidance notes are fixed, as are the subsequent cleanup and empty-signature issues. Both live jobs passed all 39 base and 39 head steps with no SQL errors. SQL Server 2025 reported 43 findings identical to base; SQL Server 2017 showed one informational difference, CheckID 185 (Wait Stats Have Been Cleared), despite this PR changing no kit procedure. I found three remaining classifier/noise issues below.

Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
Comment thread .github/scripts/run-sql-server-smoke-tests.sh
Comment thread .github/scripts/run-sql-server-smoke-tests.sh Outdated
@BrentOzar
BrentOzar requested a balanced review from Copilot August 22, 2026 17:35
Labels are display-only now that the classifier is gone, so a duplicate can no
longer cause a false pass -- every step file runs and failures are counted per
file. It would still make the output ambiguous about which step failed, and the
check is three lines, so the splitter rejects duplicates outright.

Verified both ways: a matrix with two identical labels exits with the offending
label named, and the real 39-step matrix passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

.github/scripts/smoke-test-matrix.sql:81

  • This explanation describes the removed findings comparison, classification, and volatile-CheckID filter. The current runner only executes the SELECT; its purpose is now to make missing or renamed persisted columns fail the step.
/* The readback the comparison performs, so that renaming or removing CheckID,
   DatabaseName or Finding fails this classified step instead of silently
   downgrading the findings diff to "Skipped". The volatile-CheckID filter the
   runner applies is deliberately not reproduced here -- this exists to prove the
   columns the readback depends on, not to duplicate the filter. */

Comment thread .github/scripts/smoke-test-matrix.sql Outdated
Comment thread .github/scripts/smoke-test-seed.sql Outdated
573c8ac deleted the base-vs-head comparison but left prose describing it in
three places:

- The sp_BlitzUpdate exclusion was justified by 'would invalidate the
  base-vs-head comparison'. The real and remaining reason is that it replaces
  the kit's procedures mid-run, so every later step would exercise whatever it
  just downloaded instead of the code under test.
- The findings readback was explained in terms of the classifier and the
  volatile-CheckID filter, neither of which exists. It now says what it does:
  proves the persisted columns are still shaped the way callers expect, since
  writing the table only proves the table was created.
- The seed said it runs 'before either pass'. There is one pass.

Swept the rest of the harness for the same class; the only other hit was
sp_BlitzPlanCompare's own plan comparison, which is accurate.

Stale comments describing behaviour that no longer exists are a smaller version
of the problem this PR was opened to fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7
@BrentOzar
BrentOzar marked this pull request as ready for review August 22, 2026 17:43
@BrentOzar
BrentOzar merged commit 5c00293 into dev Aug 22, 2026
4 checks passed
@BrentOzar
BrentOzar deleted the claude/issue-4046-smoke-tests branch August 22, 2026 17:44

@BrentOzar BrentOzar left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed current head 87413ca.

The base-vs-head classifier and findings-diff subsystem that motivated my prior three comments has been removed. The remaining duplicate-label ambiguity is now explicitly rejected, and this latest commit cleans up the stale comments left by the one-pass simplification.

I found no additional actionable issues in the current runner, matrix, seed, or workflow. The live SQL Server 2017 and SQL Server 2025 jobs both passed all 39 matrix steps.

BrentOzar added a commit that referenced this pull request Aug 22, 2026
Brings in the smoke-test overhaul from #4047, so this branch's 51 rewritten
cross-database blocks are executed against real SQL Server 2017 and 2025 for the
first time. That is the boxed-SQL verification this PR's description says is
missing, and the gap that let its Msg 137 and stranded-IF bugs through.
BrentOzar added a commit that referenced this pull request Aug 22, 2026
Answers the question code review cannot: does the rewritten sp_Blitz still
report the same findings as the released one? Three reviewers have now read this
change; nobody has run it and compared what it finds.

Installs dev's sp_Blitz, runs it, records every (CheckID, DatabaseName,
Finding). Installs this branch's, runs the same thing, records again, diffs.
The Azure rewrite is meant to change how the code is written, not what it
reports, so on boxed SQL the two lists should be identical. Runs on SQL Server
2017 and 2025.

Excludes CheckIDs 156 and 185, whose text embeds a timestamp and a live wait
counter respectively, so they differ between any two runs minutes apart
regardless of the code.

Deliberately not part of the permanent smoke tests -- on every PR this was noisy
and never decided anything, which is why #4047 removed it. Gated to this branch,
and both files come out once the answer is recorded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMPBMMANLaFgSQ73GTgBW7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make the PR smoke test actually run the scripts, across the whole non-deprecated kit

2 participants