Files
caddy/AGENTS.md
Mohammed Al Sahaf bd9f145321 chore: add AGENTS.md (#7652)
* chore: add `AGENTS.md`

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

* Apply suggestions from code review

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>

* review feedback

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>

---------

Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com>
Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
2026-04-17 14:49:58 -06:00

6.8 KiB
Raw Permalink Blame History

Caddy Project Guidelines

Mission

Every site on HTTPS. Caddy is a security-first, modular, extensible server platform.

Code Style

Go Idioms

Follow Go Code Review Comments:

  • Error flow: Early return, indent error handling—not else blocks
    if err != nil {
        return err
    }
    // normal code
    
  • Naming: initialisms (URL, HTTP, ID—not Url, Http, Id)
  • Receiver names: 12 letters reflecting type (c for Client, h for Handler)
  • Error strings: Lowercase, no trailing punctuation ("something failed" not "Something failed.")
  • Doc comments: Full sentences starting with the name being documented
    // Handler serves HTTP requests for the file server.
    type Handler struct { ... }
    
  • Empty slices: var t []string (nil slice), not t := []string{} (non-nil zero-length)
  • Don't panic: Use error returns for normal error handling

Caddy Patterns

Module registration:

func init() {
    caddy.RegisterModule(MyModule{})
}

func (MyModule) CaddyModule() caddy.ModuleInfo {
    return caddy.ModuleInfo{
        ID:  "namespace.category.name",
        New: func() caddy.Module { return new(MyModule) },
    }
}

Module lifecycle: New() → JSON unmarshal → Provision()Validate() → use → Cleanup()

Interface guards — compile-time verification that modules implement required interfaces:

var (
    _ caddy.Provisioner     = (*MyModule)(nil)
    _ caddy.Validator       = (*MyModule)(nil)
    _ caddyfile.Unmarshaler = (*MyModule)(nil)
)

Structured logging — use the module-scoped logger from context:

func (m *MyModule) Provision(ctx caddy.Context) error {
    m.logger = ctx.Logger()
    m.logger.Debug("provisioning", zap.String("field", m.Field))
    return nil
}

Caddyfile support — implement UnmarshalCaddyfile(*caddyfile.Dispenser) using the Dispenser API:

// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
//
//     directive [arg1] [arg2] {
//         subdir value
//     }
func (m *MyModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
    d.Next() // consume directive name
    for d.NextArg() {
        // handle inline arguments
    }
    for nesting := d.Nesting(); d.NextBlock(nesting); {
        switch d.Val() {
        case "subdir":
            if !d.NextArg() {
                return d.ArgErr()
            }
            m.Field = d.Val()
        default:
            return d.Errf("unrecognized subdirective: %s", d.Val())
        }
    }
    return nil
}

Admin API: Implement caddy.AdminRouter for custom endpoints.

Context: Use caddy.Context for accessing other apps/modules and logging—don't store contexts in structs.

Architecture

Caddy is built around a module system where everything is a module registered via caddy.RegisterModule():

  • Apps (caddy.App): Top-level modules like http, tls, pki that Caddy loads and runs
  • Modules (caddy.Module): Extensible components with namespaced IDs (e.g., http.handlers.file_server)
  • Configuration: Native JSON with adapters (Caddyfile → JSON via caddyconfig/httpcaddyfile)
Directory Purpose
modules/ All standard modules (HTTP, TLS, PKI, etc.)
modules/standard/imports.go Standard module registry
caddyconfig/httpcaddyfile/ Caddyfile → JSON adapter for HTTP
caddytest/ Test utilities and integration tests
cmd/caddy/ CLI entry point with module imports

Critical Packages

caddyhttp and caddytls require extra scrutiny in code review—these are security-critical.

Quality Gates

All required before PR is merge-ready:

Gate Command Notes
Tests pass go test -race -short ./... Race detection enabled
Lint clean golangci-lint run --timeout 10m No warnings in changed files
Builds go build ./... Must compile
Benchmarks go test -bench=. -benchmem Required for optimizations

CI runs tests on Linux, macOS, and Windows—ensure cross-platform compatibility.

Build & Test

# Build
cd cmd/caddy && go build

# Tests with race detection (matches CI)
go test -race -short ./...

# Integration tests
go test ./caddytest/integration/...

# Lint (matches CI)
golangci-lint run --timeout 10m

Testing Conventions

Table-driven tests (preferred pattern):

func TestFeature(t *testing.T) {
    for i, tc := range []struct {
        input    string
        expected string
        wantErr  bool
    }{
        {input: "valid", expected: "result", wantErr: false},
        {input: "invalid", expected: "", wantErr: true},
    } {
        actual, err := Function(tc.input)
        if tc.wantErr && err == nil {
            t.Errorf("Test %d: expected error but got none", i)
        }
        if !tc.wantErr && err != nil {
            t.Errorf("Test %d: unexpected error: %v", i, err)
        }
        if actual != tc.expected {
            t.Errorf("Test %d: expected %q, got %q", i, tc.expected, actual)
        }
    }
}

Integration tests use caddytest.Tester:

func TestHTTPFeature(t *testing.T) {
    tester := caddytest.NewTester(t)
    tester.InitServer(`
    {
        admin localhost:2999
        http_port 9080
    }
    localhost:9080 {
        respond "hello"
    }`, "caddyfile")
    
    tester.AssertGetResponse("http://localhost:9080/", 200, "hello")
}

Use non-standard ports (9080, 9443, 2999) to avoid conflicts with running servers.

AI Contribution Policy

Per CONTRIBUTING.md, AI-assisted code MUST be:

  1. Disclosed — Tell reviewers when code was AI-generated or AI-assisted, mentioning which agent/model is used
  2. Fully comprehended — You must be able to explain every line
  3. Tested — Automated tests when feasible, thorough manual tests otherwise
  4. Licensed — Verify AI output doesn't include plagiarized or incompatibly-licensed code
  5. Contributor License Agreement (CLA) — The CLA must be signed by the human user

Do NOT submit code you cannot fully explain. Contributors are responsible for their submissions.

Dependencies

  • Avoid new dependencies — Justify any additions; tiny deps can be inlined
  • No exported dependency types — Caddy must not export types defined by external packages
  • Use Go modules; check with go mod tidy

Further Reading