# CI

> Run a graceful shutdown test in CI with shutdown-check: GitHub Actions and GitLab CI examples, JUnit reports, exit codes, and a dedicated port per job.

Source: https://shutdown.jscrate.dev/docs/ci
Last updated: 2026-09-23

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:

1. Run `npx shutdown-check test` locally.
2. Commit `shutdown-check.json`.
3. Point `command` at the built entry file.
4. Reserve a port for this test.
5. Use isolated database, queue, and file resources.
6. 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

```yaml title=".github/workflows/shutdown.yml"
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

```yaml title=".gitlab-ci.yml"
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.xml
```

GitLab 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](https://shutdown.jscrate.dev/docs/codes/sc001) if anything already owns
`baseUrl`. Give each job or matrix entry a different port and pass it to the
service through `env`.

```json title="shutdown-check.json"
{
  "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

1. Read the SC code in the first output line.
2. Find the last successful timeline event.
3. Read captured stderr and stdout.
4. Open the page for the diagnostic code.
5. Reproduce with the same build command and environment locally.

Use [troubleshooting](https://shutdown.jscrate.dev/docs/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](https://shutdown.jscrate.dev/docs/guides/test-runners) 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.

## Related

- [Quick start](https://shutdown.jscrate.dev/docs/quick-start)
- [Output and reports](https://shutdown.jscrate.dev/docs/output)
- [CLI reference](https://shutdown.jscrate.dev/docs/cli)
- [Tests with node:test and Vitest](https://shutdown.jscrate.dev/docs/guides/test-runners)
- [Troubleshooting](https://shutdown.jscrate.dev/docs/troubleshooting)
