CI integration

Exit codes

Semtest uses exit codes to signal results to CI systems:

Code Meaning
0 All tests passed
1 One or more tests failed
2 Error — LLM subprocess failure, invalid config, or validation issues in strict mode

Use these in pipeline conditionals to gate deployments or flag PRs.

Output formats for CI

Every semtest run produces two files by default, with a third available on opt-in:

JSON (ci-results.json)

Always generated. Machine-readable summary designed for scripts and custom tooling.

{
  "status": "fail",
  "summary": {
    "total": 5,
    "passed": 3,
    "failed": 1,
    "errored": 1
  },
  "tests": [
    {
      "id": "session-expiry",
      "sourceFile": "auth.spec.md",
      "status": "fail",
      "group": "auth",
      "location": "src/auth/session.ts"
    },
    {
      "id": "rate-limiting",
      "sourceFile": "api-routes.spec.md",
      "status": "pass",
      "group": "api"
    },
    {
      "id": "broken-test",
      "sourceFile": "broken.spec.md",
      "status": "error",
      "error": "LLM subprocess timed out after 120s"
    }
  ]
}

Fields like errored, invalid, and skipped in summary are omitted when zero. The location field appears only on failures, and error only on errors. The group field appears when a test is in a subdirectory.

JUnit XML (junit-results.xml)

Opt-in via --junit. Compatible with most CI test reporters — GitHub Actions, GitLab, Jenkins, CircleCI.

semtest run --junit

Markdown (latest.md)

Always generated. Human-readable report. Can be posted as a PR comment or uploaded as a build artifact.

Example: GitHub Actions

name: Semantic Tests
on: [push, pull_request]

jobs:
  semtest:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install semtest
        run: npm install -g @westopp/semtest

      - name: Run semantic tests
        run: semtest run --junit --strict --skip-permissions-if-possible

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: semtest-results
          path: semtest-results/

Example: GitLab CI

semtest:
  image: node:20
  script:
    - npm install -g @westopp/semtest
    - semtest run --junit --strict --skip-permissions-if-possible
  artifacts:
    when: always
    paths:
      - semtest-results/
    reports:
      junit: semtest-results/junit-results.xml

Skipping permission prompts

LLM CLIs often prompt for user confirmation before executing actions. In CI, these prompts block execution indefinitely. Use --skip-permissions-if-possible to auto-append each tool's bypass flag:

semtest run --skip-permissions-if-possible --strict --junit

This is strongly recommended for all CI runs. See Permission bypass for details on which tools support it.

Timeouts

In CI environments, set a timeout to prevent indefinite hangs if the LLM is slow or unresponsive:

semtest run --timeout 120000

This sets a 2-minute timeout per test. Timed-out tests receive error status and the run continues with remaining tests.

Strict mode

Use --strict in CI to catch validation issues — duplicate test IDs across files or scenarios the LLM marks as invalid:

semtest run --strict

Without --strict, validation issues appear in the report but don't affect the exit code. With --strict, they cause exit code 2, failing the pipeline.