Guidance for AI coding assistants working in fluxcd/pkg. Read this file before making changes.
These rules come from fluxcd/flux2/CONTRIBUTING.md and apply to every Flux repository.
- Do not add
Signed-off-byorCo-authored-bytrailers with your agent name. Only a human can legally certify the DCO. - Disclose AI assistance with an
Assisted-bytrailer naming your agent and model:Thegit commit -s -m "Add support for X" --trailer "Assisted-by: <agent-name>/<model-id>"
-sflag adds the human'sSigned-off-byfrom their git config — do not remove it. - Commit message format: Subject in imperative mood ("Add feature X" instead of "Adding feature X"), capitalized, no trailing period, ≤50 characters. Body wrapped at 72 columns, explaining what and why. No
@mentionsor#123issue references in the commit — put those in the PR description. - Trim verbiage: in PR descriptions, commit messages, and code comments. No marketing prose, no restating the diff, no emojis.
- Rebase, don't merge: Never merge
maininto the feature branch; rebase onto the latestmainand push with--force-with-lease. Squash before merge when asked. - Pre-PR gate:
make test-<module>must pass for every module you touched. Runmake tidyto tidy all affected modules. - Flux is GA: Backward compatibility is mandatory. These modules are consumed by all Flux controllers — breaking changes to exported APIs, function signatures, or behavior will be rejected. Design additive changes.
- Copyright: All new
.gofiles must begin with the Apache 2.0 boilerplate header. Update the year to the current year when copying. - Tests: New features, improvements and fixes must have test coverage. Follow existing patterns in the module you're modifying. Run tests locally before pushing.
Before submitting code, review your changes for the following:
- No unchecked I/O. Close HTTP response bodies, file handles, and archive readers in
deferstatements. Check and propagate errors from I/O operations. - No path traversal. The
tarmodule usescyphar/filepath-securejoin— always extract archives through it. Neverfilepath.Joinwith untrusted components without validation. - No command injection. Do not shell out via
os/exec. Use Go libraries for git, OCI, and cloud operations. - No hardcoded defaults for security settings. TLS verification must remain enabled by default.
- Error handling. Wrap errors with
%wfor chain inspection. Do not swallow errors silently. Return errors that help callers diagnose the issue without leaking internal state. - Resource cleanup. Ensure temporary files and directories are cleaned up on all code paths. Use
deferandt.TempDir()in tests. - No panics. Never use
panicin library code. Return errors and let callers decide how to handle them. - Thread safety. These packages are used in concurrent reconcilers. Do not introduce shared mutable state without synchronization.
- Minimal surface. Every exported type, function, and method is a backward-compatibility commitment consumed by multiple controllers. Minimize new exports.
fluxcd/pkg is the shared Go SDK for the Flux GitOps Toolkit. It is a multi-module monorepo — there is no top-level go.mod. Each subdirectory is its own independently versioned Go module, tagged separately (e.g. runtime/v0.103.0, ssa/v0.23.0, apis/meta/v1.26.0). All Flux controllers import specific modules from this repo.
The repository provides: controller runtime helpers, server-side apply engine, git operations, cloud auth/workload identity, OCI operations, kustomize building, artifact storage, and shared API types.
There is no top-level go.mod. Each directory is its own module:
apis/meta/— foundational API types: standard conditions (Ready,Stalled,Reconciling), reasons, annotations (ReconcileRequestAnnotation), artifact spec, dependency references.apis/event/— Flux event schema dispatched to notification-controller.apis/acl/— cross-namespace access control types.apis/kustomize/— Kustomize-related API types (e.g.HealthCheckExpressions).runtime/— largest module. Sub-packages:conditions,patch,reconcile,events,metrics,features,cel,acl,controller,dependency,errors,jitter,leaderelection,logger,object,predicates,probes,pprof,secrets,statusreaders,testenv,transform,client.ssa/— server-side apply engine (ResourceManager): apply, diff, wait, delete, change sets. Sub-packages:jsondiff,normalize,errors,utils.git/— git operations.gogit/sub-package is the concrete go-git implementation.repository/definesReader/Writerinterfaces.auth/— cloud workload identity:aws/,azure/,gcp/,generic/,githubapp/,utils/. CentralGetAccessToken()with caching.artifact/— artifact storage:config/,digest/,server/,storage/.oci/— OCI registry client (push, pull, tag, list, build, diff, delete).kustomize/— kustomize generator and variable substitution.filesys/provides secure filesystem implementations.cache/— generic in-memory cache (Cache[T],LRU[K,V], token cache helpers).http/fetch/— HTTP archive fetcher with retry and digest verification.http/transport/— HTTP transport utilities.tar/— secure tarball extraction (path traversal prevention).lockedfile/— atomic file operations with OS-level locking.masktoken/— token redaction for secure logging.envsubst/— variable expansion (${var}syntax with bash string manipulation support).chartutil/— Helm chart values merging from ConfigMaps/Secrets.sourceignore/— gitignore-style source filtering.ssh/— SSH host key scanning.version/— semantic version parsing/sorting.testserver/— base test server utilities.gittestserver/— in-process Git HTTP/SSH server for tests.helmtestserver/— in-process Helm chart repository server for tests.cmd/— internalflux-toolsbinary for release automation (not tagged/released).tests/integration/— cloud provider integration tests (not tagged).actions/— reusable GitHub Actions (helm, kubectl, kustomize, etc.).
This is the most important thing to understand about this repo:
- Every directory with a
go.modis an independent module. There are 24 taggable public modules. - Each module gets its own git tag in the form
<module-path>/v<semver>(e.g.runtime/v0.103.0,http/fetch/v0.15.0). - Sibling modules reference each other via
replacedirectives during development. For example,runtime/go.modhasreplace github.com/fluxcd/pkg/apis/meta => ../apis/meta. These replaces stay permanently — they enable local cross-module development without publishing intermediate tags. - External consumers (controllers) import specific tagged versions:
go get github.com/fluxcd/pkg/runtime@v0.103.0. - Changing one module may require updating dependents. If you modify
apis/meta, all modules that depend on it (e.g.runtime,ssa) may need their tests re-run. Themake prepcommand handles version bumps for releases.
All targets in the root Makefile. Module paths use : as separator in make targets (e.g. http/fetch → http:fetch).
make all— runstidy,generate,fmt,vetfor all modules.make test— runs tests for ALL modules sequentially.make test-chunk CHUNK=N/M— runs tests for a 1/M slice of modules (CI uses 4 parallel chunks).make test-<module>— runs tidy, generate, fmt, vet, thengo test ./... -race -coverprofile cover.outfor a single module. Examples:make test-runtime,make test-ssa,make test-http:fetch.make tidy/make tidy-<module>—go mod tidyfor all or one module.make generate/make generate-<module>—controller-gencodegen.make fmt/make vet— format and vet all modules.
Run a single test: make test-runtime (runs the full runtime module suite). For a specific test function within a module, cd into the module directory and run go test ./... -run TestName -v with KUBEBUILDER_ASSETS set if envtest is needed.
After changing API types or kubebuilder markers, regenerate:
make generate-<module>Generated files (never hand-edit):
*/zz_generated.deepcopy.go— in any module with API types.
No codegen output is committed at the top level. Each module manages its own generated files.
Load-bearing replace directives — do not remove:
- Sibling
replacedirectives (e.g.../apis/meta) in every module that depends on another module in this repo. These are permanent and required for local development. gopkg.in/yaml.v3 => gopkg.in/yaml.v3 v3.0.1— CVE fix present in multiple modules.opencontainers/go-digestfork — provides BLAKE3 support inartifactandhttp/fetch.
- Standard
gofmt. All exported names need doc comments. Match the style of the module you're editing. - Interface-first design. Key abstractions are interfaces (
repository.Reader/Writerin git,Providerin auth,Policerin controllers). Add implementations behind interfaces. - Condition helpers. Use
runtime/conditions(Get, Set, Merge, Patch) for status condition manipulation. Never manipulate condition slices directly. - Patch helper. Use
runtime/patch.Helperfor conflict-safe status patching. Create the helper before reconciliation, callPatch()at the end with owned conditions. - Events. Use
runtime/events.Recorderwhich posts to both the k8s API and notification-controller's HTTP endpoint. - Metrics. Standard names:
gotk_reconcile_condition,gotk_suspend_status, duration histogram. Useruntime/metrics.Recorder. - Feature gates. Use
runtime/features.FeatureGatesbacked by--feature-gatesCLI flag. - SSA. Use
ssa.ResourceManagerfor server-side apply. Do not useclient.Applydirectly. - Artifact storage. Use
artifact/storage.Storagewithlockedfilefor concurrency-safe writes. - No cross-module imports at test time only. If a test in module A needs a helper from module B, use the existing test server packages (
testserver,gittestserver,helmtestserver) which are designed for this.
- Tests use standard
go test ./... -race. The Makefile orchestrates per-module. - Modules that need Kubernetes use
runtime/testenv(wraps controller-runtime envtest).KUBEBUILDER_ASSETSmust point at downloaded kube-apiserver/etcd binaries (installed bymake install-envtest). - Test frameworks: mix of
onsi/gomega, and standardtesting. Match the module's existing style. - Git e2e tests live in
git/internal/e2e/(separate module, runs against real GitLab/Bitbucket). - Cloud integration tests live in
tests/integration/(separate module, Terraform-based). - Test servers (
gittestserver,helmtestserver,testserver) provide in-process fakes for git repos, Helm chart repos, and HTTP artifact servers.
- No top-level
go.mod. You cannotgo test ./...from the repo root. Always work within a specific module directory or usemake test-<module>. replacedirectives are permanent. They are not development leftovers — they are how sibling module development works. Do not remove them. Do not convert them to published versions.- Module versioning is independent. Changing
apis/metadoes not automatically bumpruntime. Theflux-tools prepcommand handles version propagation at release time. - Adding a new exported symbol is a cross-repo contract change. All Flux controllers depend on these modules. Renaming, removing, or changing the signature of any exported type/function breaks downstream consumers even if this repo's tests pass.
- Adding a new module requires updating
cmd/internal/enumerate_taggable_modules.goso the release automation knows to tag it. It also needs to be added toREADME.md. - The Makefile computes
MODULESdynamically by scanning forgo.modfiles. New modules are picked up automatically formake testandmake tidy. - Colon encoding in make targets.
http/fetchis targeted asmake test-http:fetch. This is deliberate — the Makefile translates:back to/internally. - envtest is needed by many modules. If you see
KUBEBUILDER_ASSETSerrors, runmake install-envtestfirst. runtimehas the most internalreplacedirectives (all fourapis/*modules). Changes to anyapis/*module should be tested withmake test-runtimeto catch breakage.ssahas zero dependency on otherfluxcd/pkgmodules. This is intentional — keep it that way to avoid circular dependencies.- Test modules (
cmd/,git/internal/e2e/,tests/integration/) are not tagged. They are excluded from release automation. Do not addreplacedirectives pointing at them from taggable modules.