shutdown-check treats your service like a deployment platform does. It starts
the real command, sends HTTP traffic, delivers SIGTERM, and observes the
result from outside the process. It does not import your application or call
the shutdown handler directly.
What is a black-box shutdown test?
A black-box shutdown test uses only the service's public behavior: its command, port, HTTP routes, signal handling, and exit status. This covers problems that unit tests often miss, including shell wrappers, framework behavior, real sockets, process exit, and child processes.
The tradeoff is visibility. shutdown-check can see HTTP responses and process events, but it cannot know whether an internal database write or queue message finished unless the response proves it. See compatibility and limits for the exact boundary.
What happens during a run?
The stages always run in this order:
| Stage | What shutdown-check does | Main failures |
|---|---|---|
| 1 | Confirms the port is free | SC001 |
| 2 | Starts the configured command | SC002 |
| 3 | Waits for readiness | SC100–101 |
| 4 | Opens work and proves it is active | SC110–111 |
| 5 | Confirms the process and work are still alive | SC112 |
| 6 | Sends SIGTERM | SC113 |
| 7 | Optionally repeats the signal | SC114 |
| 8 | Optionally checks readiness and new-request rejection | SC310–312 |
| 9 | Waits for active responses | SC200–203 |
| 10 | Checks the process exit | SC300–301 |
| 11 | Confirms the port is closed | SC302 |
The first reported failure identifies the earliest useful problem after all required waits have settled. A pass is SC000.
1. Check that the port is free
Before launch, shutdown-check sends a short request to the readiness URL. The connection must be refused. Any response, timeout, or unclear network error means the port is not confirmed free and produces SC001.
This prevents a false pass against a server that was already running. It also makes parallel tests predictable: each test needs its own port.
2. Launch the real command
The command array is spawned directly without a shell. It runs in its own
process group, with cwd and env from the config.
{
"command": ["node", "dist/server.js"],
"cwd": ".",
"env": { "PORT": "3510", "NODE_ENV": "production" }
}Use the same entry point you use in production when possible. Starting the
server directly also avoids wrappers that swallow SIGTERM or exit before a
child server.
3. Wait for readiness
shutdown-check polls baseUrl + readiness.path until it receives the expected
status. Refused connections and other statuses are retried until
readiness.timeoutMs.
No workload is sent until readiness passes. This separates startup failures from shutdown failures.
4. Prove that work is active
The test sends one or more workload requests, then waits for the configured start barrier.
Response-headers barrier
Every request must receive response headers while its body is still open. Use this with streaming routes or controlled endpoints that flush headers before finishing.
Probe barrier
A separate route must change from inactiveStatus to activeStatus while the
workload request remains open. Use this when the handler sends its response
only after all work completes.
The check reports SC110 when the probe is already active, or SC111 when work cannot be confirmed. The in-flight work guide includes complete examples.
5. Recheck the process and requests
There is a small gap between the barrier passing and the signal being sent. shutdown-check verifies that the service and every workload request are still active in that gap. If either ended, the result is SC112.
This prevents a test from passing with a workload that was only barely slow enough to reach the barrier.
6. Send SIGTERM
shutdown-check sends SIGTERM to the exact process it launched. It does not
signal the whole process group at this stage. That detail exposes wrappers:
the launcher must forward the signal to the real server.
If the operating system refuses the signal, the result is
SC113. On success, the shutdown deadline begins and the
timeline records signal sent.
7. Repeat the signal when requested
When repeatSignalAfterMs is set, shutdown-check sends another SIGTERM only
if the process and original requests are still active. This checks that a
second signal does not force an early exit.
If the repeated signal cannot be tested or delivered, the result is
SC114. Keep the repeat delay shorter than both the
workload duration and shutdown.deadlineMs.
8. Check traffic draining
These checks are optional.
With readinessWithdrawal: true, shutdown-check polls the readiness route
after the signal. A non-ready status or refused connection counts as
withdrawn. A timeout does not. If readiness never changes, the result is
SC310.
With newRequests, the test then sends one new GET while the original work
is still active. The request must return a configured rejection status or be
refused when that behavior is allowed.
See readiness and draining for a server
that returns 503 during this window.
9. Wait for each active response
Every workload request must finish before shutdown.deadlineMs with the
configured status and optional body text.
| Result | Code |
|---|---|
| The request never ends | SC200 |
| The connection is reset or closed early | SC201 |
The final status differs from workload.status | SC202 |
The body lacks workload.bodyIncludes | SC203 |
Concurrent requests are checked individually. The first failing request in request order supplies the code.
10. Check process exit
The service must exit before the same shutdown deadline.
The default expected code is 0. A planned shutdown should normally look like
a clean exit to process managers and monitoring.
11. Confirm that the port closed
After the launched process exits, shutdown-check tries the readiness URL once more. The connection must be refused. If something still answers, a child server is probably still running and the result is SC302.
This is how the test catches npm start, shell scripts, or other launchers
that exit without stopping the actual server.
How are failures cleaned up?
After any result, shutdown-check closes its open requests and clears timers.
If the service or a child process remains, the tool sends SIGKILL to the
process group it created. Cleanup prevents a failed test from leaving the port
occupied for the next run.
Cleanup is not a passing shutdown. A service passes only when it finishes its own work, exits with the expected code, and closes the port before cleanup is needed.
How should I use the timeline?
Read it from top to bottom and find the last successful event before
check failed. For example:
- no
service readymeans startup or readiness failed; - no
work confirmed activemeans the start barrier failed; signal sentfollowed by no request completion means the drain hung;process exitedwhile the port stays open means a child server survived.
The output reference lists every event and the fields available in JSON.