What happens when you open a pull request

The checks that run on a pull request, what each one is actually testing, the extra approval step for forks, and the test image you get to try the change for real.

M
Written By M. Ibrahim (Admin)Last updated 9 days ago

Opening a pull request against main starts four workflows. This page explains what each one checks, so a red tick tells you something useful rather than just that something went wrong.

The four workflows

Workflow

What it does

When it runs

Test

Builds, tests, lints and format-checks both the server and the agent

Every pull request

Code quality

Biome on the frontend

Every pull request, unless the change only touches docker/

Security scan

CodeQL, secret scanning, Go vulnerability scanning

Every pull request

Build and Push Docker Images

Builds a real image you can run

Every pull request from the main repository. Fork pull requests need approval first, see below

There is also an npm audit, but it only runs when you have touched package.json, package-lock.json or frontend/package.json.

Test

Eight jobs run in parallel, four for the server and four for the agent:

Job

Equivalent locally

Test (server)

go test -race ./...

Lint (server)

golangci-lint

Format Check (server)

gofmt -l .

Build (server)

go build ./cmd/server

Agent Tests

go test -race ./... in agent-source-code

Agent Format Check

gofmt -l .

Agent Lint

golangci-lint

Agent Build

go build ./cmd/patchmon-agent

Tests run with -race, so a data race fails the build even when the test itself passes. Race conditions that never show up on your machine will surface here.

Both test jobs upload an HTML coverage report as a build artefact, which you can download from the workflow run page.

Running make check in server-source-code and agent-source-code covers the format, vet, lint and test jobs in one go. If that passes locally, Test should pass in CI.

One thing that surprises people: the four server jobs each build the frontend first, with npm ci and a Vite build, before touching Go. That is not redundancy for its own sake. The server embeds the compiled frontend into the binary with Go's embed, so the embed directory has to exist or the Go build fails. Even the lint and format jobs need it.

Code quality

Runs Biome across the repository in CI mode. Locally the equivalent is:

cd frontend
npx biome check --write src/
npx biome check src/

A pre-commit hook runs Biome too, so this rarely fails in CI unless the hook was bypassed.

This workflow skips changes that only touch docker/.

Security scan

Three jobs:

  • CodeQL static analysis, in four passes: the frontend TypeScript, the server Go, the agent Go, and the GitHub Actions workflows themselves. It uses the security-extended query set, which is broader than the default.
  • Gitleaks scans for committed secrets: API keys, tokens, private keys.
  • govulncheck checks the Go dependencies of both the server and the agent against the Go vulnerability database.

Findings appear under Security → Code scanning in the repository. The default view shows the default branch, so use the branch dropdown to select your pull request branch.

govulncheck is the one most likely to fail on a change you did not make: it reports newly disclosed vulnerabilities in existing dependencies. If it fails on a pull request that does not touch dependencies, that is usually what has happened, and it is not your fault.

The Docker test image

This is the useful one. Your change gets built into a real multi-architecture image and pushed to a staging registry, and a bot comments on the pull request with a ready-to-run compose snippet:

services:
  patchmon-server:
    image: cr.dev.patchmon.cloud/pr/patchmon-server:pr-123

The comment also gives a digest-pinned reference if you want an exact, reproducible pull.

Notes on how it behaves:

  • Pull request images go to a separate /pr/ namespace, so a test build can never be mistaken for a production image.
  • Nothing is pushed to the public GitHub registry from a pull request. Only main and release tags go there.
  • Pushing more commits updates the same comment rather than adding a new one, so the thread does not fill up with near-identical bot messages.
  • The image reports the next patch as a pre-release, for example 2.0.3-rc.61, rather than the last released version. See How versioning works.

Fork pull requests need approval

If you are contributing from a fork, the image build does not run straight away. A maintainer has to add the approved-for-build label first.

This is a security boundary, not a judgement on your change. The image build needs registry credentials, so it runs with access to repository secrets. Running that automatically against code from any fork would hand those credentials to anyone who opened a pull request. A maintainer reads the diff, then labels it.

The other three workflows run on fork pull requests immediately, because they need no secrets. So you get test, lint and security feedback right away, and the image once someone has looked.

If your fork pull request seems to be missing the Docker build, that is why. Ask on Discord if it has been sitting a while.

When something fails

  1. Open the failing job from the Checks tab and read the step that went red.
  2. Reproduce locally. make check covers most Test failures, npx biome check src/ covers Code quality.
  3. For CodeQL, look under Security → Code scanning and switch the branch dropdown to your branch.
  4. Push a fix. Everything reruns automatically, and the image comment updates in place.

A failure in a workflow you did not touch is worth mentioning in the pull request rather than force-pushing repeatedly at it. Dependency vulnerability alerts in particular are often nothing to do with your change.

Where to go next

Was this helpful?

Your feedback shapes what we write next.