Skip to content

GitLab CI

Integrate CodeCharter into a GitLab pipeline as a job with SARIF upload.

GitLab has no official CodeCharter integration as a component, but a few lines of YAML are enough to run the CLI cleanly in the pipeline.

If your repository is hosted on GitHub and only the build runs on GitLab, you can additionally publish a branded CodeCharter check run and PR comment from this pipeline — see Publish checks from any CI.

Minimal setup

.gitlab-ci.yml:

codecharter:
  image: mcr.microsoft.com/dotnet/sdk:9.0
  stage: test
  before_script:
    - curl -sSL -H "Authorization: Bearer $CODECHARTER_API_KEY" -o codecharter.tar.gz https://codecharter.tools/api/v1/cli/linux-x64/latest
    - mkdir -p /opt/codecharter && tar -xzf codecharter.tar.gz -C /opt/codecharter
    - chmod +x /opt/codecharter/codecharter
    - export PATH="/opt/codecharter:$PATH"
  script:
    - codecharter analyze MySolution.sln --fail-on error --output sarif --output-file codecharter.sarif
  artifacts:
    when: always
    paths:
      - codecharter.sarif

Replace MySolution.sln with the path to your solution or project file — analyze expects a .sln, .slnx, or .csproj file, not a directory.

Getting an API key

  1. Generate a key in the portal under API Keys.
  2. In GitLab, go to Settings → CI/CD → Variables and store it as CODECHARTER_API_KEY with "Protected" and "Masked" enabled.

The variable does double duty: before_script uses it to download the CLI, and the analyze step uses it to automatically mint a short-lived license when none is cached. It must therefore be available to the whole job, which a GitLab CI/CD variable is by default.

SARIF and GitLab SAST

GitLab's artifacts.reports.sast does not ingest SARIF — it expects GitLab's own security report JSON schema, which CodeCharter does not produce. Pointing reports.sast at the SARIF file would surface nothing in the GitLab UI.

The SARIF file (SARIF 2.1.0) is still useful as a plain artifact: download it from the pipeline, feed it to SARIF-aware tools, or archive it for audits. To get findings onto merge requests, use the Code Quality converter under Merge request reports.

Caching

CodeCharter has no analysis-result cache, so there is no speedup to be had from caching analysis output. The one thing worth caching is the rule-bundle cache: when your project uses portal rule profiles (a .codecharter/codecharter.lock.json in the .codecharter/ directory), downloaded bundles are stored in .codecharter/cache. Persisting that directory skips re-downloading the bundles on warm runs:

codecharter:
  # ... as above ...
  cache:
    key: codecharter-$CI_COMMIT_REF_SLUG
    paths:
      - .codecharter/cache/

If your solution file is not at the repo root, adjust the path accordingly. Without portal profiles there is nothing to cache and you can skip this section.

Version pinning

Instead of /latest/, pull a specific CLI version:

- curl -sSL -H "Authorization: Bearer $CODECHARTER_API_KEY"
    -o codecharter.tar.gz
    https://codecharter.tools/api/v1/cli/linux-x64/1.0.12

Recommendation: always pin in CI, see Versioning.

Every download response carries an X-CodeCharter-Sha256 header. Compare it against the checksum of the downloaded archive if you want to verify download integrity in the job.

Self-hosted GitLab runners

A self-hosted runner needs access to codecharter.tools on every pipeline run — the before_script above downloads the CLI each time. No other changes are required: the minimal setup uses mcr.microsoft.com/dotnet/sdk from Microsoft's public registry. If your organisation mirrors Docker images internally, pull the .NET SDK image from your own mirror instead.

In air-gapped networks: mirror the CLI binary to the runner and use a local path instead of a curl download.

Merge request reports

GitLab's "Code Quality" section on MRs requires a JSON file in the GitLab Code Quality schema (different from SARIF). CodeCharter does not produce this format out of the box — you must write the converter yourself.

A minimal jq mapping from CodeCharter JSON output to the GitLab Code Quality schema looks like this:

codecharter analyze MySolution.sln --fail-on error --output json --output-file codecharter.json
jq '[.violations[] | {
  description: .message,
  check_name:  .ruleId,
  severity:    (if .severity == "error" then "major" elif .severity == "warning" then "minor" else "info" end),
  fingerprint: .fingerprint,
  location: { path: .filePath, lines: { begin: .lineNumber } }
}]' codecharter.json > codequality.json

Then wire the output file to the GitLab artifact:

artifacts:
  reports:
    codequality: codequality.json

The JSON output already contains a stable fingerprint per violation that does not depend on line numbers, so GitLab does not show duplicate annotations when unrelated lines shift.

Merge request pipelines: only changed lines

In MR pipelines you can restrict both the reported findings and the --fail-on gate to lines changed by the MR with --git-ref:

codecharter-mr:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  variables:
    GIT_DEPTH: 0
  script:
    - codecharter analyze MySolution.sln --fail-on error --git-ref $CI_MERGE_REQUEST_DIFF_BASE_SHA..HEAD

--git-ref runs git diff <range> --unified=0 internally, so the base commit must be present in the clone (hence GIT_DEPTH: 0).

For legacy codebases there is also baseline support: --write-baseline records the current findings once, and --baseline then makes output and --fail-on apply only to new findings.

Exit codes

For pipeline debugging, codecharter analyze uses these exit codes:

Code Meaning
0 No gating findings
1 Findings at or above the --fail-on level; without --fail-on, any finding at all
2 Usage error (invalid options, corrupt baseline, ...)
3 Solution or project could not be loaded
6 License error

If the job suddenly fails at the download or analyze step with HTTP 402, the subscription has expired — both the CLI download and the license minting return 402 in that case.

Pipeline variables

Useful variables available in the job:

Variable Usage
$CI_PROJECT_DIR Repo root
$CI_COMMIT_REF_SLUG Branch name for caching
$CI_MERGE_REQUEST_IID MR number when present
$CI_MERGE_REQUEST_DIFF_BASE_SHA MR base commit for --git-ref
$CODECHARTER_API_KEY Your secret