Skip to content

Latest commit

 

History

History
228 lines (180 loc) · 10.3 KB

File metadata and controls

228 lines (180 loc) · 10.3 KB

AGENTS.md

Guidance for AI coding agents (and humans) working in this repository. This is the shared, tool-agnostic source of truth: Claude Code loads it through CLAUDE.md (which imports this file), and other agents (Codex, Cursor, Aider, Zed, …) read AGENTS.md directly. Edit repository guidance here, not in CLAUDE.md.

Repository

go-redis is the official Redis client for Go. Module path: github.com/redis/go-redis/v9 (Go 1.24+). The repo is a multi-module workspace — every directory containing a go.mod is built and tested independently:

  • root (github.com/redis/go-redis/v9) — the client library.
  • extra/redisotel, extra/redisotel-native, extra/redisprometheus, extra/rediscensus, extra/rediscmd — instrumentation adapters with their own module paths (so they can pin large telemetry deps without forcing them on root consumers).
  • internal/customvet — custom go vet analyzers (also its own module).
  • maintnotifications/e2e, doctests, fuzz, examples under example/ — separate modules.

The Makefile iterates over every go.mod (GO_MOD_DIRS) when running test.ci, go_mod_tidy, etc. When you add a dependency in one module, you almost never need to update the others.

Common commands

Tests run against a Redis stack started via Docker Compose. Profiles in docker-compose.yml control which services come up (standalone, cluster, sentinel, all, e2e).

make docker.start                # bring up the full test stack (profile: all)
make docker.stop
make test                        # docker.start -> test.ci -> docker.stop
make test.ci                     # run tests assuming containers are already up
make test.ci.skip-vectorsets     # when REDIS_VERSION < 8
make bench                       # go test -bench=. (root module only)
make fmt                         # gofumpt + goimports -local github.com/redis/go-redis
make build
make go_mod_tidy                 # go mod tidy across every module

E2E (maintenance notifications) needs the extra cae-resp-proxy service:

make test.e2e                    # starts e2e profile, runs ./maintnotifications/e2e/, tears down
make test.e2e.docker             # subset that runs inside docker
make test.e2e.logic              # logic-only tests, no proxy required

Run a single test. The root suite is Ginkgo-based (bsm/ginkgo + bsm/gomega forks), so go test -run matches the Go-level wrapper and you focus a spec with the Ginkgo flag:

go test -run TestGinkgoSuite . -ginkgo.focus="ZAdd"
go test -run TestGinkgoSuite . -ginkgo.focus="cluster"

Plain go test tests (most files outside the Ginkgo suite, e.g. internal/..., maintnotifications/...) work the usual way:

go test -run TestConnStateMachine ./internal/pool/...
go test -race -run TestCircuitBreaker ./maintnotifications/...

Env knobs (passed through the Makefile):

  • REDIS_VERSION — e.g. 8.8. Drives both the test image tag and main_test.go version-gating (SkipBeforeRedisVersion / SkipAfterRedisVersion).
  • CLIENT_LIBS_TEST_IMAGE — full image ref, e.g. redislabs/client-libs-test:8.8-m03.
  • RE_CLUSTER=true — run against a Redis Enterprise cluster instead of the docker-compose stack (the suite then skips ring/sentinel/TLS-cluster setup).
  • RCE_DOCKER=true — Redis CE in docker (default for make test).
  • REDIS_PORT — override the default standalone port (6380).

CI also runs the custom vet tool: go vet -vettool ./internal/customvet/customvet ./.... The setval analyzer requires every Cmder with a Result() to also have a SetVal().

Architecture

Client types (root package)

All clients are in the root package and share most plumbing:

  • Client (redis.go) — single-node client.
  • ClusterClient (osscluster.go) — Redis Cluster aware. osscluster_router.go routes commands to the right shard; internal/routing/ handles cluster-wide aggregation policies (e.g. fan-out for KEYS, DBSIZE).
  • Ring (ring.go) — client-side sharding across independent Redis nodes (consistent hashing, no cluster protocol).
  • Failover client (sentinel.go) — Sentinel-managed failover.
  • UniversalClient (universal.go) — wrapper that picks one of the above based on options.

Command surface lives in topical files: string_commands.go, hash_commands.go, stream_commands.go, search_commands.go, vectorset_commands.go, etc. Each file defines methods on the shared Cmdable interface so every client type gets the same API.

Hooks (redis.go hooksMixin)

Three hook chains run around every operation: DialHook, ProcessHook, ProcessPipelineHook. Hooks are registered via client.AddHook(...) and chain in FIFO order; each hook must call next to continue. When a hook wraps an error, it must call cmd.SetErr(wrappedErr) so the typed-error helpers (redis.IsLoadingError, IsMovedError, etc. in error.go) keep working through errors.As. The README has a longer pipeline-hook example.

Connection pool (internal/pool)

