{"owner":"redis","repo":"go-redis","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md","AGENTS.md"],"files":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\nRepository guidance — layout, commands, architecture, and conventions — is\nmaintained in [`AGENTS.md`](AGENTS.md) so it stays shared across AI tools. It is\nimported below; edit `AGENTS.md`, not this file, for that content.\n\n@AGENTS.md\n\n## Claude-specific\n\n- Repo-local **skills** under `.claude/skills/` auto-trigger from their\n  descriptions — no need to invoke them manually (the set is listed in\n  `AGENTS.md`).\n- **Slash commands** under `.claude/commands/` (e.g. `/check-ci`) are available\n  in-session.\n- Architectural **specs** under `.claude/specs/` are read on demand; open the\n  relevant one before changing that subsystem.\n","AGENTS.md":"# AGENTS.md\n\nGuidance for AI coding agents (and humans) working in this repository. This is\nthe shared, tool-agnostic source of truth: Claude Code loads it through\n`CLAUDE.md` (which imports this file), and other agents (Codex, Cursor, Aider,\nZed, …) read `AGENTS.md` directly. Edit repository guidance here, not in\n`CLAUDE.md`.\n\n## Repository\n\ngo-redis is the official Redis client for Go. Module path:\n`github.com/redis/go-redis/v9` (Go 1.24+). The repo is a multi-module workspace\n— every directory containing a `go.mod` is built and tested independently:\n\n- root (`github.com/redis/go-redis/v9`) — the client library.\n- `extra/redisotel`, `extra/redisotel-native`, `extra/redisprometheus`,\n  `extra/rediscensus`, `extra/rediscmd` — instrumentation adapters with their\n  own module paths (so they can pin large telemetry deps without forcing them on\n  root consumers).\n- `internal/customvet` — custom `go vet` analyzers (also its own module).\n- `maintnotifications/e2e`, `doctests`, `fuzz`, examples under `example/` —\n  separate modules.\n\nThe Makefile iterates over every `go.mod` (`GO_MOD_DIRS`) when running\n`test.ci`, `go_mod_tidy`, etc. When you add a dependency in one module, you\nalmost never need to update the others.\n\n## Common commands\n\nTests run against a Redis stack started via Docker Compose. Profiles in\n`docker-compose.yml` control which services come up (`standalone`, `cluster`,\n`sentinel`, `all`, `e2e`).\n\n```sh\nmake docker.start                # bring up the full test stack (profile: all)\nmake docker.stop\nmake test                        # docker.start -> test.ci -> docker.stop\nmake test.ci                     # run tests assuming containers are already up\nmake test.ci.skip-vectorsets     # when REDIS_VERSION < 8\nmake bench                       # go test -bench=. (root module only)\nmake fmt                         # gofumpt + goimports -local github.com/redis/go-redis\nmake build\nmake go_mod_tidy                 # go mod tidy across every module\n```\n\nE2E (maintenance notifications) needs the extra `cae-resp-proxy` service:\n\n```sh\nmake test.e2e                    # starts e2e profile, runs ./maintnotifications/e2e/, tears down\nmake test.e2e.docker             # subset that runs inside docker\nmake test.e2e.logic              # logic-only tests, no proxy required\n```\n\nRun a single test. The root suite is Ginkgo-based (`bsm/ginkgo` + `bsm/gomega`\nforks), so `go test -run` matches the Go-level wrapper and you focus a spec with\nthe Ginkgo flag:\n\n```sh\ngo test -run TestGinkgoSuite . -ginkgo.focus=\"ZAdd\"\ngo test -run TestGinkgoSuite . -ginkgo.focus=\"cluster\"\n```\n\nPlain `go test` tests (most files outside the Ginkgo suite, e.g. `internal/...`,\n`maintnotifications/...`) work the usual way:\n\n```sh\ngo test -run TestConnStateMachine ./internal/pool/...\ngo test -race -run TestCircuitBreaker ./maintnotifications/...\n```\n\nEnv knobs (passed through the Makefile):\n\n- `REDIS_VERSION` — e.g. `8.8`. Drives both the test image tag and\n  `main_test.go` version-gating (`SkipBeforeRedisVersion` /\n  `SkipAfterRedisVersion`).\n- `CLIENT_LIBS_TEST_IMAGE` — full image ref, e.g.\n  `redislabs/client-libs-test:8.8-m03`.\n- `RE_CLUSTER=true` — run against a Redis Enterprise cluster instead of the\n  docker-compose stack (the suite then skips ring/sentinel/TLS-cluster setup).\n- `RCE_DOCKER=true` — Redis CE in docker (default for `make test`).\n- `REDIS_PORT` — override the default standalone port (`6380`).\n\nCI also runs the custom vet tool:\n`go vet -vettool ./internal/customvet/customvet ./...`. The `setval` analyzer\nrequires every `Cmder` with a `Result()` to also have a `SetVal()`.\n\n## Architecture\n\n### Client types (root package)\n\nAll clients are in the root package and share most plumbing:\n\n- `Client` (`redis.go`) — single-node client.\n- `ClusterClient` (`osscluster.go`) — Redis Cluster aware. `osscluster_router.go`\n  routes commands to the right shard; `internal/routing/` handles cluster-wide\n  aggregation policies (e.g. fan-out for `KEYS`, `DBSIZE`).\n- `Ring` (`ring.go`) — client-side sharding across independent Redis nodes\n  (consistent hashing, no cluster protocol).\n- Failover client (`sentinel.go`) — Sentinel-managed failover.\n- `UniversalClient` (`universal.go`) — wrapper that picks one of the above based\n  on options.\n\nCommand surface lives in topical files: `string_commands.go`, `hash_commands.go`,\n`stream_commands.go`, `search_commands.go`, `vectorset_commands.go`, etc. Each\nfile defines methods on the shared `Cmdable` interface so every client type gets\nthe same API.\n\n### Hooks (`redis.go` `hooksMixin`)\n\nThree hook chains run around every operation: `DialHook`, `ProcessHook`,\n`ProcessPipelineHook`. Hooks are registered via `client.AddHook(...)` and chain\nin FIFO order; each hook must call `next` to continue. When a hook wraps an\nerror, it must call `cmd.SetErr(wrappedErr)` so the typed-error helpers\n(`redis.IsLoadingError`, `IsMovedError`, etc. in `error.go`) keep working through\n`errors.As`. The README has a longer pipeline-hook example.\n\n### Connection pool (`internal/pool`)\n\nOwns dialing, idle/active connection bookkeeping, conn state (`conn_state.go`),\npubsub-conn lifecycle (`pubsub.go`), and the dial-retry/backoff logic that powers\n`DialerRetries` / `DialerRetryBackoff` (also exposed at `dial_retry_backoff.go`\nin the root). `OnConnect`, `MinIdleConns`, and the buffer-size options\n(`ReadBufferSize`/`WriteBufferSize`, default 32 KiB since v9.12) flow through\nhere.\n\n### Protocol (`internal/proto`)\n\nRESP2/RESP3 reader and writer. Push notifications (RESP3 `>`-prefixed frames) are\npeeked here and dispatched via the `push/` package. The `push.Registry` lets\ncallers register handlers for specific notification names;\n`maintnotifications/push_notification_handler.go` is how `maintnotifications`\nplugs in.\n\n### Maintenance notifications (`maintnotifications/`)\n\nThis is a non-trivial subsystem worth understanding before touching\ncluster/handoff code. It listens for RESP3 push notifications about cluster\nmaintenance (`MOVING`, `MIGRATING`, `MIGRATED`, `FAILING_OVER`, `FAILED_OVER`\nfor standalone; `SMIGRATING`, `SMIGRATED` for cluster) and performs seamless\nconnection handoff to new endpoints. Key pieces:\n\n- `manager.go` — coordinates state transitions.\n- `handoff_worker.go` — moves in-flight ops to new connections.\n- `pool_hook.go` — integrates with `internal/pool` to mark/replace connections.\n- `circuit_breaker.go` — backs off when the upstream is unhealthy.\n- `state.go` — per-connection state machine.\n- E2E coverage lives in `maintnotifications/e2e/` and drives a fault-injector /\n  RESP proxy (`cae-resp-proxy`).\n\nConfiguration is via `redis.Options.MaintNotificationsConfig`; modes are\n`ModeAuto` (default), `ModeEnabled` (require server support), `ModeDisabled`.\nRESP3 (`Protocol: 3`) is required.\n\n### Authentication (`auth/`, `internal/auth/streaming`)\n\nFour credential sources, in priority order: streaming provider (e.g. Entra ID\nvia `go-redis-entraid`), context-based provider, function provider, static\n`Username`/`Password`. The streaming provider is what enables token rotation\nwithout reconnecting — the listener in `auth/reauth_credentials_listener.go`\nissues `AUTH` on each refresh.\n\n### Internal helpers\n\n- `internal/hscan` — struct scanning for `HGETALL` results (`Scan` interface\n  re-exported as `redis.Scanner`).\n- `internal/hashtag` — extracts `{tag}` segments for cluster slot routing.\n- `internal/routing` — aggregator policies and shard pickers used by\n  `ClusterClient` for multi-shard commands.\n- `internal/otel` — small OpenTelemetry shim used to keep root free of telemetry\n  deps; full instrumentation lives in `extra/redisotel-native`.\n\n## Architectural specs\n\nRead the relevant design doc **before** changing code in that subsystem. They\ncover invariants and decisions that aren't obvious from the code, and are plain\nmarkdown any tool or editor can open:\n\n- `.claude/specs/pool.md` — connection pool: `wantConn` queue and FIFO\n  discipline, `ConnState` machine, dial retry/backoff, hook integration, the\n  re-auth/handoff coexistence contract.\n- `.claude/specs/cluster-routing.md` — slot computation, MOVED/ASK redirection,\n  request/response policies, aggregators, replica routing, topology reload,\n  cross-slot rules.\n- `.claude/specs/maintnotifications.md` — RESP3 push notification protocol, mode\n  handshake, per-conn state, handoff worker pool, circuit breaker, endpoint-type\n  resolution, cluster vs. standalone differences.\n\n## Conventions\n\n- New `Cmder` type → also implement `SetVal` (the custom vet `setval` check\n  enforces this; `SetErr` is on the embedded `baseCmd`).\n- Wrap errors with custom error types that implement `Unwrap`, or use\n  `fmt.Errorf(\"...: %w\", err)`. Always call `cmd.SetErr(...)` after wrapping so\n  typed-error checks still pass.\n- `gofumpt` + `goimports -local github.com/redis/go-redis` is the formatter\n  (`make fmt`); CI runs both.\n- Don't log directly — use `internal.Logger` (set via `redis.SetLogger`);\n  `logging.Disable()` is called in tests.\n- Version-gate Redis-version-specific tests with `SkipBeforeRedisVersion` /\n  `SkipAfterRedisVersion` rather than skipping at the suite level.\n\n### Commits and PRs\n\nConventional Commits, short and exact — `<type>(<scope>): <imperative summary>`.\nSubject ≤50 chars (hard cap 72), imperative (\"add\", not \"added\"), no trailing\nperiod. Body only when the *why* isn't obvious from the diff; wrap at 72.\n\n- Types: `feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `chore` (also\n  `build`, `ci`, `style`, `revert`).\n- Scope = the subsystem touched, lowercase: `pool`, `conn`, `pubsub`,\n  `sentinel`, `retry`, `command`/`cmd`, `vectorset`, `otel`, `streams`, `push`,\n  `deps`, `ci`, `tests`, `docs`. Omit only for genuinely cross-cutting changes.\n- Breaking change: `feat(scope)!: ...` plus a `BREAKING CHANGE:` body line.\n  Reference issues/PRs at the end — `Closes #42`, `Refs #17`.\n- **No AI-attribution trailer.** Do not add `Co-Authored-By: …`, \"Generated with\n  …\", or any AI-attribution line to commits or PR bodies in this repo.\n\n## Repo-specific tooling\n\n`.claude/` holds shared AI config:\n\n- `commands/` — slash commands (e.g. `check-ci`, which summarizes a PR's CI).\n- `skills/` — task playbooks: `testing`, `add-command`, `commit-style`,\n  `update-ci-image`, `prepare-release`.\n- `specs/` — the architecture docs listed above.\n\nFor Claude Code, the skills auto-trigger from their descriptions. For other\ntools, each `SKILL.md` is plain markdown you can open and follow directly.\n"}}