Skip to content

feat(redis): implement COMMAND (INFO / COUNT / LIST / DOCS / GETKEYS)#607

Merged
bootjp merged 6 commits intomainfrom
feat/redis-command-command
Apr 25, 2026
Merged

feat(redis): implement COMMAND (INFO / COUNT / LIST / DOCS / GETKEYS)#607
bootjp merged 6 commits intomainfrom
feat/redis-command-command

Conversation

@bootjp
Copy link
Copy Markdown
Owner

@bootjp bootjp commented Apr 23, 2026

Motivation

COMMAND is issued at connect time by ~every modern Redis client
(go-redis, redis-py >= 4, ioredis, node-redis) for capability probing
and key-routing inference. elastickv previously rejected it as
unsupported command, inflating the "unknown" error bucket and
breaking client initialization in strict libraries that actually
consult COMMAND INFO output.

Companion to #594 (bounded unsupported-command metric) and #601
(HELLO). After this + HELLO land, those two commands should cover

95% of the previously-"unknown" error traffic.

Subcommand matrix

Implemented:

  • COMMAND — flat array of per-command info (6-element shape)
  • COMMAND COUNT — integer count of routed commands
  • COMMAND LIST — flat array of command names
  • COMMAND INFO [name ...] — per-command info array, nil per unknown
  • COMMAND DOCS [name ...] — minimal map-shaped doc entry per command
  • COMMAND GETKEYS <command> <args> — positional key extraction via
    per-command first_key/last_key/step

Explicitly rejected (returns ERR unsupported):

  • COMMAND GETKEYSANDFLAGS — Redis 7 key-specs shape we do not emit
  • COMMAND LIST FILTERBY MODULE|ACLCAT|PATTERN — elastickv has no
    modules and no ACL categories

Metadata table

Single source of truth: adapter/redis_command_info.go. One row per
routed command holding (arity, flags, first_key, last_key, step).
Adding a new command handler now takes three steps:

  1. Register the handler in RedisServer.route (adapter/redis.go).
  2. Add an argsLen entry (adapter/redis.go).
  3. Add a row to redisCommandTable in redis_command_info.go.

Forgetting step 3 is caught at CI time by
TestCommand_RouteMatchesTable. In production the runtime falls
through to a zero-metadata row and emits a single deduplicated log
warning per missing command name, so the command still appears in
COMMAND output (degraded) rather than vanishing entirely.

Flags follow a three-value taxonomy: readonly / write / admin.
denyoom / pubsub / stale etc. are intentionally not emitted —
no real client consults them for routing decisions.

Wire count, LIST length, and the bare-COMMAND reply are driven off
argsLen (the 1:1 route-keyed set) rather than the table, so the
three subcommands stay mutually consistent even during the brief
window when a new route has been added but the table row is pending.

Test plan

  • COMMAND COUNT returns len(argsLen) and equals len(redisCommandTable)
  • COMMAND (no args) returns an array of that length
  • COMMAND INFO GET — name/arity/flags/positions match spec
  • COMMAND INFO SET — arity=-3, flags contain "write"
  • COMMAND INFO nosuchcommand — nil entry, not an error
  • COMMAND INFO GET NOSUCH SET — 3 entries, middle is nil
  • COMMAND GETKEYS SET foo bar["foo"]
  • COMMAND GETKEYS DEL k1 k2 k3["k1","k2","k3"]
  • redisCommandGetKeys MSET-shape helper test (step=2)
  • COMMAND GETKEYS NOSUCH → error
  • COMMAND LIST — sorted names, length = count
  • COMMAND LIST FILTERBY MODULE foo — rejected
  • COMMAND DOCS GET — 4-element map-shaped entry
  • COMMAND BADSUB — "Unknown COMMAND subcommand"
  • COMMAND GETKEYSANDFLAGS — rejected
  • Route-wiring test confirms COMMAND dispatches to the handler
  • TestCommand_RouteMatchesTable — invariant that every routed
    command has a metadata row
  • golangci-lint run ./adapter/... ./monitoring/... is clean
  • go test -race -run 'TestCommand|TestRedisCommand' ./adapter/... ./monitoring/... passes

