Skip to content

Commit 27db4bb

Browse files
dhensbyclaude
andcommitted
fix: treat SQL Server setup exit code 3010 as success
Setup exits with 3010 when it succeeds but wants a reboot, which a runner can't do mid-job. Previously @actions/exec rejected that as a failure even though the instance was installed. Log a warning instead and continue, letting the wait-for-ready check confirm the instance is usable. Any other non-zero exit code still fails the action. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 9f58477 commit 27db4bb

6 files changed

Lines changed: 34 additions & 5 deletions

File tree

‎.github/copilot-instructions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ The build step (`npm run build`) also runs `npm run docs`, which regenerates the
4747
3. Optionally installs SQL Native Client (`src/install-native-client.ts`) and ODBC driver (`src/install-odbc.ts`)
4848
4. Downloads or cache-hits the SQL Server installer (box+exe, standalone exe, or SSEI bootstrapper)
4949
5. Optionally downloads cumulative updates (resolves Microsoft download-page JSON, falls back to legacy links if metadata is unusable, retries network errors, timeouts and all non-2xx page responses up to three times with 5- and 10-second delays, and warns then installs without updates if the download fails)
50-
6. Runs the installer via `@actions/exec`
50+
6. Runs the installer via `@actions/exec` (exit code 3010, "succeeded but reboot required", is logged as a warning and treated as success; any other non-zero code fails the action)
5151
7. Waits for the database to be ready (exponential backoff)
5252

5353
**Installer abstraction:** `src/installers/` contains a base `Installer` class and `MsiInstaller` subclass used by the native client and ODBC installations. SQL Server itself uses direct exe/box download logic or the SSEI bootstrapper (for 2025+) in `src/utils.ts`.

‎README.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ reason and installs SQL Server without updates. Update URLs are configured for
7373
SQL Server 2016 and later, including 2025 (and so the default, `'latest'`);
7474
older versions skip updates.
7575

76+
### Reboot requests
77+
78+
If SQL Server setup succeeds but asks for a reboot (exit code 3010), the action
79+
logs a warning and carries on without rebooting. The wait-for-ready check (when
80+
`wait-for-ready` is enabled) then confirms the instance is accepting queries.
81+
7682
### Basic usage
7783

7884
```yml

‎lib/main/index.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎lib/main/index.js.map‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/install.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export default async function install() {
120120
const instanceName = 'MSSQLSERVER';
121121
try {
122122
// @todo - make sure that the arguments are unique / don't conflict
123-
await core.group('Installing SQL Server', () => exec.exec(`"${toolPath}"`, [
123+
const exitCode = await core.group('Installing SQL Server', () => exec.exec(`"${toolPath}"`, [
124124
'/q',
125125
'/ACTION=Install',
126126
'/FEATURES=SQLEngine', //,FullText,RS,Tools
@@ -137,7 +137,15 @@ export default async function install() {
137137
...installArgs,
138138
], {
139139
windowsVerbatimArguments: true,
140+
ignoreReturnCode: true,
140141
}));
142+
// 3010 means setup succeeded but requested a reboot, which isn't possible mid-job. Carry on
143+
// and let the readiness check confirm the instance is usable.
144+
if (exitCode === 3010) {
145+
core.warning('SQL Server setup succeeded but requested a reboot (exit code 3010); continuing without rebooting');
146+
} else if (exitCode !== 0) {
147+
throw new Error(`SQL Server setup failed with exit code ${exitCode}`);
148+
}
141149
// set outputs
142150
core.setOutput('sa-password', password);
143151
core.setOutput('instance-name', instanceName);

‎test/install.ts‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,22 @@ describe('install', () => {
146146
await install();
147147
const call = exec.exec.mock.calls[0];
148148
assert.equal(call.arguments[0], '"C:/tmp/box/setup.exe"');
149-
assert.deepEqual(call.arguments[2], { windowsVerbatimArguments: true });
149+
assert.deepEqual(call.arguments[2], { windowsVerbatimArguments: true, ignoreReturnCode: true });
150+
});
151+
it('warns and waits for the database if setup requests a reboot', async () => {
152+
exec.exec.mock.mockImplementation(async () => 3010);
153+
await install();
154+
assert.equal(core.warning.mock.callCount(), 1);
155+
assert.equal(core.warning.mock.calls[0].arguments[0], 'SQL Server setup succeeded but requested a reboot (exit code 3010); continuing without rebooting');
156+
assert.equal(utils.waitForDatabase.mock.callCount(), 1);
157+
assert.deepEqual(core.setOutput.mock.calls.map((c) => c.arguments), [['sa-password', 'secret password'], ['instance-name', 'MSSQLSERVER']]);
158+
});
159+
it('fails if setup exits with any other non-zero code', async () => {
160+
exec.exec.mock.mockImplementation(async () => 1);
161+
await assert.rejects(() => install(), { message: 'SQL Server setup failed with exit code 1' });
162+
assert.equal(utils.waitForDatabase.mock.callCount(), 0);
163+
assert.equal(core.setOutput.mock.callCount(), 0);
164+
assert.equal(utils.gatherSummaryFiles.mock.calls[0].arguments[0], true);
150165
});
151166
it('runs an exe install', async () => {
152167
utils.gatherInputs.mock.mockImplementation(() => defaultInputs({ version: 'exe' }));

0 commit comments

Comments
 (0)