From 8ad8dcd830c8f08e80965e93f5cb256d472898d2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 24 Oct 2026 12:27:58 +0000 Subject: [PATCH 1/4] Add SQL Server write-safety instructions for T-SQL Co-authored-by: scottkcoffey-a11y --- docs/README.instructions.md | 1 + .../sql-server-safe-tsql.instructions.md | 131 ++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 instructions/sql-server-safe-tsql.instructions.md diff --git a/docs/README.instructions.md b/docs/README.instructions.md index 710960f3d..99855f4e7 100644 --- a/docs/README.instructions.md +++ b/docs/README.instructions.md @@ -191,6 +191,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-instructions) for guidelines on | [Spring Boot 3.x to 4.0 Migration Guide](../instructions/springboot-4-migration.instructions.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fspringboot-4-migration.instructions.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fspringboot-4-migration.instructions.md) | Comprehensive guide for migrating Spring Boot applications from 3.x to 4.0, focusing on Gradle Kotlin DSL and version catalogs | | [Spring Boot Development](../instructions/springboot.instructions.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fspringboot.instructions.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fspringboot.instructions.md) | Guidelines for building Spring Boot base applications | | [SQL Development](../instructions/sql-sp-generation.instructions.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fsql-sp-generation.instructions.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fsql-sp-generation.instructions.md) | Guidelines for generating SQL statements and stored procedures | +| [SQL Server Production-Safe T-SQL](../instructions/sql-server-safe-tsql.instructions.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fsql-server-safe-tsql.instructions.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fsql-server-safe-tsql.instructions.md) | SQL Server-specific write-safety rules for T-SQL: XACT_ABORT with THROW, @@TRANCOUNT-aware transactions, fail-closed dry runs, batched OUTPUT...INTO archiving, autocommit-only admin statements, and read-only DMV diagnostics. | | [Style Components with Modern Theming (Preview)](../instructions/pcf-fluent-modern-theming.instructions.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fpcf-fluent-modern-theming.instructions.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fpcf-fluent-modern-theming.instructions.md) | Style components with modern theming using Fluent UI | | [Svelte 5 and SvelteKit Development Instructions](../instructions/svelte.instructions.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fsvelte.instructions.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fsvelte.instructions.md) | Svelte 5 and SvelteKit 2 development standards and best practices for component-based user interfaces and full-stack applications | | [Swift MCP Server Development Guidelines](../instructions/swift-mcp-server.instructions.md)
[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fswift-mcp-server.instructions.md)
[![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://aka.ms/awesome-copilot/install/instructions?url=vscode-insiders%3Achat-instructions%2Finstall%3Furl%3Dhttps%3A%2F%2Fraw.githubusercontent.com%2Fgithub%2Fawesome-copilot%2Fmain%2Finstructions%2Fswift-mcp-server.instructions.md) | Best practices and patterns for building Model Context Protocol (MCP) servers in Swift using the official MCP Swift SDK package. | diff --git a/instructions/sql-server-safe-tsql.instructions.md b/instructions/sql-server-safe-tsql.instructions.md new file mode 100644 index 000000000..5ac896b52 --- /dev/null +++ b/instructions/sql-server-safe-tsql.instructions.md @@ -0,0 +1,131 @@ +--- +description: 'SQL Server-specific write-safety rules for T-SQL: XACT_ABORT with THROW, @@TRANCOUNT-aware transactions, fail-closed dry runs, batched OUTPUT...INTO archiving, autocommit-only admin statements, and read-only DMV diagnostics.' +applyTo: '**/*.sql, **/*.ps1' +--- + +# SQL Server Production-Safe T-SQL + +Apply these rules when writing or reviewing T-SQL, SQL Server Agent jobs, or PowerShell that runs against SQL Server. They complement general SQL style guidance by focusing on what keeps a script safe to run against production-adjacent systems. + +## Script header + +Start every script with a comment block that states: + +- Purpose (one line) +- `COMPAT:` the minimum SQL Server version (for example `SQL Server 2016 SP1+` for `CREATE OR ALTER`) and whether it works on Azure SQL Managed Instance +- `Perms:` the permissions required (for example `VIEW SERVER STATE`, or `VIEW SERVER PERFORMANCE STATE` on SQL Server 2022+) +- Whether the script is read-only or writes data + +If you are not sure a syntax or DMV column exists in the target version, say so instead of guessing. + +## Required shape for any write (DML or DDL) + +- Begin with `SET NOCOUNT ON; SET XACT_ABORT ON;`. +- Wrap transaction-compatible work in `BEGIN TRY ... BEGIN TRANSACTION ... COMMIT ... END TRY BEGIN CATCH ... END CATCH`. +- Some administration statements cannot run inside a user transaction (for example `CREATE DATABASE`, `ALTER DATABASE`, `DROP DATABASE`, `BACKUP`, `RESTORE`, and `RECONFIGURE`). For these, use an autocommit path instead of the transaction wrapper: keep the same dry-run default and confirmation gate, run each statement on its own inside `TRY ... CATCH` with `THROW;`, check state before and after, and write the operation-specific recovery step (for example the backup to restore from, or the previous configuration value to set back). +- In the `CATCH` block, roll back only if a transaction is open, then `THROW;` so the original error number and line reach the caller and Agent job history. Do not swallow errors. +- In single-transaction procedures that may be called inside an outer transaction, only begin and commit a transaction when `@@TRANCOUNT = 0` on entry. Procedures that commit in batches must refuse to run inside a caller-owned transaction, because the outer transaction would keep every batch's locks and log until it ends. +- Default to a dry run that fails closed. In T-SQL, use a `@DryRun bit = 1` parameter and treat anything other than an explicit `0`, including `NULL`, as a dry run (`IF @DryRun IS NULL OR @DryRun <> 0`). The parameter default only covers omitted arguments, so never test `IF @DryRun = 1` alone. The dry run prints or returns what would change (for example a row count) and changes nothing. +- In PowerShell, `SupportsShouldProcess` and `-WhatIf` alone do not make dry run the default. Require an explicit `-Apply` switch that is off by default, and wrap every mutation in `if ($Apply -and $PSCmdlet.ShouldProcess($target, $action)) { ... }`. Without `-Apply`, report what would change and exit. +- Make scripts idempotent: `CREATE OR ALTER`, `IF OBJECT_ID(...) IS NULL`, `IF NOT EXISTS`. Never silently overwrite an existing Agent job, linked server, alert, login, or user; detect it and stop or require an explicit flag. +- Every `UPDATE` and `DELETE` has a selective `WHERE` clause. Batch large changes with a `TOP (N)` loop, one short transaction per batch, and an accumulated row count. +- When moving rows (archive then delete), capture each batch's keys into a freshly cleared key set with `OUTPUT ... INTO`, and delete only the rows proven to be copied in that batch. +- Validate parameters at the top of procedures and return a documented non-zero return code on invalid input. + +### Procedure template + +```sql +CREATE OR ALTER PROCEDURE dbo.usp_ArchiveOrders + @OlderThanDays int, + @BatchSize int = 5000, + @DryRun bit = 1, + @RowsAffected int = NULL OUTPUT +AS +BEGIN + SET NOCOUNT ON; + SET XACT_ABORT ON; + + IF @OlderThanDays IS NULL OR @OlderThanDays < 1 + BEGIN + RAISERROR(N'@OlderThanDays must be >= 1.', 16, 1); + RETURN 1; + END; + + IF @BatchSize IS NULL OR @BatchSize NOT BETWEEN 1 AND 50000 + BEGIN + RAISERROR(N'@BatchSize must be between 1 and 50000.', 16, 1); + RETURN 2; + END; + + -- Batches commit independently, so a caller-owned transaction is not allowed. + IF @@TRANCOUNT > 0 + BEGIN + RAISERROR(N'Run outside a caller transaction; this procedure commits per batch.', 16, 1); + RETURN 3; + END; + + DECLARE @Cutoff datetime2(0) = DATEADD(DAY, -@OlderThanDays, SYSDATETIME()); + SET @RowsAffected = 0; + + -- Fail closed: anything other than an explicit 0, including NULL, is a dry run. + IF @DryRun IS NULL OR @DryRun <> 0 + BEGIN + SELECT @RowsAffected = COUNT(*) FROM dbo.Orders WHERE Status = 'Closed' AND CreatedAt < @Cutoff; + PRINT CONCAT(N'DRY RUN: would archive ', @RowsAffected, N' rows in batches of ', @BatchSize, N'.'); + RETURN 0; + END; + + DECLARE @moved TABLE (OrderId int PRIMARY KEY); + DECLARE @BatchRows int = 1; + + BEGIN TRY + WHILE @BatchRows > 0 + BEGIN + DELETE FROM @moved; -- fresh key set for every batch + + BEGIN TRANSACTION; + + INSERT dbo.OrdersArchive (OrderId, Status, CreatedAt) + OUTPUT inserted.OrderId INTO @moved (OrderId) + SELECT TOP (@BatchSize) o.OrderId, o.Status, o.CreatedAt + FROM dbo.Orders o WITH (UPDLOCK, HOLDLOCK) + WHERE o.Status = 'Closed' AND o.CreatedAt < @Cutoff + ORDER BY o.OrderId; + + DELETE o FROM dbo.Orders o JOIN @moved m ON m.OrderId = o.OrderId; + SET @BatchRows = @@ROWCOUNT; + + COMMIT TRANSACTION; + SET @RowsAffected += @BatchRows; + END; + RETURN 0; + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION; -- rolls back only the failing batch + THROW; + END CATCH; +END; +``` + +On failure, earlier batches stay committed and `@RowsAffected` reports only committed rows. A rerun picks up the remaining qualifying rows. + +## Ask before generating destructive or risky operations + +Stop and ask for explicit confirmation, stating the data-loss or availability impact, before producing any of: + +- `DROP`, `TRUNCATE`, `ALTER TABLE ... DROP COLUMN`, or column type narrowing +- `UPDATE` or `DELETE` without a selective predicate +- `KILL`, or `DBCC` repair options +- Enabling `xp_cmdshell`, Ole Automation, or CLR +- Server-level configuration changes (for example `max server memory`) +- Disabling constraints or triggers + +Before any data-loss operation, state what should be backed up, where, and how to restore it. Include rollback steps for every change. + +## Diagnostics are read-only + +- Diagnostic queries against DMVs (blocking chains, wait statistics, expensive queries, orphaned users, linked server checks) must not change state. Label them `READ-ONLY` in the header. +- For blocking, identify head blockers (sessions that block others but are not blocked themselves) and show the chain with `sys.dm_exec_sessions`, `sys.dm_exec_requests`, and `sys.dm_exec_sql_text`. Do not issue `KILL`; if the user decides to, tell them to record the session id and reason, and that `KILL` rolls back the open transaction. +- For wait statistics, capture two snapshots of `sys.dm_os_wait_stats` and report the delta, excluding benign idle waits, rather than reporting cumulative values since startup. +- For orphaned users, restrict detection to instance-authenticated SQL users (`type = 'S'` and `authentication_type = 1` in `sys.database_principals`) whose SID has no matching row in `sys.server_principals`. Exclude system principals (`dbo`, `guest`, `INFORMATION_SCHEMA`, `sys`). Users created `WITHOUT LOGIN`, contained database users, and Windows users intentionally have no matching SQL login and are not orphans. +- Keep orphaned-user detection read-only. Any repair is a separate, dry-run-first script that requires a confirmed user-to-login mapping, uses `ALTER USER ... WITH LOGIN = ...`, never creates logins, and never drops users. From 958cdf4f5577f389a4ecd613d7ea8ca7e57d862a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 25 Oct 2026 15:08:18 +0000 Subject: [PATCH 2/4] Address review: refuse IMPLICIT_TRANSACTIONS ON and correct @RowsAffected note Co-authored-by: scottkcoffey-a11y --- instructions/sql-server-safe-tsql.instructions.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/instructions/sql-server-safe-tsql.instructions.md b/instructions/sql-server-safe-tsql.instructions.md index 5ac896b52..c9e89347d 100644 --- a/instructions/sql-server-safe-tsql.instructions.md +++ b/instructions/sql-server-safe-tsql.instructions.md @@ -24,7 +24,7 @@ If you are not sure a syntax or DMV column exists in the target version, say so - Wrap transaction-compatible work in `BEGIN TRY ... BEGIN TRANSACTION ... COMMIT ... END TRY BEGIN CATCH ... END CATCH`. - Some administration statements cannot run inside a user transaction (for example `CREATE DATABASE`, `ALTER DATABASE`, `DROP DATABASE`, `BACKUP`, `RESTORE`, and `RECONFIGURE`). For these, use an autocommit path instead of the transaction wrapper: keep the same dry-run default and confirmation gate, run each statement on its own inside `TRY ... CATCH` with `THROW;`, check state before and after, and write the operation-specific recovery step (for example the backup to restore from, or the previous configuration value to set back). - In the `CATCH` block, roll back only if a transaction is open, then `THROW;` so the original error number and line reach the caller and Agent job history. Do not swallow errors. -- In single-transaction procedures that may be called inside an outer transaction, only begin and commit a transaction when `@@TRANCOUNT = 0` on entry. Procedures that commit in batches must refuse to run inside a caller-owned transaction, because the outer transaction would keep every batch's locks and log until it ends. +- In single-transaction procedures that may be called inside an outer transaction, only begin and commit a transaction when `@@TRANCOUNT = 0` on entry. Procedures that commit in batches must refuse to run inside a caller-owned transaction or with `IMPLICIT_TRANSACTIONS ON` (`@@OPTIONS & 2`), because either one keeps every batch's locks and log until an outer commit and lets a later rollback undo earlier batches. - Default to a dry run that fails closed. In T-SQL, use a `@DryRun bit = 1` parameter and treat anything other than an explicit `0`, including `NULL`, as a dry run (`IF @DryRun IS NULL OR @DryRun <> 0`). The parameter default only covers omitted arguments, so never test `IF @DryRun = 1` alone. The dry run prints or returns what would change (for example a row count) and changes nothing. - In PowerShell, `SupportsShouldProcess` and `-WhatIf` alone do not make dry run the default. Require an explicit `-Apply` switch that is off by default, and wrap every mutation in `if ($Apply -and $PSCmdlet.ShouldProcess($target, $action)) { ... }`. Without `-Apply`, report what would change and exit. - Make scripts idempotent: `CREATE OR ALTER`, `IF OBJECT_ID(...) IS NULL`, `IF NOT EXISTS`. Never silently overwrite an existing Agent job, linked server, alert, login, or user; detect it and stop or require an explicit flag. @@ -57,10 +57,11 @@ BEGIN RETURN 2; END; - -- Batches commit independently, so a caller-owned transaction is not allowed. - IF @@TRANCOUNT > 0 + -- Batches commit independently, so a caller-owned transaction or + -- IMPLICIT_TRANSACTIONS ON is not allowed. + IF @@TRANCOUNT > 0 OR (@@OPTIONS & 2) = 2 BEGIN - RAISERROR(N'Run outside a caller transaction; this procedure commits per batch.', 16, 1); + RAISERROR(N'Run outside a caller transaction with IMPLICIT_TRANSACTIONS OFF; this procedure commits per batch.', 16, 1); RETURN 3; END; @@ -107,7 +108,7 @@ BEGIN END; ``` -On failure, earlier batches stay committed and `@RowsAffected` reports only committed rows. A rerun picks up the remaining qualifying rows. +On failure, earlier batches stay committed and the error is rethrown. Because the error is rethrown, SQL Server does not copy `@RowsAffected` back to the caller, so treat it as reliable only on successful completion; to see partial progress, count the archive table or log each batch before rethrowing. A rerun picks up the remaining qualifying rows. ## Ask before generating destructive or risky operations From 90b0f871f94d891329dda0a5854beaeb2619073a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 25 Oct 2026 16:32:40 +0000 Subject: [PATCH 3/4] Address review: reject implicit transactions in single-transaction rule, drop HOLDLOCK Co-authored-by: scottkcoffey-a11y --- instructions/sql-server-safe-tsql.instructions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/instructions/sql-server-safe-tsql.instructions.md b/instructions/sql-server-safe-tsql.instructions.md index c9e89347d..b3ebc398a 100644 --- a/instructions/sql-server-safe-tsql.instructions.md +++ b/instructions/sql-server-safe-tsql.instructions.md @@ -24,7 +24,7 @@ If you are not sure a syntax or DMV column exists in the target version, say so - Wrap transaction-compatible work in `BEGIN TRY ... BEGIN TRANSACTION ... COMMIT ... END TRY BEGIN CATCH ... END CATCH`. - Some administration statements cannot run inside a user transaction (for example `CREATE DATABASE`, `ALTER DATABASE`, `DROP DATABASE`, `BACKUP`, `RESTORE`, and `RECONFIGURE`). For these, use an autocommit path instead of the transaction wrapper: keep the same dry-run default and confirmation gate, run each statement on its own inside `TRY ... CATCH` with `THROW;`, check state before and after, and write the operation-specific recovery step (for example the backup to restore from, or the previous configuration value to set back). - In the `CATCH` block, roll back only if a transaction is open, then `THROW;` so the original error number and line reach the caller and Agent job history. Do not swallow errors. -- In single-transaction procedures that may be called inside an outer transaction, only begin and commit a transaction when `@@TRANCOUNT = 0` on entry. Procedures that commit in batches must refuse to run inside a caller-owned transaction or with `IMPLICIT_TRANSACTIONS ON` (`@@OPTIONS & 2`), because either one keeps every batch's locks and log until an outer commit and lets a later rollback undo earlier batches. +- In single-transaction procedures that may be called inside an outer transaction, check `@@TRANCOUNT` on entry, before any read, and only begin and commit a transaction when it is `0`. If it is `0` and `IMPLICIT_TRANSACTIONS` is ON (`@@OPTIONS & 2`), reject the call before `BEGIN TRANSACTION`, because `BEGIN TRANSACTION` then opens a second level and the single `COMMIT` leaves the write uncommitted with its locks held (for example `IF @@TRANCOUNT = 0 AND (@@OPTIONS & 2) = 2 THROW 50000, N'Run with IMPLICIT_TRANSACTIONS OFF; this procedure owns its transaction.', 1;`). Procedures that commit in batches must refuse to run inside a caller-owned transaction or with `IMPLICIT_TRANSACTIONS ON` (`@@OPTIONS & 2`), because either one keeps every batch's locks and log until an outer commit and lets a later rollback undo earlier batches. - Default to a dry run that fails closed. In T-SQL, use a `@DryRun bit = 1` parameter and treat anything other than an explicit `0`, including `NULL`, as a dry run (`IF @DryRun IS NULL OR @DryRun <> 0`). The parameter default only covers omitted arguments, so never test `IF @DryRun = 1` alone. The dry run prints or returns what would change (for example a row count) and changes nothing. - In PowerShell, `SupportsShouldProcess` and `-WhatIf` alone do not make dry run the default. Require an explicit `-Apply` switch that is off by default, and wrap every mutation in `if ($Apply -and $PSCmdlet.ShouldProcess($target, $action)) { ... }`. Without `-Apply`, report what would change and exit. - Make scripts idempotent: `CREATE OR ALTER`, `IF OBJECT_ID(...) IS NULL`, `IF NOT EXISTS`. Never silently overwrite an existing Agent job, linked server, alert, login, or user; detect it and stop or require an explicit flag. @@ -89,7 +89,7 @@ BEGIN INSERT dbo.OrdersArchive (OrderId, Status, CreatedAt) OUTPUT inserted.OrderId INTO @moved (OrderId) SELECT TOP (@BatchSize) o.OrderId, o.Status, o.CreatedAt - FROM dbo.Orders o WITH (UPDLOCK, HOLDLOCK) + FROM dbo.Orders o WITH (UPDLOCK) WHERE o.Status = 'Closed' AND o.CreatedAt < @Cutoff ORDER BY o.OrderId; From 9c19d393f20f2840d63742b27ad8f2550a25aa8f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 25 Oct 2026 17:23:59 +0000 Subject: [PATCH 4/4] Address review: lower default batch size to 1000 to avoid lock escalation Co-authored-by: scottkcoffey-a11y --- instructions/sql-server-safe-tsql.instructions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/instructions/sql-server-safe-tsql.instructions.md b/instructions/sql-server-safe-tsql.instructions.md index b3ebc398a..0476f678d 100644 --- a/instructions/sql-server-safe-tsql.instructions.md +++ b/instructions/sql-server-safe-tsql.instructions.md @@ -37,7 +37,7 @@ If you are not sure a syntax or DMV column exists in the target version, say so ```sql CREATE OR ALTER PROCEDURE dbo.usp_ArchiveOrders @OlderThanDays int, - @BatchSize int = 5000, + @BatchSize int = 1000, @DryRun bit = 1, @RowsAffected int = NULL OUTPUT AS @@ -108,6 +108,8 @@ BEGIN END; ``` +The default `@BatchSize` is 1000; tune larger values, because a batch that takes thousands of locks can escalate to a table lock that blocks other sessions. + On failure, earlier batches stay committed and the error is rethrown. Because the error is rethrown, SQL Server does not copy `@RowsAffected` back to the caller, so treat it as reliable only on successful completion; to see partial progress, count the archive table or log each batch before rethrowing. A rerun picks up the remaining qualifying rows. ## Ask before generating destructive or risky operations