@@ -131,6 +131,7 @@ public async Task<AlertHealthResult> GetAlertHealthAsync(
131131 bool excludeWaitFor = true ,
132132 bool excludeBackups = true ,
133133 bool excludeMiscWaits = true ,
134+ bool excludeCdc = true ,
134135 IReadOnlyList < string > ? excludedDatabases = null )
135136 {
136137 var result = new AlertHealthResult ( ) ;
@@ -149,7 +150,7 @@ public async Task<AlertHealthResult> GetAlertHealthAsync(
149150 ? GetFilteredDeadlockCountAsync ( connection , excludedDatabases )
150151 : null ;
151152 var poisonWaitTask = GetPoisonWaitDeltasAsync ( connection ) ;
152- var longRunningTask = GetLongRunningQueriesAsync ( connection , longRunningQueryThresholdMinutes , longRunningQueryMaxResults , excludeSpServerDiagnostics , excludeWaitFor , excludeBackups , excludeMiscWaits ) ;
153+ var longRunningTask = GetLongRunningQueriesAsync ( connection , longRunningQueryThresholdMinutes , longRunningQueryMaxResults , excludeSpServerDiagnostics , excludeWaitFor , excludeBackups , excludeMiscWaits , excludeCdc ) ;
153154 var tempDbTask = GetTempDbSpaceAsync ( connection ) ;
154155 var anomalousJobTask = GetAnomalousJobsAsync ( connection , longRunningJobMultiplier ) ;
155156 var missingCaptureTask = GetMissingCaptureSessionsAsync ( connection ) ;
@@ -751,7 +752,8 @@ private async Task<List<LongRunningQueryInfo>> GetLongRunningQueriesAsync(
751752 bool excludeSpServerDiagnostics = true ,
752753 bool excludeWaitFor = true ,
753754 bool excludeBackups = true ,
754- bool excludeMiscWaits = true )
755+ bool excludeMiscWaits = true ,
756+ bool excludeCdc = true )
755757 {
756758 maxResults = Math . Clamp ( maxResults , 1 , 1000 ) ;
757759
@@ -763,9 +765,45 @@ private async Task<List<LongRunningQueryInfo>> GetLongRunningQueriesAsync(
763765 ? "AND r.wait_type NOT IN (N'BACKUPTHREAD', N'BACKUPIO')" : "" ;
764766 string miscWaitsFilter = excludeMiscWaits
765767 ? "AND r.wait_type NOT IN (N'XE_LIVE_TARGET_TVF')" : "" ;
768+ // CDC capture runs continuously as a SQL Agent job (EXEC sys.sp_MScdc_capture_job -> sys.sp_cdc_scan),
769+ // so it permanently exceeds the duration threshold and none of the wait_type filters above catch it.
770+ //
771+ // Primary signal: resolve the capture job_id(s) from msdb.dbo.cdc_jobs and match the running session via
772+ // its SQL Agent program_name ('SQLAgent - TSQL JobStep (Job 0x<job_id> : Step N)'). This is CDC-specific
773+ // and never hides unrelated Agent jobs. The msdb reference is deferred through sp_executesql inside
774+ // TRY/CATCH so a login without msdb access gets a *catchable* error (not an uncatchable cross-db 916) and
775+ // cleanly falls back to a text match on the whole batch/object text.
776+ string cdcSetup = excludeCdc ? @"
777+ DECLARE @cdc_capture_jobs TABLE (job_id uniqueidentifier PRIMARY KEY);
778+ DECLARE @cdc_readable bit = 0;
779+ BEGIN TRY
780+ INSERT @cdc_capture_jobs (job_id)
781+ EXEC sys.sp_executesql N'SELECT cj.job_id FROM msdb.dbo.cdc_jobs AS cj WHERE cj.job_type = N''capture'';';
782+ SET @cdc_readable = 1;
783+ END TRY
784+ BEGIN CATCH
785+ SET @cdc_readable = 0;
786+ END CATCH;
787+ " : "" ;
788+ string cdcFilter = excludeCdc ? @"
789+ AND NOT
790+ (
791+ (
792+ @cdc_readable = 1
793+ AND s.program_name LIKE N'SQLAgent - TSQL JobStep (Job 0x%'
794+ AND TRY_CONVERT(uniqueidentifier, TRY_CONVERT(binary(16), SUBSTRING(s.program_name, 32, 32), 2))
795+ IN (SELECT j.job_id FROM @cdc_capture_jobs AS j)
796+ )
797+ OR
798+ (
799+ @cdc_readable = 0
800+ AND t.text IS NOT NULL
801+ AND (t.text LIKE N'%sp_MScdc_capture_job%' OR t.text LIKE N'%sp_cdc_scan%')
802+ )
803+ )" : "" ;
766804
767805 string query = @$ "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
768-
806+ { cdcSetup }
769807 SELECT TOP(@maxResults)
770808 r.session_id,
771809 DB_NAME(r.database_id) AS database_name,
@@ -787,6 +825,7 @@ CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
787825 { waitForFilter }
788826 { backupsFilter }
789827 { miscWaitsFilter }
828+ { cdcFilter }
790829 ORDER BY r.total_elapsed_time DESC
791830 OPTION(MAXDOP 1, RECOMPILE);" ;
792831
0 commit comments