Summary by CodeRabbit

Release Notes

  • New Features
    • Added full support for the Redis COMMAND command, enabling client tools and monitoring solutions to query server capabilities. Clients can now probe supported commands and retrieve command metadata including argument requirements, key extraction, and documentation at connect-time.

Add the Redis `COMMAND` handler (COUNT / LIST / INFO / DOCS / GETKEYS
subcommands) so modern clients (go-redis, redis-py, ioredis) stop
polluting the unsupported-command metric at connect time. Metadata lives
in a single static table in adapter/redis_command_info.go so adding a
new command is a one-line row addition.

- Route `COMMAND` in RedisServer.route and register `-1` in argsLen.
- Extend monitoring.redisCommandSet so the metric shows the real name.
- Return 6-element INFO arrays (name, arity, flags, first/last/step) —
  enough for every client that consumes COMMAND output.
- GETKEYS walks the per-command first_key/last_key/step positionally.
- GETKEYSANDFLAGS and LIST FILTERBY variants are explicitly rejected.
- Routed-but-missing-from-table commands emit a one-shot warning and a
  zero-metadata fallback row so the omission is discoverable in prod;
  TestCommand_RouteMatchesTable is the hard CI gate.
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Warning

Rate limit exceeded

@bootjp has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 32 minutes and 45 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 32 minutes and 45 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4a50834e-9e1c-42dc-ada9-5931f688f8da

📥 Commits

Reviewing files that changed from the base of the PR and between 3632cdc and ccde868.

📒 Files selected for processing (4)
  • adapter/redis.go
  • adapter/redis_command_info.go
  • adapter/redis_command_specs.go
  • adapter/redis_command_test.go
📝 Walkthrough

Walkthrough

This PR implements support for the Redis COMMAND command, adding wire-up in the adapter, a comprehensive handler supporting COUNT, LIST, INFO, DOCS, and GETKEYS subcommands, metadata infrastructure for command inspection, and dedicated test coverage.

Changes

Cohort / File(s) Summary
Command Registration
adapter/redis.go, monitoring/redis.go
Registers the new COMMAND command by defining its constant, wiring argument count validation, and adding it to the recognized command set for metrics.
Command Handler Implementation
adapter/redis_compat_commands.go
Implements the COMMAND handler with support for COUNT (cardinality), LIST (routed names), INFO (per-command metadata arrays), DOCS (RESP2 flat-map output), and GETKEYS (key extraction with validation). Handles subcommand routing, error cases, and unsupported argument rejection.
Metadata Infrastructure
adapter/redis_command_info.go
Defines static metadata table and dispatch-time lookup logic for command inspection. Provides command-to-metadata resolution, key extraction using FirstKey/LastKey/Step semantics (supporting negative LastKey offsets), and deduplicated warning logging for missing metadata entries.
Test Suite
adapter/redis_command_test.go
Comprehensive tests covering COMMAND COUNT, COMMAND, COMMAND INFO (with known/unknown commands), COMMAND GETKEYS (including negative last_key handling), COMMAND LIST (with FILTERBY rejection), COMMAND DOCS (with mapping and ordering preservation), error handling, and metadata table completeness validation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • feat(redis): implement HELLO command #601: Adds a similar Redis command handler using the same pattern—updating adapter/redis.go for wire-up, implementing a compat handler in adapter/redis_compat_commands.go, and registering the command in monitoring/redis.go.

Poem

