main and every pull request runs through a CI pipeline that exercises the full Go build and test suite. This page documents what runs, why it runs, and how to reproduce CI failures on your laptop.
Workflow
The CI pipeline is defined in.github/workflows/ci.yml. It runs on:
- Every push to
main - Every pull request targeting
main
What runs
Single ubuntu-latest runner. ~3-5 minutes total for a clean cache run; ~1-2 minutes with cache hit.Step-by-step
| Step | Command | What it catches |
|---|---|---|
| Tidy check | go mod download + go mod verify | Drift between go.mod, go.sum, and module cache. Catches forgotten go mod tidy runs. |
| Vet | go vet ./... | Static analysis: format-string mismatches, struct-tag errors, copylocks, unreachable code. |
| Build | go build ./... | Every package compiles. Catches type errors, missing dependencies. |
| Test | go test ./... -count=1 -timeout 5m | Full unit + integration test suite. The -count=1 flag bypasses Go’s test result cache so every run actually re-executes. |
Known issue: -race is currently disabled
The race detector (go test -race) is not enabled in CI right now. There are two known races that need fixing first:
internal/host/codex/appserver_test.go— a real fracta-owned race. The mock app server’s response struct is written from a background goroutine while the test reads it. Fixable with a mutex or channel.go-keyring v0.2.8mock provider — a third-party race ingithub.com/zalando/go-keyring’s test mock. The mock’s in-memory map isn’t mutex-protected. Used transitively byinternal/oauthtests. Not our bug; fix needs to come from upstream or via a library swap.
-race should be re-enabled in ci.yml. The flag adds ~2x runtime overhead but catches concurrency bugs that otherwise only surface under production load — worth re-enabling once the noise is gone.
In the meantime, you can run with race detection locally on a per-package basis:
-skip-dir is illustrative; Go doesn’t have that exact flag — use explicit package globs instead.)
Reproducing CI locally
CI does nothing magic — every step is reproducible on your machine.Full pipeline
Just the failing step
When CI fails, the workflow log shows which step. To drill in:| CI step failed | Local reproduction |
|---|---|
go mod verify | go mod tidy && git diff go.mod go.sum — commit any changes |
go vet ./... | go vet ./... — fix the reported issues |
go build ./... | go build ./... — read the error, fix the type / import |
go test ./... | Drill into the failing package: go test ./internal/orchestrator/... -run TestSpawnGoldenSubset -v -race |
A specific test
-v flag prints the names of subtests as they run, useful for narrowing down which assertion failed.
Test categories
Fracta’s tests fall into roughly four groups, all run bygo test ./...:
| Category | Convention | Example |
|---|---|---|
| Unit | *_test.go next to source | internal/contract/parse_test.go |
| Integration (in-process) | *_integration_test.go | internal/registry/reconciler_integration_test.go |
| Golden | testdata/*.golden.json checked vs runtime output | internal/orchestrator/spawn_golden_test.go |
| Wire-protocol | Tests that exercise the full MCP protocol surface | internal/mcpserver/checkpoint_integration_test.go |
*_e2e_test.go flavor — the K8s smoke test (scripts/k8s-smoke-test.sh) is the closest thing to a true end-to-end test, but it’s a shell script that runs outside the Go test runner and isn’t part of CI.
Test data conventions
- Goldens live alongside the test under
testdata/(per-test subdirectory + scenario name +.golden.jsonor.golden.txt). Update withgo test ./... -update(where the test supports an-updateflag — varies by package). - Fixtures for MCP protocol tests live in
testdata/directories. Read-only inputs. - Temp directories are created per-test via
t.TempDir(). Auto-cleanup; never leaked.
Skipping tests
Some tests require external services (FalkorDB, Postgres, Docker). They use build tags or environment-gatedt.Skip():
Caching
The CI workflow usesactions/setup-go@v5 with cache: true, which caches:
~/go/pkg/mod(downloaded module sources)~/.cache/go-build(compiled package artifacts)
go.sum — bumping a dependency invalidates the cache for that PR. Most PRs hit the cache and finish in ~1-2 minutes.
When CI is green but local is broken
Ifgo test fails locally but passes CI, check:
- Stale build cache:
go clean -testcache && go test ./... - Stale module cache:
go clean -modcache && go mod download - Different Go version:
go versionagainstgo.moddeclared version - Uncommitted changes:
git status— CI runs against the pushed commit, not your working tree - Different OS: CI runs Linux; some tests behave differently on macOS (e.g. file permission specifics, default temp dir locations)
When CI is broken but local is green
Less common but worth knowing:- Race detector finds something: the local run might have been
go test ./...(no-race); CI is-race. Try locally with-race. - Test depends on env: a test reads
$HOMEor$USERand your local values differ from CI’srunneruser. - Test depends on time/timezone: CI runs UTC; your machine probably doesn’t. Check for hardcoded timezone assumptions.
- Flaky test: re-run the failed CI job. If it passes, the test has a race or timing issue. Open an issue and fix the test rather than retrying.
GitHub Actions upkeep
The actions used inci.yml and release.yml ship as standalone projects with their own release cycles. Periodically (~quarterly) check for upstream deprecations:
- Node.js runtime deprecations. GitHub Actions runners deprecate Node.js major versions on an ~18-month cadence. Each deprecation is announced via a warning on the workflow run summary (“Node.js NN actions are deprecated”). When you see one, find the corresponding action’s latest major (e.g.
actions/setup-python@v6runs Node 24) and bump. - SHA pins vs floating majors. SHA pins (
@<long-sha>) are immutable and supply-chain-resistant but require manual bumps. Floating majors (@v6) auto-receive patch/minor updates within the major. The repo currently uses floating majors uniformly acrossactions/checkout,actions/setup-go,actions/setup-python,docker/*, andsoftprops/action-gh-release. Pick one and stick with it.
| Date | Action | What changed |
|---|---|---|
| 2026-05 | actions/setup-python | Bumped from v5-era SHA pin to @v6 to silence Node 20 deprecation warning. |
Future enhancements
Things not yet in CI that may be added later:- Lint (golangci-lint) — broader static analysis beyond
go vet. Currently optional locally; not enforced. - Coverage reporting —
go test -coverprofile, upload to Codecov or similar. - Benchmark drift detection — run
go test -benchon PRs and compare to baseline. - Integration tests against a real FalkorDB / Postgres — would require service containers in the CI workflow.
- Race detector — see Known issue:
-raceis currently disabled above.

