mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-25 00:51:28 +02:00
[misc] add AGENTS.md file
Add the first version of the agents.md file and update the contributing guide to reflect expectations. the CLAUDE.md redirects agents to AGENTS.md
This commit is contained in:
505
AGENTS.md
Normal file
505
AGENTS.md
Normal file
@@ -0,0 +1,505 @@
|
||||
# NetBird Agent Guidelines
|
||||
|
||||
**NetBird** is an open-source connectivity platform: a WireGuard®-based overlay
|
||||
network with a control plane. The **agent** (`client/`) runs on user machines as
|
||||
a privileged daemon and manages the WireGuard interface, routing, firewall, and
|
||||
DNS. **Management** (`management/`) is the control plane and REST/gRPC API,
|
||||
**Signal** (`signal/`) brokers peer handshakes, **Relay** (`relay/`) carries
|
||||
traffic when a direct tunnel is impossible, and **Proxy** (`proxy/`) is the
|
||||
identity-aware proxy behind Agent Network.
|
||||
|
||||
This file applies to the whole repository, and is the single source of truth for
|
||||
agent guidance here. `CLAUDE.md` is a one-line pointer to it — keep the guidance
|
||||
in this file, not duplicated there.
|
||||
|
||||
## Contents
|
||||
|
||||
- [NetBird Agent Guidelines](#netbird-agent-guidelines)
|
||||
- [Contents](#contents)
|
||||
- [STOP and ask the user before](#stop-and-ask-the-user-before)
|
||||
- [Quick reference](#quick-reference)
|
||||
- [Structure](#structure)
|
||||
- [Where to look](#where-to-look)
|
||||
- [Repo-wide principles](#repo-wide-principles)
|
||||
- [Error handling](#error-handling)
|
||||
- [Comments](#comments)
|
||||
- [Testing](#testing)
|
||||
- [Pitfalls](#pitfalls)
|
||||
- [Commits, PRs, releases](#commits-prs-releases)
|
||||
- [After you push: CI and review bots](#after-you-push-ci-and-review-bots)
|
||||
- [Discussion and support](#discussion-and-support)
|
||||
|
||||
## STOP and ask the user before
|
||||
|
||||
- **Opening a pull request for anything beyond a trivial fix, without an agreed
|
||||
ticket.** Ask the user directly: *"Is there a discussion or issue for this
|
||||
change?"* NetBird is discussion-first — community reports start in
|
||||
[Discussions](https://github.com/netbirdio/netbird/discussions), DevRel
|
||||
validates them, and only validated discussions become issues. A PR that
|
||||
changes behavior with no linked issue may be closed on arrival. If there is no
|
||||
ticket, offer to draft the discussion post **instead of** the PR, and wait for
|
||||
the user's call. Only typos, broken links, documentation corrections, and
|
||||
one-line fixes that already have an issue can skip this.
|
||||
- **Designing in any high-risk area** (see
|
||||
[CONTRIBUTING.md](CONTRIBUTING.md#high-risk-areas)): public API and OpenAPI
|
||||
schema, gRPC protos, behavior existing deployments would notice after an
|
||||
upgrade, peer connectivity (ICE, NAT traversal, relay selection, WireGuard® or
|
||||
Rosenpass key handling), client system integration (routing, firewall, DNS,
|
||||
interface), authentication and authorization, CLI or service flags, config
|
||||
file format, daemon IPC, store schema and migrations, or a new feature. The
|
||||
design gets agreed in the ticket before code is written.
|
||||
- **Writing a store migration or changing a persisted model.** Migrations are
|
||||
one-way in the field and both the GORM and pgx paths may need the change.
|
||||
- **Hand-editing generated code.** `*.pb.go`, `*.gen.go`, and mocks are outputs.
|
||||
Edit the source (`.proto`, `openapi.yml`) and rerun the matching
|
||||
`generate.sh`.
|
||||
- **Adding, removing, or bumping a dependency**, and never vendor a fork.
|
||||
- **Weakening a security control** — authentication, authorization, certificate
|
||||
verification, privilege dropping, or peer identity checks — even when it is
|
||||
the fastest way to make a test pass.
|
||||
- **Force-pushing to `main`**, force-pushing any branch that is already under
|
||||
review, amending pushed commits, or bypassing hooks with `--no-verify`.
|
||||
|
||||
## Quick reference
|
||||
|
||||
```bash
|
||||
# Build
|
||||
go build ./...
|
||||
cd client && CGO_ENABLED=0 go build . # agent
|
||||
cd management && go build . # management service
|
||||
cd signal && go build . # signal service
|
||||
|
||||
# Verify (run before every push)
|
||||
go fmt ./...
|
||||
make lint # golangci-lint on files changed vs origin/main (also the pre-push hook)
|
||||
make lint-all # full-repository lint, matches CI
|
||||
make test-unit # host-safe unit tests, -tags devcert, no sudo
|
||||
make test-privileged # privileged-tagged suite in a Docker container with NET_ADMIN
|
||||
make setup-hooks # wire make lint into .githooks/pre-push
|
||||
|
||||
# Narrow runs
|
||||
go test ./client/internal/dns/...
|
||||
go test -race -run TestPeerConn ./client/internal/peer/...
|
||||
PRIV_RUN=TestNftablesManager PRIV_PKGS=./client/firewall/nftables/... make test-privileged
|
||||
|
||||
# Code generation (never hand-edit the output)
|
||||
./shared/management/http/api/generate.sh # REST types from openapi.yml
|
||||
./shared/management/proto/generate.sh
|
||||
./shared/signal/proto/generate.sh
|
||||
./client/proto/generate.sh
|
||||
./flow/proto/generate.sh
|
||||
|
||||
# Run locally
|
||||
sudo ./client/netbird up --log-level debug --log-file console
|
||||
./signal/signal run --log-level debug --log-file console
|
||||
./management/management management --log-level debug --log-file console --config ./management.json
|
||||
```
|
||||
|
||||
## Structure
|
||||
|
||||
```text
|
||||
netbird/
|
||||
├── client/ NetBird agent
|
||||
│ ├── cmd/ agent CLI
|
||||
│ ├── internal/ agent business logic (engine, peer, dns, routemanager, ...)
|
||||
│ ├── server/ daemon for background execution
|
||||
│ ├── proto/ daemon gRPC protos
|
||||
│ ├── iface/ WireGuard® interface management
|
||||
│ ├── firewall/ nftables, iptables, pf, WFP, userspace backends
|
||||
│ ├── ssh/ built-in SSH server and client
|
||||
│ ├── ui/ desktop UI (Wails v3 + React)
|
||||
│ ├── android/, ios/ mobile bindings
|
||||
│ ├── wasm/ WebAssembly build
|
||||
│ └── mdm/, system/ MDM policy, host information
|
||||
├── management/ control plane
|
||||
│ └── server/ account, peer, groups, networks, posture, permissions,
|
||||
│ settings, store, http (REST), idp, integrations, migration
|
||||
├── signal/ handshake broker (peer/, server/)
|
||||
├── relay/ relay service (protocol/, server/, healthcheck/)
|
||||
├── proxy/ identity-aware proxy (llm/, acme/, accesslog/, middleware/, tcp/, udp/)
|
||||
├── agent-network/ Agent Network overview
|
||||
├── shared/ imported by both agent and services
|
||||
│ ├── management/ proto/, client/, http/api (OpenAPI + generated types)
|
||||
│ ├── signal/ proto/, client/
|
||||
│ └── relay/, auth/, sshauth/, metrics/
|
||||
├── e2e/ end-to-end suites and harness
|
||||
├── encryption/, dns/, route/, stun/, sharedsock/, util/, flow/
|
||||
├── infrastructure_files/ docker compose and getting-started templates
|
||||
└── release_files/ files packaged into releases
|
||||
```
|
||||
|
||||
## Where to look
|
||||
|
||||
| Task | Location |
|
||||
| --------------------------- | ------------------------------------------------------------ |
|
||||
| REST API / OpenAPI | `shared/management/http/api/` + `management/server/http/` |
|
||||
| Management gRPC protocol | `shared/management/proto/` |
|
||||
| Signal protocol | `shared/signal/proto/` |
|
||||
| Daemon IPC protocol | `client/proto/` |
|
||||
| Peer connection and NAT | `client/internal/peer/` |
|
||||
| Network map handling | `client/internal/engine.go`, `shared/management/networkmap/` |
|
||||
| Routing | `client/internal/routemanager/`, `route/` |
|
||||
| Firewall backends | `client/firewall/` |
|
||||
| DNS | `client/internal/dns/`, `dns/` |
|
||||
| WireGuard® interface | `client/iface/` |
|
||||
| Persistence and migrations | `management/server/store/`, `management/server/migration/` |
|
||||
| IdP integrations | `management/server/idp/` |
|
||||
| Permissions model | `management/server/permissions/` |
|
||||
| LLM routing / Agent Network | `proxy/internal/llm/`, `agent-network/` |
|
||||
| End-to-end tests | `e2e/` |
|
||||
|
||||
## Repo-wide principles
|
||||
|
||||
1. **Run `go fmt` on every modified Go file.** Formatting is not optional.
|
||||
2. **Zero unaddressed diagnostics.** Fix IDE and linter warnings on code you
|
||||
touch, and delete imports, helpers, and parameters your refactor orphaned.
|
||||
Exception: unused parameters in shared code may be consumed by builds outside
|
||||
this repository — do not remove them, ask instead.
|
||||
3. **Function comments are mandatory for exported functions**, written as full
|
||||
sentences with a period, starting with the identifier name.
|
||||
4. **Prefer private functions and constants.** Export only what a caller outside
|
||||
the package genuinely needs.
|
||||
5. **Early returns and guard clauses.** Handle errors and edge cases first
|
||||
instead of nesting `if`/`else` chains.
|
||||
6. **Split complex functions.** If a function trips a complexity warning, break
|
||||
it into named helpers rather than silencing the warning.
|
||||
7. **Avoid LLM-slop tells:** em dashes, hedging narration, restating the diff in
|
||||
prose, trailing summaries. Defaults, not absolute bans. Applies to code,
|
||||
comments, commit messages, and PR descriptions alike.
|
||||
8. **Concurrency: do a two-pass race analysis after every change** that adds
|
||||
shared state. Guard maps and slices with a mutex, keep critical sections
|
||||
short, and run `go test -race` on the touched packages.
|
||||
9. **Cross-platform builds must keep working.** The agent targets Linux, macOS,
|
||||
Windows, FreeBSD, Android, and iOS. When you add a platform-specific file,
|
||||
add the counterpart or a build-tagged fallback for the others.
|
||||
10. **Never hand-edit generated files.** Change the source and regenerate.
|
||||
11. **Never log secrets** — private keys, setup keys, tokens, PAT values — and
|
||||
keep peer IPs and hostnames out of logs above debug level.
|
||||
|
||||
## Error handling
|
||||
|
||||
Use single-assignment form when the error is only needed inside the `if`:
|
||||
|
||||
```go
|
||||
// Good
|
||||
if err := someCall(); err != nil {
|
||||
return fmt.Errorf("context: %w", err)
|
||||
}
|
||||
|
||||
// Bad - unnecessary split
|
||||
err := someCall()
|
||||
if err != nil {
|
||||
return fmt.Errorf("context: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
Use multiple assignment when the value is needed after the block:
|
||||
|
||||
```go
|
||||
result, err := someCall()
|
||||
if err != nil {
|
||||
return fmt.Errorf("context: %w", err)
|
||||
}
|
||||
```
|
||||
|
||||
Add short, meaningful context, and **do not** start `fmt.Errorf` messages with
|
||||
obvious words like "failed to" or "error":
|
||||
|
||||
```go
|
||||
// Good
|
||||
return fmt.Errorf("parse remote address: %w", err)
|
||||
return fmt.Errorf("listen on %s: %w", addr, err)
|
||||
|
||||
// Bad
|
||||
return fmt.Errorf("failed to parse remote address: %w", err)
|
||||
return fmt.Errorf("error listening on %s: %w", addr, err)
|
||||
|
||||
// "failed" is fine in log messages
|
||||
log.Debugf("failed to parse remote address: %v", err)
|
||||
```
|
||||
|
||||
Skip the wrapping when a function only extracts or delegates and the wrap would
|
||||
add nothing:
|
||||
|
||||
```go
|
||||
func parseAddr(addr string) (string, int, error) {
|
||||
host, portStr, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Log the errors you choose not to act on:
|
||||
|
||||
- `log.Debugf()` for errors that do not affect program flow but help debugging.
|
||||
- `log.Tracef()` for very verbose errors that would otherwise spam logs.
|
||||
- **Never ignore** errors from writes, network sends, or critical cleanup.
|
||||
- Close errors may be ignored for read-only operations; log them at debug for
|
||||
writes.
|
||||
|
||||
## Comments
|
||||
|
||||
Comment the **why**, never the **what**. Default to no comment, and add one only
|
||||
when a hidden constraint or workaround would surprise a future reader. Never
|
||||
reference the current task, PR, or your own changes in a comment.
|
||||
|
||||
```go
|
||||
// Bad - trailing comments explaining the obvious
|
||||
defer localConn.Close() // Close the connection
|
||||
if err != nil { // Check if error occurred
|
||||
|
||||
// Good
|
||||
defer localConn.Close()
|
||||
|
||||
// Good - explains a non-obvious constraint
|
||||
// Use incremental checksum update per RFC 1624 for performance.
|
||||
checksum = updateChecksum(checksum, oldPort, newPort)
|
||||
```
|
||||
|
||||
### Length budget
|
||||
|
||||
- **90 characters per line.** Wrap the comment, do not run past it.
|
||||
- **250 characters per comment**, roughly three wrapped lines. Doc comments on
|
||||
exported identifiers may exceed it when the API genuinely needs the
|
||||
explanation; inline comments inside a function body may not.
|
||||
|
||||
The budget is a smell detector, not a rule to game. Do not compress a needed
|
||||
explanation into cryptic shorthand to fit — if a block of code needs more than
|
||||
250 characters of prose, the code is doing too much. Fix the code:
|
||||
|
||||
- **Extract a named function.** A well-named function replaces the comment: the
|
||||
name says *what*, the body shows *how*, and the comment you no longer write
|
||||
was the *what* anyway. Clean Code calls this "explain yourself in code".
|
||||
- **Extract a named constant or predicate.** `if isExpiredSetupKey(key)` needs
|
||||
no comment; `if key.ExpiresAt.Before(now) && !key.Revoked && key.UsageLimit > 0`
|
||||
does.
|
||||
- **Keep the surviving comment for the why** — the RFC, the kernel quirk, the
|
||||
ordering constraint. That part is usually one or two lines.
|
||||
|
||||
### Long switch and if/else chains
|
||||
|
||||
A `switch` whose cases carry multi-line explanations is the usual place this
|
||||
budget is breached, and the comment is a symptom. In order of preference:
|
||||
|
||||
1. **Extract each case body into a named function.** The case becomes one line,
|
||||
the name carries the meaning, and the switch reads as a table of contents.
|
||||
2. **Replace the switch with a lookup table** — `map[Kind]handlerFunc` — when the
|
||||
branches are uniform. Adding a case stops meaning editing a growing function.
|
||||
3. **Replace conditional with polymorphism** when branches vary by type and the
|
||||
same switch shape starts appearing in more than one place. Clean Code's rule
|
||||
of thumb: tolerate a switch statement if it appears **once**, is buried in a
|
||||
factory that returns an interface, and no other switch dispatches on the same
|
||||
type. A second switch over the same enum is the signal to introduce the
|
||||
interface.
|
||||
|
||||
Do not restructure a switch purely to satisfy the budget when the cases are one
|
||||
line each and self-evident — a flat, boring `switch` over an enum is fine and
|
||||
needs no comments at all.
|
||||
|
||||
Explanatory comments in tests are welcome — they document the scenario being set
|
||||
up, and the 250-character budget does not apply to them.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Unit tests** live beside the code as `_test.go`. `make test-unit` runs the
|
||||
host-safe set with `-tags devcert` and no sudo.
|
||||
- **Privileged tests** carry the `privileged` build tag and mutate host
|
||||
networking. They run through `make test-privileged`, inside a Docker container
|
||||
with `NET_ADMIN`. Never bypass that harness by running them directly on the
|
||||
host.
|
||||
- **End-to-end suites** live in `e2e/` with a shared harness.
|
||||
- **Test real behavior, not API existence.** Assert on the observable end state
|
||||
a consumer would see — bytes that arrived, the packet after translation, the
|
||||
row after the write — not merely that a method exists or returns an error.
|
||||
- **Avoid mocks for code we own.** Exercise the real store, manager, or
|
||||
controller and assert what the caller actually receives.
|
||||
- **`require` for setup and preconditions, `assert` for the conditions under
|
||||
test.** Use `require` whenever a later line would panic or be meaningless
|
||||
otherwise.
|
||||
- **Message guidance:** optional for `NoError`/`Error`; always give context for
|
||||
comparison, boolean, and collection assertions.
|
||||
|
||||
```go
|
||||
server, err := StartTestServer()
|
||||
require.NoError(t, err, "Test server setup must succeed")
|
||||
defer server.Close()
|
||||
|
||||
result, err := client.DoOperation()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, result, "Result should match expected")
|
||||
```
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **The agent runs as root.** Anything touching routing, firewall, DNS, or the
|
||||
interface can take a user's machine off the network. Prefer a reversible
|
||||
change and make sure cleanup runs on every exit path.
|
||||
- **Management has two account loaders** (GORM and pgx). Adding a relation to an
|
||||
account often means updating both, or it silently comes back empty in
|
||||
production.
|
||||
- **`go test ./...` without `-tags devcert` skips tests** that need the
|
||||
development certificate. Use `make test-unit`.
|
||||
- **`make lint` only checks the diff against `origin/main`.** CI runs
|
||||
`make lint-all`; run it too before pushing a large change.
|
||||
- **Protos are consumed by released clients.** An old agent must keep working
|
||||
against a new Management, so fields are added, never renumbered or removed.
|
||||
- **Windows requires the wintun driver**, and the daemon serves a named pipe
|
||||
(`npipe://netbird`) rather than loopback TCP. Loopback TCP carries no caller
|
||||
identity, so privileged operations are refused over it.
|
||||
|
||||
## Commits, PRs, releases
|
||||
|
||||
- **PR titles must start with a bracketed tag.** Before you propose a title,
|
||||
**read [`.github/workflows/pr-title-check.yml`](.github/workflows/pr-title-check.yml)
|
||||
and take the allowed tags from the `allowedTags` array in that file.** It is
|
||||
the only source of truth, it changes as components are added, and the check
|
||||
runs on every title edit — a tag that is not in that array is a red build. Do
|
||||
not rely on a list memorized from anywhere else, including this file.
|
||||
|
||||
```text
|
||||
[client] Authorize daemon IPC callers by their local identity
|
||||
[management,client] Add MDM policy support
|
||||
```
|
||||
|
||||
Multiple tags are comma-separated inside one pair of brackets. Match the tag
|
||||
to the component you actually changed, not to the one you read the most.
|
||||
|
||||
- **Use the repository's PR template.** Fill in
|
||||
[`.github/pull_request_template.md`](.github/pull_request_template.md) rather
|
||||
than replacing it with your own summary: describe the change, link the issue,
|
||||
tick the checklist honestly (including "ran locally" and "single purpose"),
|
||||
and complete the documentation section. Do not tick a box you have not
|
||||
verified, and do not delete rows that do not apply.
|
||||
|
||||
- **Keep the PR description short.** Under 1000 words on top of the template's
|
||||
own text, and usually far less — a few paragraphs. Reviewers read the diff;
|
||||
the description exists to explain what the diff cannot say for itself. This is
|
||||
well below what an agent will produce by default, so cut before you post.
|
||||
|
||||
- **Body: why before what.** Lead with the problem and the reason for this
|
||||
approach, then the shape of the change. No bullet list of files changed, no
|
||||
per-function walkthrough, no restating the diff in prose, no trailing summary
|
||||
section, no self-congratulatory closing line.
|
||||
|
||||
- **No `Co-Authored-By` or tool-attribution trailers in the PR description**,
|
||||
and none in commits either. Contributors own their contributions. Whatever
|
||||
tooling produced the diff, the person opening the PR is its author: they have
|
||||
read every line, they can explain why it works, they can answer review
|
||||
questions without going back to a model, and they are accountable for the
|
||||
consequences of merging it. Do not add a trailer, footer, or description line
|
||||
that spreads that ownership onto a tool.
|
||||
|
||||
- **Commit subjects follow the same `[scope] Subject` convention.** Keep the
|
||||
subject short, and use the body for why before what. No bullet lists of files
|
||||
changed.
|
||||
|
||||
- **Push review fixes as separate commits.** The PR is squashed on merge, so
|
||||
there is no reason to rewrite history mid-review; many small commits make the
|
||||
re-review readable.
|
||||
|
||||
- **Do not force-push a branch that is under review.** A force-push detaches
|
||||
existing review comments from the lines they were written against, destroys
|
||||
the "changes since your last review" diff a reviewer relies on, and discards
|
||||
the CI history that showed which commit broke what. Add commits instead —
|
||||
including for fixups and reverts. Force-push only when there is no
|
||||
alternative: a rebase to clear a genuine conflict, or removing a secret or a
|
||||
large binary that was committed by mistake. When you must, ask the user first,
|
||||
then say so in a PR comment so reviewers know their anchors moved. Never
|
||||
force-push `main`, and never force-push a branch you do not own.
|
||||
|
||||
- **One PR, one purpose.** Split refactors out of fixes and fixes out of
|
||||
features.
|
||||
|
||||
- **Keep the PR small.** Size is the single strongest predictor of how long a PR
|
||||
waits. Aim for **under ~400 changed lines across under ~20 files**; past
|
||||
roughly **1000 lines or 50 files** a community PR is likely to be sent back to
|
||||
be split, or left unreviewed until it is. Large PRs from outside the core team
|
||||
may be blocked outright when the size was never agreed in the ticket —
|
||||
reviewing a sprawling change against a privileged networking daemon is a
|
||||
security risk in itself, not just a time cost.
|
||||
|
||||
Judge the size by hand-written code: exclude generated output, `go.sum`,
|
||||
vendored files, and test fixtures from the estimate, but do not use their
|
||||
presence to argue a 3000-line PR is small.
|
||||
|
||||
When a change genuinely cannot be small — a protocol migration, a
|
||||
cross-component rename — agree the split in the ticket **before** writing
|
||||
code, and land it as a sequence of PRs that each build, test, and make sense
|
||||
on their own. Propose that split to the user rather than opening one large PR
|
||||
and hoping.
|
||||
|
||||
- **User-facing changes need a docs PR** in
|
||||
[netbirdio/docs](https://github.com/netbirdio/docs), linked from the PR
|
||||
description.
|
||||
|
||||
## After you push: CI and review bots
|
||||
|
||||
Opening the PR is not the end of the task. Watch the run, read what the bots
|
||||
say, and drive the PR to green before you report the work as done.
|
||||
|
||||
```bash
|
||||
gh pr checks <pr> --watch # all checks, live
|
||||
gh run view <run-id> --log-failed # only the failing steps
|
||||
gh pr view <pr> --comments # bot and human review comments
|
||||
```
|
||||
|
||||
**Never report a change as finished while checks are pending or red**, and never
|
||||
describe a red PR as passing. If you ran out of turn before CI finished, say
|
||||
which checks were still running.
|
||||
|
||||
### The checks
|
||||
|
||||
- **Go tests** — `golang-test-{linux,darwin,windows,freebsd}.yml`, sharded per
|
||||
component. A failure in a component you did not touch is usually a real
|
||||
interaction, not noise; read the log before assuming flake.
|
||||
- **golangci-lint** — `golangci-lint.yml` runs the full repository, while
|
||||
`make lint` only checks your diff. A clean local lint does not guarantee green
|
||||
CI on a large change.
|
||||
- **PR Title Check** — `pr-title-check.yml`, see above.
|
||||
- **Codecov** — uploaded from the Linux test workflow with per-component flags
|
||||
(`unit,client`, `unit,management`, `unit,relay`, `unit,proxy`, `unit,signal`,
|
||||
`integration,management`). Coverage on new code should not go backwards. Add
|
||||
tests for the paths you introduced; do not adjust thresholds or exclude files
|
||||
to clear the report.
|
||||
- **CodeRabbit** — configured in [`.coderabbit.yaml`](.coderabbit.yaml): `chill`
|
||||
profile, auto-review on every non-draft PR, TypeScript/JavaScript/SVG paths
|
||||
filtered out. Chat auto-reply is on, so `@coderabbitai` in a comment reaches
|
||||
it.
|
||||
- **SonarCloud** — project `netbirdio_netbird`, quality gate on new code (bugs,
|
||||
vulnerabilities, code smells, duplication, coverage).
|
||||
- **Snyk** — dependency and code scanning.
|
||||
|
||||
Sonar and Snyk report as GitHub App checks rather than workflows in this
|
||||
repository, so their detail lives on the PR check, not in the Actions logs.
|
||||
|
||||
### Handling bot findings
|
||||
|
||||
- **Read every comment and act on it.** Either fix it, or reply with the reason
|
||||
it does not apply. Do not bulk-resolve threads to clear the count, and do not
|
||||
silently ignore a finding because the check is advisory.
|
||||
- **Bots are frequently wrong here.** NetBird has privileged, platform-specific,
|
||||
and concurrency-heavy code that static analysis reads poorly. A confident
|
||||
CodeRabbit or Sonar comment can still be nonsense. Verify the claim against
|
||||
the code before you change anything — never edit correct code just to silence
|
||||
a bot.
|
||||
- **Security findings get the opposite default.** For a Snyk or Sonar
|
||||
vulnerability, or a CodeRabbit comment about authentication, authorization,
|
||||
certificate verification, or key handling, assume it is real until you have
|
||||
disproved it. Surface it to the user rather than dismissing it yourself.
|
||||
- **A new vulnerable dependency is a stop.** Bumping or replacing dependencies
|
||||
needs the user's decision, as above.
|
||||
- **Never change a workflow, threshold, lint exclusion, or bot config to make a
|
||||
check pass.** If a check is genuinely wrong, say so and let the user decide.
|
||||
- **Do not paper over flakes with blind re-runs.** Identify the failure first. If
|
||||
it is a known flake, name it; if you cannot tell, report it as unresolved
|
||||
rather than re-running until it goes green.
|
||||
|
||||
## Discussion and support
|
||||
|
||||
- Discussions: <https://github.com/netbirdio/netbird/discussions>
|
||||
- Slack: <https://docs.netbird.io/slack-url>
|
||||
- Docs: <https://docs.netbird.io>
|
||||
- Security: <https://github.com/netbirdio/netbird/security/policy> — never in public
|
||||
- Contribution process: [CONTRIBUTING.md](CONTRIBUTING.md)
|
||||
1
CLAUDE.md
Normal file
1
CLAUDE.md
Normal file
@@ -0,0 +1 @@
|
||||
See [AGENTS.md](AGENTS.md) for the agent guidelines in this repository.
|
||||
@@ -66,11 +66,41 @@ Typical bug fixes, internal refactors, documentation updates, and tests do not
|
||||
need a design discussion, but should still be tied to an issue so the work is
|
||||
visible and nobody duplicates it.
|
||||
|
||||
### Using AI coding agents
|
||||
|
||||
We have no policy for or against using an AI agent to write NetBird code. That
|
||||
choice is yours, and we are not going to interrogate anyone about their tools.
|
||||
|
||||
What we do have is a lot of incoming contributions that were plainly drafted with
|
||||
one, and enough experience reviewing them to see the same avoidable problems
|
||||
again and again: no ticket behind the change, a diff far too large to review, a
|
||||
description longer than the code it describes, an approach that was never going
|
||||
to be accepted, and an author who cannot answer questions about their own PR.
|
||||
None of that is caused by the tooling — it is what happens when a tool is pointed
|
||||
at a repository whose expectations it has never been told.
|
||||
|
||||
So rather than a rule, there is a guide. [AGENTS.md](AGENTS.md) restates the
|
||||
expectations from this document in the form agents read automatically
|
||||
(`CLAUDE.md` points to it), so pointing your tool at the repository is usually
|
||||
enough. Among other things it tells the agent to ask you for the
|
||||
discussion or issue before drafting a PR, to keep the change small and
|
||||
single-purpose, to run the tests locally, to use this repository's PR template
|
||||
and title tags, and to write a description a reviewer can get through.
|
||||
|
||||
The guardrails are the point, and they are the same ones we apply to everyone: an
|
||||
agreed ticket, a change you have actually run, a diff small enough to review with
|
||||
care, and an author who can explain it. Whatever wrote the diff, you are its
|
||||
author — you own every line you submit and the consequences of opening a PR with it.
|
||||
|
||||
We reserve the right to consider if it is worth to maintain the contribution and to ensure
|
||||
that what is merged aligns with our security standards and design expectations.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Contributing to NetBird](#contributing-to-netbird)
|
||||
- [Ticket first, PR second](#ticket-first-pr-second)
|
||||
- [High-risk areas](#high-risk-areas)
|
||||
- [Using AI coding agents](#using-ai-coding-agents)
|
||||
- [Contents](#contents)
|
||||
- [Code of conduct](#code-of-conduct)
|
||||
- [Directory structure](#directory-structure)
|
||||
|
||||
Reference in New Issue
Block a user