# Contributor handoff — fatedier/frp

> Canonical page: https://codevetter.com/unpack/frp/agent-handoff

# Contributor handoff — fatedier/frp

Scanned commit d20a232996007dfe6ab425abc0a39a3ae9a0889b (2026-09-10). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

Start with AGENTS.md for the command surface: `make build|frps|frpc|test|e2e|fmt|fmt-more|gci|vet`, `golangcici-lint run`, `make web`. Read cmd/frps/root.go + cmd/frpc/sub/root.go, then server/service.go and client/service.go for wiring, and server/control.go for the lifecycle/locking protocol before touching session code. Config changes must go through pkg/config/v1 types + validation and be tested for TOML/YAML/JSON and strict mode. Protocol changes must update pkg/msg on both sides and consider the v2 wire negotiation in pkg/proto/wire. E2E tests need built binaries and Ginkgo; unit tests run with the noweb tag when dashboards aren't built.
- AGENTS.md documents the canonical commands: make build/frps/frpc/test/e2e/e2e-trace/alltest/fmt/fmt-more/gci/vet, make web for dashboards, and points to doc/agents/ runbooks (release.md). — [AGENTS.md](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/AGENTS.md)
- First files to read: cmd/frps/root.go and cmd/frpc/sub/root.go (entrypoints), server/service.go and client/service.go (composition roots), server/control.go (session lifecycle/locking), pkg/config/v1 (config schema), pkg/msg/msg.go (protocol). — [cmd/frps/root.go](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/cmd/frps/root.go) [cmd/frpc/sub/root.go](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/cmd/frpc/sub/root.go) [server/service.go](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/server/service.go) [client/service.go](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/client/service.go) [server/control.go](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/server/control.go) [pkg/msg/msg.go](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/pkg/msg/msg.go)
- Build tags matter: 'frps'/'frpc' select per-binary code and 'noweb' is auto-added when web/*/dist is missing — run go test/vet with -tags noweb or build dashboards first. — [Makefile#L4](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/Makefile#L4) [Makefile#L40-53](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/Makefile#L40-L53)
- When editing server/control.go, respect the documented global lock order (runMu, cm.mu, ctl.lifecycleMu, then registry locks) and the pending/activated/running state machine — generation handoff uses barriers between replaced controls. — [server/control.go#L51-105](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/server/control.go#L51-L105) [server/control.go#L378-435](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/server/control.go#L378-L435)
- Config edits require updating v1 types, Complete() defaults, validation.Validate* and ideally e2e coverage; strict_config=true by default rejects unknown fields, so new fields break older parsers cross-version. — [cmd/frps/root.go#L46](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/cmd/frps/root.go#L46) [pkg/config/load.go#L176-217](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/pkg/config/load.go#L176-L217) [pkg/config/v1/validation](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/pkg/config/v1/validation)
- Client config now flows through source.Aggregator (ConfigSource + StoreSource); bypassing it breaks /api/reload and store CRUD. Keep Service.reloadMu semantics when touching UpdateConfigSource. — [cmd/frpc/sub/root.go#L137-183](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/cmd/frpc/sub/root.go#L137-L183) [client/service.go#L385-412](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/client/service.go#L385-L412) [pkg/config/source/aggregator.go](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/pkg/config/source/aggregator.go)
- Run `go test -tags noweb ./pkg/... ./client/... ./server/...` for unit tests; `make e2e` requires bin/frps+bin/frpc built first and ginkgo installed (script auto-installs). — [Makefile#L46-56](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/Makefile#L46-L56) [hack/run-e2e.sh](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/hack/run-e2e.sh)
- Don't add proxy types piecemeal: a type needs a configurer in proxyConfigTypeMap, marshal/unmarshal to msg, server factory init, client factory init, validation, and usually e2e — the CLI subcommand is then auto-generated. — [pkg/config/v1/proxy.go#L242-261](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/pkg/config/v1/proxy.go#L242-L261) [server/proxy/tcp.go#L26-28](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/server/proxy/tcp.go#L26-L28) [client/proxy/general_tcp.go#L23](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/client/proxy/general_tcp.go#L23) [cmd/frpc/sub/proxy.go#L45-87](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/cmd/frpc/sub/proxy.go#L45-L87)
- Auth token bytes double as the work-conn encryption key (ClientAuth/ServerAuth key = []byte(resolved.Token)) — changing token handling affects transport encryption. — [pkg/auth/auth.go#L58-61](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/pkg/auth/auth.go#L58-L61) [pkg/auth/auth.go#L112-115](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/pkg/auth/auth.go#L112-L115) [server/service.go#L468](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/server/service.go#L468)
- Version pinning/compat is enforced by tests: FRP_COMPAT_FLOOR_VERSION=0.61.0 and baseline-count e2e runs mean wire/protocol changes must remain compatible across ~8 released versions. — [Makefile#L5-6](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/Makefile#L5-L6) [Makefile#L61-68](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/Makefile#L61-L68)
- Logging uses pkg/util/log and xlog (context-scoped, prefixed by runID); follow existing xl.Debugf/Tracef patterns rather than fmt.Println in service code (entrypoints print user-facing errors via fmt). — [client/service.go#L330-332](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/client/service.go#L330-L332) [server/control.go#L331-334](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/server/control.go#L331-L334) [pkg/util/log](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/pkg/util/log)
- Panic-on-duplicate registries (plugin.Register, proxy factories) run in init() — import cycles or double registration crash at startup, so new plugins/types must use unique names/types. — [pkg/plugin/client/plugin.go#L33-39](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/pkg/plugin/client/plugin.go#L33-L39) [server/proxy/proxy.go#L43-47](https://github.com/fatedier/frp/blob/d20a232996007dfe6ab425abc0a39a3ae9a0889b/server/proxy/proxy.go#L43-L47)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
