

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1# AGENTS.md23Behavioural guidance for AI agents working in this repository. Reference4material for complex procedures lives next to the code — integration5testing is documented in [`cmd/hi/README.md`](cmd/hi/README.md) and6[`integration/README.md`](integration/README.md). Read those files7before running tests or writing new ones.89Headscale is an open-source implementation of the Tailscale control server10written in Go. It manages node registration, IP allocation, policy11enforcement, and DERP routing for self-hosted tailnets.1213## Interaction Rules1415These rules govern how you work in this repo. They are listed first16because they shape every other decision.1718### Ask with comprehensive multiple-choice options1920When you need to clarify intent, scope, or approach, use the21`AskUserQuestion` tool (or a numbered list fallback) and present the user22with a comprehensive set of options. Cover the likely branches explicitly23and include an "other — please describe" escape.2425- Bad: _"How should I handle expired nodes?"_26- Good: _"How should expired nodes be handled? (a) Remain visible to peers27 but marked expired (current behaviour); (b) Hidden from peers entirely;28 (c) Hidden from peers but visible in admin API; (d) Other."_2930This matters more than you think — open-ended questions waste a round31trip and often produce a misaligned answer.3233### Read the documented procedure before running complex commands3435Before invoking any `hi` command, integration test, generator, or36migration tool, read the referenced README in full —37`cmd/hi/README.md` for running tests, `integration/README.md` for38writing them. Never guess flags. If the procedure is not documented39anywhere, ask the user rather than inventing one.4041### Map once, then act4243Use `Glob` / `Grep` to understand file structure, then execute. Do not44re-explore the same area to "double-check" once you have a plan. Do not45re-read files you edited in this session — the harness tracks state for46you.4748### Fail fast, report up4950If a command fails twice with the same error, stop and report the exact51error to the user with context. Do not loop through variants or52"try one more thing". A repeated failure means your model of the problem53is wrong.5455### Confirm scope for multi-file changes5657Before touching more than three files, show the user which files will58change and why. Use plan mode (`ExitPlanMode`) for non-trivial work.5960### Prefer editing existing files6162Do not create new files unless strictly necessary. Do not generate helper63abstractions, wrapper utilities, or "just in case" configuration. Three64similar lines of code is better than a premature abstraction.6566## Quick Start6768```bash69# Enter the nix dev shell (Go 1.26.1, buf, golangci-lint, prek)70nix develop7172# Full development workflow: fmt + lint + test + build73make dev7475# Individual targets76make build # build the headscale binary77make test # go test ./...78make fmt # format Go, docs, proto79make lint # lint Go, proto80make generate # regenerate protobuf code (after changes to proto/)81make clean # remove build artefacts8283# Direct go test invocations84go test ./...85go test -race ./...8687# Integration tests — read cmd/hi/README.md first88go run ./cmd/hi doctor89go run ./cmd/hi run "TestName"90```9192Go 1.26.1 minimum (per `go.mod:3`). `nix develop` pins the exact toolchain93used in CI.9495## Pre-Commit with prek9697`prek` installs git hooks that run the same checks as CI.9899```bash100nix develop101prek install # one-time setup102prek run # run hooks on staged files103prek run --all-files # run hooks on the full tree104```105106Hooks cover: file hygiene (trailing whitespace, line endings, BOM),107syntax validation (JSON/YAML/TOML/XML), merge-conflict markers, private108key detection, nixpkgs-fmt, prettier, and `golangci-lint` via109`--new-from-rev=HEAD~1` (see `.pre-commit-config.yaml:59`). A manual110invocation with an `upstream/main` remote is equivalent:111112```bash113golangci-lint run --new-from-rev=upstream/main --timeout=5m --fix114```115116`git commit --no-verify` is acceptable only for WIP commits on feature117branches — never on `main`.118119## Project Layout120121```122headscale/123├── cmd/124│ ├── headscale/ # Main headscale server binary125│ └── hi/ # Integration test runner (see cmd/hi/README.md)126├── hscontrol/ # Core control plane127├── integration/ # End-to-end Docker-based tests (see integration/README.md)128├── proto/ # Protocol buffer definitions129├── gen/ # Generated code (buf output — do not edit)130├── docs/ # User and ACL reference documentation131└── packaging/ # Distribution packaging132```133134### `hscontrol/` packages135136- `app.go`, `handlers.go`, `grpcv1.go`, `noise.go`, `auth.go`, `oidc.go`,137 `poll.go`, `metrics.go`, `debug.go`, `tailsql.go`, `platform_config.go`138 — top-level server files139- `state/` — central coordinator (`state.go`) and the copy-on-write140 `NodeStore` (`node_store.go`). All cross-subsystem operations go141 through `State`.142- `db/` — GORM layer, migrations, schema. `node.go`, `users.go`,143 `api_key.go`, `preauth_keys.go`, `ip.go`, `policy.go`.144- `mapper/` — streaming batcher that distributes MapResponses to145 clients: `batcher.go`, `node_conn.go`, `builder.go`, `mapper.go`.146 Performance-critical.147- `policy/` — `policy/v2/` is **the** policy implementation. The148 top-level `policy.go` is thin wrappers. There is no v1 directory.149- `routes/`, `dns/`, `derp/`, `types/`, `util/`, `templates/`, `capver/`150 — routing, MagicDNS, relay, core types, helpers, client templates,151 capability versioning.152- `servertest/` — in-memory test harness for server-level tests that153 don't need Docker. Prefer this over `integration/` when possible.154- `assets/` — embedded UI assets.155156### `cmd/hi/` files157158`main.go`, `run.go`, `doctor.go`, `docker.go`, `cleanup.go`, `stats.go`,159`README.md`. **Read `cmd/hi/README.md` before running any `hi` command.**160161## Architecture Essentials162163- **`hscontrol/state/state.go`** is the central coordinator. Cross-cutting164 operations (node updates, policy evaluation, IP allocation) go through165 the `State` type, not directly to the database.166- **`NodeStore`** in `hscontrol/state/node_store.go` is a copy-on-write167 in-memory cache backed by `atomic.Pointer[Snapshot]`. Every read is a168 pointer load; writes rebuild a new snapshot and atomically swap. It is169 the hot path for `MapRequest` processing and peer visibility.170- **The map-request sync point** is171 `State.UpdateNodeFromMapRequest()` in172 `hscontrol/state/state.go:2351`. This is where Hostinfo changes,173 endpoint updates, and route advertisements land in the NodeStore.174- **Mapper subsystem** streams MapResponses via `batcher.go` and175 `node_conn.go`. Changes here affect all connected clients.176- **Node registration flow**: noise handshake (`noise.go`) → auth177 (`auth.go`) → state/DB persistence (`state/`, `db/`) → initial map178 (`mapper/`).179180## Database Migration Rules181182These rules are load-bearing — violating them corrupts production183databases. The `migrationsRequiringFKDisabled` map in184`hscontrol/db/db.go:962` is frozen as of 2025-07-02 (see the comment at185`db.go:989`). All new migrations must:1861871. **Never reorder existing migrations.** Migration order is immutable188 once committed.1892. **Only add new migrations to the end** of the migrations array.1903. **Never disable foreign keys.** No new entries in191 `migrationsRequiringFKDisabled`.1924. **Use the migration ID format** `YYYYMMDDHHMM-short-description`193 (timestamp + descriptive suffix). Example: `202602201200-clear-tagged-node-user-id`.1945. **Never rename columns** that later migrations reference. Let195 `AutoMigrate` create a new column if needed.196197## Tags-as-Identity198199Headscale enforces **tags XOR user ownership**: every node is either200tagged (owned by tags) or user-owned (owned by a user namespace), never201both. This is a load-bearing architectural rule.202203- **Use `node.IsTagged()`** (`hscontrol/types/node.go:221`) to determine204 ownership, not `node.UserID().Valid()`. A tagged node may still have205 `UserID` set for "created by" tracking — `IsTagged()` is authoritative.206- `IsUserOwned()` (`node.go:227`) returns `!IsTagged()`.207- Tagged nodes are presented to Tailscale as the special208 `TaggedDevices` user (`hscontrol/types/users.go`, ID `2147455555`).209- `SetTags` validation is enforced by `validateNodeOwnership()` in210 `hscontrol/state/tags.go`.211- Examples and edge cases live in `hscontrol/types/node_tags_test.go`212 and `hscontrol/grpcv1_test.go` (`TestSetTags_*`).213214**Don't do this**:215216```go217if node.UserID().Valid() { /* assume user-owned */ } // WRONG218if node.UserID().Valid() && !node.IsTagged() { /* ok */ } // correct219```220221## Policy Engine222223`hscontrol/policy/v2/policy.go` is the policy implementation. The224top-level `hscontrol/policy/policy.go` contains only wrapper functions225around v2. There is no v1 directory.226227Key concepts an agent will encounter:228229- **Autogroups**: `autogroup:self`, `autogroup:member`, `autogroup:internet`230- **Tag owners**: IP-based authorization for who can claim a tag231- **Route approvals**: auto-approval of subnet routes by policy232- **SSH policies**: SSH access control via grants233- **HuJSON** parsing for policy files234235For usage examples, read `hscontrol/policy/v2/policy_test.go`. For ACL236reference documentation, see `docs/`.237238## Integration Testing239240**Before running any `hi` command, read `cmd/hi/README.md` in full.**241Guessing at `hi` flags leads to broken runs and stale containers.242243Test-authoring patterns (`EventuallyWithT`, `IntegrationSkip`, helper244variants, scenario setup) are documented in `integration/README.md`.245246Key reminders:247248- Integration test functions **must** start with `IntegrationSkip(t)`.249- External calls (`client.Status`, `headscale.ListNodes`, etc.) belong250 inside `EventuallyWithT`; state-mutating commands (`tailscale set`)251 must not.252- Tests generate ~100 MB of logs per run under `control_logs/{runID}/`.253 Prune old runs if disk is tight.254- Flakes are almost always code, not infrastructure. Read `hs-*.stderr.log`255 before blaming Docker.256257## Code Conventions258259- **Commit messages** follow Go-style `package: imperative description`.260 Recent examples from `git log`:261 - `db: scope DestroyUser to only delete the target user's pre-auth keys`262 - `state: fix policy change race in UpdateNodeFromMapRequest`263 - `integration: fix ACL tests for address-family-specific resolve`264265 Not Conventional Commits. No `feat:`/`chore:`/`docs:` prefixes.266267- **Protobuf regeneration**: changes under `proto/` require268 `make generate` (which runs `buf generate`) and should land in a269 **separate commit** from the callers that use the regenerated types.270- **Formatting** is enforced by `golangci-lint` with `golines` (width 88)271 and `gofumpt`. Run `make fmt` or rely on the pre-commit hook.272- **Logging** uses `zerolog`. Prefer single-line chains273 (`log.Info().Str(...).Msg(...)`). For 4+ fields or conditional fields,274 build incrementally and **reassign** the event variable:275 `e = e.Str("k", v)`. Forgetting to reassign silently drops the field.276- **Tests**: prefer `hscontrol/servertest/` for server-level tests that277 don't need Docker — faster than full integration tests.278- **View types in read paths**: response serializers must read through279 `NodeView`/`UserView`/`PreAuthKeyView` accessors. `AsStruct()` clones the280 whole record on every read — it is only for DB-write/merge clones and mutable281 working copies, never to build an API response. `grep AsStruct hscontrol/api`282 must come back empty.283284## Gotchas285286- **Database**: SQLite for local dev, PostgreSQL for integration-heavy287 tests (`go run ./cmd/hi run "..." --postgres`). Some race conditions288 only surface on one backend.289- **NodeStore writes** rebuild a full snapshot. Measure before changing290 hot-path code.291- **`.claude/agents/` is deprecated.** Do not create new agent files292 there. Put behavioural guidance in this file and procedural guidance293 in the nearest README.294- **Do not edit `gen/`** — it is regenerated from `proto/` by295 `make generate`.296- **Proto changes + code changes should be two commits**, not one.297
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| vllm-project/vllmAGENTS.md · 89k | AGENTS.md | setuptestlint-formatstyle+5 | 100/100 | 14 days ago | |
| duckduckgo/content-scope-scriptsspecial-pages/AGENTS.md · 70 | AGENTS.md | buildteststylearch+3 | 100/100 | 14 days ago | |
| TryGhost/Ghoste2e/AGENTS.md · 55k | AGENTS.md | setupteststylearch+2 | 100/100 | today | |
| deepseek-ai/deepseek-harnessnative/landlock-run/AGENTS.md · 104k | AGENTS.md | setupteststylearch+3 | 100/100 | today | |
| aaif-goose/gooseAGENTS.md · 53k | AGENTS.md | setupbuildtestlint-format+7 | 100/100 | 8 days ago | |
| n8n-io/n8npackages/@n8n/agents/AGENTS.md · 201k | AGENTS.md | buildteststylearch+3 | 100/100 | 14 days ago | |
| code-yeongyu/oh-my-openagentpackages/web/AGENTS.md · 68k | AGENTS.md | setupbuildtestlint-format+6 | 100/100 | 13 days ago | |
| mui/material-uiAGENTS.md · 99k | AGENTS.md | setupbuildtestlint-format+9 | 100/100 | 14 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/juanfont-headscale-agents)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.