Owns dialing, idle/active connection bookkeeping, conn state (conn_state.go), pubsub-conn lifecycle (pubsub.go), and the dial-retry/backoff logic that powers DialerRetries / DialerRetryBackoff (also exposed at dial_retry_backoff.go in the root). OnConnect, MinIdleConns, and the buffer-size options (ReadBufferSize/WriteBufferSize, default 32 KiB since v9.12) flow through here.

Protocol (internal/proto)

RESP2/RESP3 reader and writer. Push notifications (RESP3 >-prefixed frames) are peeked here and dispatched via the push/ package. The push.Registry lets callers register handlers for specific notification names; maintnotifications/push_notification_handler.go is how maintnotifications plugs in.

Maintenance notifications (maintnotifications/)

This is a non-trivial subsystem worth understanding before touching cluster/handoff code. It listens for RESP3 push notifications about cluster maintenance (MOVING, MIGRATING, MIGRATED, FAILING_OVER, FAILED_OVER for standalone; SMIGRATING, SMIGRATED for cluster) and performs seamless connection handoff to new endpoints. Key pieces:

  • manager.go — coordinates state transitions.
  • handoff_worker.go — moves in-flight ops to new connections.
  • pool_hook.go — integrates with internal/pool to mark/replace connections.
  • circuit_breaker.go — backs off when the upstream is unhealthy.
  • state.go — per-connection state machine.
  • E2E coverage lives in maintnotifications/e2e/ and drives a fault-injector / RESP proxy (cae-resp-proxy).

Configuration is via redis.Options.MaintNotificationsConfig; modes are ModeAuto (default), ModeEnabled (require server support), ModeDisabled. RESP3 (Protocol: 3) is required.

Authentication (auth/, internal/auth/streaming)

Four credential sources, in priority order: streaming provider (e.g. Entra ID via go-redis-entraid), context-based provider, function provider, static Username/Password. The streaming provider is what enables token rotation without reconnecting — the listener in auth/reauth_credentials_listener.go issues AUTH on each refresh.

Internal helpers

  • internal/hscan — struct scanning for HGETALL results (Scan interface re-exported as redis.Scanner).
  • internal/hashtag — extracts {tag} segments for cluster slot routing.
  • internal/routing — aggregator policies and shard pickers used by ClusterClient for multi-shard commands.
  • internal/otel — small OpenTelemetry shim used to keep root free of telemetry deps; full instrumentation lives in extra/redisotel-native.

Architectural specs

Read the relevant design doc before changing code in that subsystem. They cover invariants and decisions that aren't obvious from the code, and are plain markdown any tool or editor can open:

  • .claude/specs/pool.md — connection pool: wantConn queue and FIFO discipline, ConnState machine, dial retry/backoff, hook integration, the re-auth/handoff coexistence contract.
  • .claude/specs/cluster-routing.md — slot computation, MOVED/ASK redirection, request/response policies, aggregators, replica routing, topology reload, cross-slot rules.
  • .claude/specs/maintnotifications.md — RESP3 push notification protocol, mode handshake, per-conn state, handoff worker pool, circuit breaker, endpoint-type resolution, cluster vs. standalone differences.

Conventions

  • New Cmder type → also implement SetVal (the custom vet setval check enforces this; SetErr is on the embedded baseCmd).
  • Wrap errors with custom error types that implement Unwrap, or use fmt.Errorf("...: %w", err). Always call cmd.SetErr(...) after wrapping so typed-error checks still pass.
  • gofumpt + goimports -local github.com/redis/go-redis is the formatter (make fmt); CI runs both.
  • Don't log directly — use internal.Logger (set via redis.SetLogger); logging.Disable() is called in tests.
  • Version-gate Redis-version-specific tests with SkipBeforeRedisVersion / SkipAfterRedisVersion rather than skipping at the suite level.

Commits and PRs

Conventional Commits, short and exact — <type>(<scope>): <imperative summary>. Subject ≤50 chars (hard cap 72), imperative ("add", not "added"), no trailing period. Body only when the why isn't obvious from the diff; wrap at 72.

  • Types: feat, fix, refactor, perf, docs, test, chore (also build, ci, style, revert).
  • Scope = the subsystem touched, lowercase: pool, conn, pubsub, sentinel, retry, command/cmd, vectorset, otel, streams, push, deps, ci, tests, docs. Omit only for genuinely cross-cutting changes.
  • Breaking change: feat(scope)!: ... plus a BREAKING CHANGE: body line. Reference issues/PRs at the end — Closes #42, Refs #17.
  • No AI-attribution trailer. Do not add Co-Authored-By: …, "Generated with …", or any AI-attribution line to commits or PR bodies in this repo.

Repo-specific tooling

.claude/ holds shared AI config:

  • commands/ — slash commands (e.g. check-ci, which summarizes a PR's CI).
  • skills/ — task playbooks: testing, add-command, commit-style, update-ci-image, prepare-release.
  • specs/ — the architecture docs listed above.

For Claude Code, the skills auto-trigger from their descriptions. For other tools, each SKILL.md is plain markdown you can open and follow directly.