> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scanoss.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Local pre-commit check with scanoss-cli

> A Git pre-commit hook that scans only the staged files with scanoss-cli and stops the commit when it finds open source you have not approved.

## Goal

Before each commit, scan the files you are about to commit. If they contain open source that
`scanoss.json` does not approve, stop the commit and show what the scan found. This catches copied
code before it reaches a pull request.

## When to use it

* Developers on your team paste code from the internet or from AI coding tools, and you want
  feedback before review.
* You want the same rule as your
  [pull request check](/en/latest/developer-tools/recipes/scan-pull-requests), but earlier.

The hook gives developers early feedback, but it cannot enforce anything. Anyone can skip it with
`git commit --no-verify`. Keep the pull request check as the gate.

## Prerequisites

* scanoss-cli on the developer's `PATH` (see [Quickstart](/en/latest/developer-tools/quickstart))

* An API key stored once on the workstation:

  ```bash theme={null}
  scanoss-cli config set api-key "<your-key>"
  ```

* `jq` and `bash`

* Network access to the SCANOSS API (or your own deployment) at commit time

* `scanoss/unapproved.jq` committed to the repository (see [Approve components](/en/latest/developer-tools/recipes/approve-components-bom-rules#how-it-fails-the-pipeline))

## The hook

Save this as `.git/hooks/pre-commit` in your repository and make it executable
(`chmod +x .git/hooks/pre-commit`).

```bash theme={null}
#!/usr/bin/env bash
# Scan the staged files with scanoss-cli before each commit.
# Skip once with: git commit --no-verify
set -euo pipefail

repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"

# 1. Copy the staged version of each added, copied, modified, or renamed
#    file into a temporary directory, keeping its path.
staged_dir="$(mktemp -d)"
results="$(mktemp)"
trap 'rm -rf "$staged_dir" "$results"' EXIT

git diff --cached --name-only -z --diff-filter=ACMR \
  | git checkout-index --prefix="${staged_dir}/" -z --stdin

if [ -z "$(ls -A "$staged_dir")" ]; then
  exit 0   # nothing to scan, for example a commit that only deletes files
fi

# 2. Apply the repository's approvals and skip rules.
settings_flag=""
if [ -f scanoss.json ]; then
  settings_flag="--settings=${repo_root}/scanoss.json"
fi

# 3. Scan. If the scan cannot run (no key, offline), say so and stop.
if ! scanoss-cli scan "$staged_dir" ${settings_flag:+"$settings_flag"} --output "$results"; then
  echo "SCANOSS pre-commit check could not run. Fix the error above, or skip once with: git commit --no-verify" >&2
  exit 1
fi

# 4. Stop the commit on unapproved open source.
#    scanoss/unapproved.jq is the filter from the "Approve components" recipe.
rules=scanoss.json; [ -f "$rules" ] || rules=/dev/null
unapproved="$(jq -c --slurpfile rules "$rules" -f scanoss/unapproved.jq "$results" \
  | jq -r '"  \(.purl)@\(.version // "unknown")  in: \([.evidence[]?.path] | join(", "))"')"

if [ -n "$unapproved" ]; then
  echo "SCANOSS found open source that is not approved in scanoss.json:" >&2
  echo "$unapproved" >&2
  echo "Approve it in scanoss.json, remove it, or skip once with: git commit --no-verify" >&2
  exit 1
fi
```

## What each step does

1. **Export the staged files.** `git diff --cached` lists the staged files.
   `git checkout-index` writes the staged version of each one into a temporary directory,
   not the version in your working tree. The hook checks exactly what the commit will contain,
   even if you staged only part of your changes.
2. **Settings.** The temporary directory has no `scanoss.json`, so the hook passes the
   repository's file with `--settings`. Paths keep their layout, so path-scoped rules still match.
3. **Scan.** scanoss-cli fingerprints the files locally and uploads only the fingerprints to the
   API. Your source code does not leave the machine. The scan uses the key stored by
   `scanoss-cli config set`.
4. **Gate.** The hook runs the same `jq` check as the pull request recipe. Any detected component
   without a `bom.identify` approval stops the commit.

## What the output means

```
SCANOSS found open source that is not approved in scanoss.json:
  pkg:github/madler/zlib@1.2.11  in: src/compress/inflate.c
Approve it in scanoss.json, remove it, or skip once with: git commit --no-verify
```

Each line names the matched component and the staged files where the scan found it. To continue:

* If you meant to include it, add a `bom.identify` rule to `scanoss.json` and stage that change
  too. See [Approve components with scanoss.json](/en/latest/developer-tools/recipes/approve-components-bom-rules).
* If you did not, remove the code and stage again.

## How it fails

Git aborts the commit when the hook exits with a non-zero code:

| Exit code | Meaning |
| - | - |
| `0` | Nothing staged to scan, or no unapproved components. The commit continues. |
| `1` | The scan could not run, or it found unapproved components. The commit stops. |

If you prefer the hook to warn instead of block when the API cannot be reached (for example, on
a train), change the `exit 1` in step 3 to `exit 0`. The pull request check still applies.

## Share the hook with your team

Git does not commit files in `.git/hooks`. To share the hook, commit it to the repository, for
example as `scripts/hooks/pre-commit`, and ask each developer to point Git at that directory once:

```bash theme={null}
git config core.hooksPath scripts/hooks
```

If your team already uses a hook manager, call the same script from it.

## Keep it fast

The hook scans only the staged files, so most commits finish in seconds. For large commits,
such as an initial import, skip the hook with `--no-verify` and rely on the pull request check.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.