Stop GPL Code at the Pull Request: A Licence Check in CI
The cheapest moment to deal with copied code is before it merges. Once it ships, a GPL function in a closed product means a release to pull back and a rewrite under pressure. In a pull request it means a red build and a few lines to replace.
remixware check is made for that moment. It scans the project, applies your policy, prints what breaks it, and exits with status 1, so the build stops. When nothing breaks the policy it prints PASS and exits with 0. A check that couldn’t do its job exits with 2 instead of passing: a file it can’t read, or a folder with no source files to scan, which usually means a wrong path or an --exclude that covers everything.

A Step for GitHub Actions
This job checks a Go project against the modules it depends on. For a JavaScript project, run npm ci and index node_modules instead; for Python, index the virtual environment.
name: Licence check
on: [pull_request]
jobs:
remixware:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: stable
- run: go mod download
- name: Install Remixware
run: |
mkdir -p "$HOME/bin"
curl -sSL https://github.com/softdevstudio/remixware/releases/latest/download/remixware_linux_amd64.tar.gz \
| tar -xz -C "$HOME/bin" ./remixware
echo "$HOME/bin" >> "$GITHUB_PATH"
- name: Check copied code
run: |
remixware index "$(go env GOMODCACHE)" --out deps.rmx --quiet
remixware check . --index deps.rmx --html remixware-report.html
- name: Keep the report
if: always()
uses: actions/upload-artifact@v4
with:
name: remixware-report
path: remixware-report.html
The report is kept even when the check fails, so whoever looks at the red build can open it and see the copied lines next to their source.
What Fails the Build
By default check fails on three statuses: conflict, no-license and attribution-missing. Findings marked review are listed but don’t fail it. A copy that needs a review and its notice, such as an MPL 2.0 file, counts as attribution-missing until your notices carry it, so the check still stops for the part that’s certain. A .remixware.json file in the project changes that:
{
"fail_on": ["conflict", "no-license"],
"ignore": ["testdata/**"],
"allow": [
{"component": "github.com/acme/*", "reason": "Acme gave us a commercial licence in 2025"}
]
}
Starting with conflicts and missing licences alone is a gentle way in: those are the findings that need code changed. Add attribution-missing once your notices file is up to date. remixware notice . --index deps.rmx --out THIRD_PARTY_NOTICES.md writes the missing entries for you, and the next check passes once you’ve filled in anything it couldn’t find, such as a copyright line.
Allow rules record decisions. Each needs a reason, and the reason shows up in the text, HTML and JSON reports, so a finding you accepted on purpose doesn’t look like one somebody missed.
Adding SCANOSS
--scanoss works in CI too, with one thing to plan for. Hosted runners are shared by many projects, so the free SCANOSS service’s usage limits may already be used up when your job asks. Keep a SCANOSS API key as a repository secret and pass it in:
- name: Check copied code
env:
SCANOSS_API_KEY: ${{ secrets.SCANOSS_API_KEY }}
run: |
remixware index "$(go env GOMODCACHE)" --out deps.rmx --quiet
remixware check . --index deps.rmx --scanoss --html remixware-report.html
Pull requests from forks don’t get repository secrets, so their checks ask the free service. If the service turns the check away, it exits with 2 and says why, so the build can’t pass without the answers.
A Clean Pass
On a project with nothing copied, remixware check . --index deps.rmx --quiet prints two lines:
No copied code found.
PASS: no findings break the policy (fail on: conflict, no-license, attribution-missing).
The reference lists every option of check, and the quick start shows the same steps by hand.