Run shutdown-check after building your application. The CLI's exit code fails the job, while JSON and JUnit preserve the diagnostic code, timeline, and captured service output.
Prepare the project
Before adding CI configuration:
- Run
npx shutdown-check testlocally. - Commit
shutdown-check.json. - Point
commandat the built entry file. - Reserve a port for this test.
- Use isolated database, queue, and file resources.
- Make sure the workload route is deterministic.
shutdown-check starts and stops the service itself. Do not start the same service in an earlier CI step or service container on the configured port.
GitHub Actions
name: Graceful shutdown
on:
push:
branches: [main]
pull_request:
jobs:
shutdown-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- name: Test graceful shutdown
run: npx shutdown-check test --json --junit shutdown-result.xml
- name: Save shutdown report
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: shutdown-check-report
path: shutdown-result.xml
if-no-files-found: ignore--json keeps the full structured result in the job log. Remove it if you
prefer the readable text timeline.
The artifact step uses if: !cancelled() so it still runs after a failed
check. if-no-files-found: ignore handles setup errors, which exit with code
2 before a JUnit result exists.
To show the result directly in the pull-request checks, pass
shutdown-result.xml to a JUnit reporter action instead of, or in addition
to, uploading it.
GitLab CI
shutdown-check:
image: node:22
script:
- npm ci
- npm run build
- npx shutdown-check test --junit shutdown-result.xml
artifacts:
when: always
reports:
junit: shutdown-result.xml
paths:
- shutdown-result.xmlGitLab reads the report and adds the test case to the merge request. The
artifact remains downloadable after failure because when: always is set.
Exit codes in CI
| Exit code | Meaning |
|---|---|
| 0 | passed |
| 1 | shutdown check failed |
| 2 | setup/configuration error |
| Code | CI meaning | JUnit file |
|---|---|---|
0 | The shutdown behavior passed | Written when requested |
1 | The test ran and a shutdown check failed | Written when requested |
2 | Arguments, config, or setup prevented the test | Not written |
Do not add || true to the shutdown command. That hides the exit code and can
let a broken deployment behavior merge.
Give parallel jobs separate ports
shutdown-check fails with SC001 if anything already owns
baseUrl. Give each job or matrix entry a different port and pass it to the
service through env.
{
"env": { "PORT": "3510" },
"baseUrl": "http://127.0.0.1:3510"
}For dynamic matrices, generate a small config in the job or call the Node API with a port derived from the matrix value. Do not run concurrent shutdown tests against the same database records or queue messages.
Choose CI timeouts
The job must allow enough time for:
- dependency installation and build;
readiness.timeoutMs;workload.started.timeoutMs;shutdown.deadlineMs;- a small process and runner overhead.
A test-runner timeout that ends first removes the useful shutdown result. Keep the outer timeout above the sum of the shutdown-check timeouts, while keeping the shutdown deadline realistic for production.
Diagnose a failed job
- Read the SC code in the first output line.
- Find the last successful timeline event.
- Read captured stderr and stdout.
- Open the page for the diagnostic code.
- Reproduce with the same build command and environment locally.
Use troubleshooting when the failure is timing-related or occurs before the service becomes ready.
Run through an existing test suite
If CI already publishes node:test or Vitest results, call checkShutdown()
from a test file. The existing runner can report the assertion, and you can
print the timeline on failure.
See tests with node:test and Vitest for complete examples and timeout guidance.
Supported runners
Use Linux or macOS with Node.js 22 or later. Windows runners are not supported because shutdown-check depends on POSIX signals and process groups. A Linux container runner is supported when it can start child processes and bind a local port.