🐰 A redis command, oh what a sight!
COUNT, LIST, INFO—commands burning bright,
With DOCS and GETKEYS to light the way,
The metadata hops—hooray, hooray!
Comprehensive tests make the code feel right! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: implementing the Redis COMMAND family with its key subcommands (INFO, COUNT, LIST, DOCS, GETKEYS).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/redis-command-command

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 046169bd9b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adapter/redis_command_info.go Outdated
Comment on lines +234 to +236
if last < 0 {
// "to end": last arg index is len(argv)-1.
last = len(argv) - 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Interpret negative last-key offsets correctly

redisCommandGetKeys currently collapses every negative LastKey to len(argv)-1, but the table includes commands like BZPOPMIN with LastKey: -2 to exclude the trailing timeout argument. With the current logic, COMMAND GETKEYS BZPOPMIN k1 k2 0 incorrectly returns 0 as a key, which can break client key-routing decisions. Negative LastKey values need to be handled as relative offsets (e.g., len(argv)+LastKey) instead of always meaning “to end.”

Useful? React with 👍 / 👎.

Codex P1: redisCommandGetKeys collapsed every negative LastKey to
"to end" (len(argv)-1), but the command table includes entries like
BZPOPMIN with LastKey=-2 to EXCLUDE the trailing blocking timeout arg.
With the previous logic, COMMAND GETKEYS BZPOPMIN k1 k2 0 returned
0 as a key, which can misdirect client key-routing decisions.

Negative LastKey is now treated as an offset from the end: -1 is the
final arg, -2 is the second-to-last, and so on (len(argv)+last). This
matches the Redis key-specs semantics.

Tests pin the BZPOPMIN case end-to-end via the handler and a helper-
level test for synthetic -2 metadata, so a future change that reverts
to the "every negative means to end" shortcut trips CI.
@bootjp
Copy link
Copy Markdown
Owner Author

bootjp commented Apr 24, 2026

Addressed Codex P1 in 9a90b7e:

Interpret negative LastKey correctly in COMMAND GETKEYS.
redisCommandGetKeys previously collapsed every negative LastKey to len(argv)-1 ("to end"), but the command table uses entries like BZPOPMIN: LastKey=-2 to exclude the trailing blocking timeout argument. Under the old logic, COMMAND GETKEYS BZPOPMIN k1 k2 0 returned 0 as a key — which would misroute the command in a key-sharded client.

Negative LastKey is now treated as an offset from the end (len(argv)+last), matching the Redis key-specs semantics. -1 still means the final arg; -2 means second-to-last, and so on.

Two new regression tests pin the fix:

  • TestCommandGetKeys_BZPOPMIN_ExcludesTimeout — end-to-end through the handler.
  • TestRedisCommandGetKeysHelper_NegativeLastKeyOffset — synthetic -2 metadata against the helper directly.

/gemini review
@codex review

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a90b7edd4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adapter/redis_compat_commands.go Outdated
Comment on lines +336 to +337
conn.WriteArray(len(requested))
for _, raw := range requested {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return all docs for bare COMMAND DOCS

COMMAND DOCS is defined to return documentation for all commands when no command names are provided, but this implementation sizes the reply from len(requested) and iterates only requested, so COMMAND DOCS (without names) always returns an empty array. That makes the subcommand non-compliant and breaks clients/tools that rely on the default full-docs behavior.

Useful? React with 👍 / 👎.

Comment on lines +342 to +346
conn.WriteArray(docEntryLen)
conn.WriteBulkString("summary")
conn.WriteBulkString("")
conn.WriteBulkString("arguments")
conn.WriteArray(0)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Emit command-name keys in COMMAND DOCS replies

The RESP2 form of COMMAND DOCS is a flattened map where each key is the command name and each value is that command's doc map, but this code writes only the value map fields (summary/arguments) and never writes the command-name key. Clients decoding the response as a map (e.g., command name -> docs) will fail or see malformed data.

Useful? React with 👍 / 👎.

Codex P2 (x2) on COMMAND DOCS wire shape:

1. Bare COMMAND DOCS (no command names) previously returned an empty
   array — the implementation sized the reply from len(requested) and
   iterated only the requested slice. Real Redis returns docs for
   every command in that case (same pattern as bare COMMAND and bare
   COMMAND INFO), which is what redis-cli --docs and capability-
   probing clients rely on. Bare DOCS now iterates the routed set via
   routedRedisCommandMetas().

2. The RESP2 form of COMMAND DOCS is a flattened map: alternating
   command-name keys and 4-element doc-map values. Previously we wrote
   only the value slots (summary / arguments) and never the command-
   name key, so any client decoding the response as a name -> docs
   map saw malformed data. Now each entry is a (name, docs) pair;
   unknown commands emit (name, nil) preserving the flat-map layout.

Updated the existing TestCommandDocs_Get to assert the new shape
(outer array length 2, name bulk, then doc-map). Added
TestCommandDocs_BareReturnsAllDocs pinning the full-set default and
TestCommandDocs_UnknownReturnsNamedNil pinning the (name, nil) shape
for unknown commands.
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@bootjp
Copy link
Copy Markdown
Owner Author

bootjp commented Apr 24, 2026

Addressed Codex P2 (x2) in fb540a2:

Bare COMMAND DOCS now returns docs for all routed commands. The prior implementation sized the reply from len(requested) and iterated only the requested slice, so bare DOCS returned an empty array — non-compliant with real Redis and with how redis-cli --docs and capability-probing clients consume the reply. Bare DOCS now iterates routedRedisCommandMetas(), matching the bare-COMMAND / bare-COMMAND-INFO pattern. Pinned by TestCommandDocs_BareReturnsAllDocs.

Command-name keys are now emitted in the RESP2 flat-map shape. Redis replies to COMMAND DOCS with alternating (name, doc-map, name, doc-map, ...) pairs; previously we wrote only the doc-map slots, so any client decoding the response as a name -> docs map saw malformed data. Every entry now writes both the name bulk and the doc-map (or (name, nil) for unknown commands to preserve the flat-map layout). Existing TestCommandDocs_Get updated to pin the new shape; new TestCommandDocs_UnknownReturnsNamedNil covers the unknown-command pair.

/gemini review
@codex review

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@bootjp
Copy link
Copy Markdown
Owner Author

bootjp commented Apr 25, 2026

/gemini review
@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fb540a29b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +228 to +229
if meta.FirstKey <= 0 {
return nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle movable-key commands in COMMAND GETKEYS

redisCommandGetKeys returns nil as soon as FirstKey <= 0, which makes every movable-key command in the new table (EVAL, EVALSHA, XREAD) look like it has no keys. For example, COMMAND GETKEYS EVAL "..." 2 k1 k2 arg should return k1/k2, but this path falls through to ERR The command has no key arguments in writeCommandGetKeys. That breaks client key-routing flows that rely on server-side key extraction for scripted/dynamic commands.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the Redis COMMAND family of subcommands, including COUNT, LIST, INFO, DOCS, and GETKEYS, to support client capability probing. It introduces a centralized metadata table in adapter/redis_command_info.go that defines arity, flags, and key positions for all supported commands. The changes also include comprehensive unit tests and integration into the existing command routing and monitoring systems. I have no feedback to provide.

@bootjp bootjp enabled auto-merge April 25, 2026 11:58
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
adapter/redis_command_info.go (1)

196-206: Prefer slog over log.Printf for the missing-metadata warning.

The repo's logging guideline calls for slog with structured keys (e.g. command=…), which makes this warning easier to filter/route in production than the current free-form log.Printf line. Since this is a one-shot warn, the cost of switching is trivial.

♻️ Proposed slog refactor
 import (
-	"log"
+	"log/slog"
 	"sort"
 	"strings"
 	"sync"
 )
@@
 	if !warned {
-		log.Printf("redis-command: routed command %q has no entry in redisCommandTable; emitting zero-metadata fallback. Add a row to adapter/redis_command_info.go.", upper)
+		slog.Warn("routed command has no entry in redisCommandTable; emitting zero-metadata fallback",
+			"command", upper,
+			"hint", "add a row to adapter/redis_command_info.go")
 	}

As per coding guidelines: "Use slog for logging and maintain structured logging keys (e.g., key, commit_ts)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@adapter/redis_command_info.go` around lines 196 - 206, Replace the free-form
log.Printf call in warnMissingRedisCommandMeta with structured slog usage: call
slog.Warn (or slog.Logger.Warn) and include the command value as a structured
key (e.g., command=upper) and a short message about missing redis command
metadata; keep the one-shot guard logic around redisCommandFallbackWarnedOnce
and redisCommandFallbackWarnedOnceMu unchanged. Ensure the file imports slog if
not already present.
adapter/redis_command_test.go (2)

71-72: Optional: make WriteAny / WriteRaw flag unexpected use rather than silently dropping writes.

These are no-ops, which works for the current handler (it uses typed writers exclusively). However, a future refactor that switches any reply path to WriteAny would silently bypass every assertion in this file without any failing test. Since the tests already encode the precise wire shape, recording these as {op: "any"} / {op: "raw"} entries (or calling t.Errorf from inside them via a stored *testing.T) costs little and keeps the trace honest.

♻️ Minimal trace-instead-of-drop variant
-func (c *commandRecorder) WriteRaw([]byte)                {}
-func (c *commandRecorder) WriteAny(any)                   {}
+func (c *commandRecorder) WriteRaw(b []byte) {
+	c.writes = append(c.writes, commandRecorderEntry{op: "raw", s: string(b)})
+}
+func (c *commandRecorder) WriteAny(v any) {
+	c.writes = append(c.writes, commandRecorderEntry{op: "any", s: fmt.Sprintf("%v", v)})
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@adapter/redis_command_test.go` around lines 71 - 72, The no-op
implementations of commandRecorder.WriteRaw and commandRecorder.WriteAny should
not silently drop data; update commandRecorder to either store a testing.T
pointer or otherwise record these invocations so tests fail or reflect the call:
change WriteRaw to append an entry like {op:"raw", data:...} to the recorder's
trace (or call t.Errorf) and change WriteAny to append {op:"any", value:...} (or
similarly report an error) so any unexpected use is detectable; locate these
methods on the commandRecorder type and modify them to record the operation and
payload instead of doing nothing.

414-425: Tighten TestCommand_RouteMatchesTable to assert the route map directly.

The comment acknowledges the workaround: iterating argsLen instead of the actual route map. Since NewRedisServer’s setup is the source of truth for routing, you can extract the route registration into a small helper (buildRedisRoutes(*RedisServer) map[string]func(...)) shared between NewRedisServer and this test. That would catch a mismatch where a handler is added to route but forgotten in argsLen (the current test only catches the converse). Optional under chill review since TestCommandCount_MatchesTableSize already pins len(argsLen) == len(redisCommandTable).

The trailing _ = srv (Line 424) is dead — srv is never used; please drop it along with the no-op declaration on Line 416.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@adapter/redis_command_test.go` around lines 414 - 425,
TestCommand_RouteMatchesTable currently inspects argsLen instead of the actual
route map and leaves a dead srv variable; extract the route registration logic
used by NewRedisServer into a helper (e.g., buildRedisRoutes(*RedisServer)
map[string]handlerFunc) used by both NewRedisServer and this test, then change
TestCommand_RouteMatchesTable to iterate the map returned by
buildRedisRoutes(srv) and assert each route key exists in redisCommandTable
(checking for mismatches both ways), and remove the unused srv declaration and
the trailing "_ = srv" no-op.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@adapter/redis_command_info.go`:
- Around line 196-206: Replace the free-form log.Printf call in
warnMissingRedisCommandMeta with structured slog usage: call slog.Warn (or
slog.Logger.Warn) and include the command value as a structured key (e.g.,
command=upper) and a short message about missing redis command metadata; keep
the one-shot guard logic around redisCommandFallbackWarnedOnce and
redisCommandFallbackWarnedOnceMu unchanged. Ensure the file imports slog if not
already present.

In `@adapter/redis_command_test.go`:
- Around line 71-72: The no-op implementations of commandRecorder.WriteRaw and
commandRecorder.WriteAny should not silently drop data; update commandRecorder
to either store a testing.T pointer or otherwise record these invocations so
tests fail or reflect the call: change WriteRaw to append an entry like
{op:"raw", data:...} to the recorder's trace (or call t.Errorf) and change
WriteAny to append {op:"any", value:...} (or similarly report an error) so any
unexpected use is detectable; locate these methods on the commandRecorder type
and modify them to record the operation and payload instead of doing nothing.
- Around line 414-425: TestCommand_RouteMatchesTable currently inspects argsLen
instead of the actual route map and leaves a dead srv variable; extract the
route registration logic used by NewRedisServer into a helper (e.g.,
buildRedisRoutes(*RedisServer) map[string]handlerFunc) used by both
NewRedisServer and this test, then change TestCommand_RouteMatchesTable to
iterate the map returned by buildRedisRoutes(srv) and assert each route key
exists in redisCommandTable (checking for mismatches both ways), and remove the
unused srv declaration and the trailing "_ = srv" no-op.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0da4b01f-ac96-42e2-892b-10a49c20410d

📥 Commits

Reviewing files that changed from the base of the PR and between 4db754f and 3632cdc.

📒 Files selected for processing (5)
  • adapter/redis.go
  • adapter/redis_command_info.go
  • adapter/redis_command_test.go
  • adapter/redis_compat_commands.go
  • monitoring/redis.go

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3632cdc7b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread adapter/redis_command_info.go Outdated
Comment on lines +79 to +80
"HDEL": {Name: "hdel", Arity: -3, Flags: []string{redisCmdFlagWrite}, FirstKey: 1, LastKey: 1, Step: 1},
"HEXISTS": {Name: "hexists", Arity: 3, Flags: []string{redisCmdFlagReadonly}, FirstKey: 1, LastKey: 1, Step: 1},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add HELLO to COMMAND metadata table

redisCommandTable is missing a HELLO row even though HELLO is routed and has an argsLen entry in adapter/redis.go. Because writeCommandInfo looks up only this table, COMMAND INFO HELLO is emitted as nil (unknown command), and COMMAND GETKEYS HELLO ... is treated as invalid despite HELLO being supported. This makes capability probing inconsistent for clients that rely on COMMAND INFO to detect feature support.

Useful? React with 👍 / 👎.

…ble drift

Root-cause fix for the post-merge CI failure on this branch (TestCommand
_RouteMatchesTable / TestCommandCount_MatchesTableSize) and for the
recurring Codex P2 (HELLO missing from redisCommandTable). Both
symptoms shared the same underlying cause: command registration was
spread across THREE separate maps (r.route in redis.go, argsLen in
redis.go, redisCommandTable in redis_command_info.go) and adding a
new command required updating all three by hand. HELLO was added to
the first two on main but missed the third — silently breaking
COMMAND INFO HELLO and tripping the route-vs-table mirror test.

This refactor collapses the three views to a single source of truth.

Single-row registration (adapter/redis_command_specs.go):

- redisCommandSpecs is the canonical slice. Each row carries
  Constant / Name / Arity / Flags / FirstKey / LastKey / Step.
- argsLen is derived from the slice once at init (function-call
  package var initializer); same for redisCommandTable.
- buildRouteMap binds handlers per RedisServer and panics at server
  construction if any spec lacks a handler or any handler lacks a
  spec, so the surviving 2-way sync between specs and handlers is
  enforced at startup rather than at first request.

Why handlers stay separate from the spec slice:

- Embedding method values directly in the spec literal triggered a
  Go init cycle (redisCommandSpecs → method body → argsLen →
  redisCommandSpecs) because Go's init-order analysis follows
  function bodies. The buildRouteMap layout sidesteps the cycle
  while keeping the structural invariant under test.

Tests added:

- TestRedisCommandSpecs_NoDuplicateConstants pins the "every
  Constant appears exactly once" invariant — without it a duplicate
  spec would silently shadow earlier rows in the derived maps.
- TestRedisCommandSpecs_HELLOPresent is the explicit regression
  guard: HELLO must have a redisCommandTable row (the exact gap
  Codex P2 + TestCommand_RouteMatchesTable both flagged).
- The pre-existing TestCommand_RouteMatchesTable and
  TestCommandCount_MatchesTableSize now pass by construction; they
  remain as the perimeter check for "anything routed must be
  tabled".

Build / vet / lint clean. CI failures from the previous merge commit
(3632cdc) are eliminated by construction.
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@bootjp bootjp merged commit e055e98 into main Apr 25, 2026
8 checks passed
@bootjp bootjp deleted the feat/redis-command-command branch April 25, 2026 12:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant