microsoft/garnet · error · GarnetException
v3 RespCommand '{V3Order[i]}' (legacy AOF value {i + 1}) no
Error message
v3 RespCommand '{V3Order[i]}' (legacy AOF value {i + 1}) no longer maps to a current RespCommand. Renaming or removing a persisted (write) command breaks v3 AOF compatibility; add an explicit mapping. What it means
Thrown at static initialization time (BuildMap / EnsureInitialized) when a name in the LegacyRespCommand.V3Order table does not parse to a current RespCommand enum value AND is not listed in RemovedNonPersistedV3Commands. This is a fail-fast guard: renaming or removing a persisted (write) command breaks v3 AOF replay compatibility, so the build refuses to start rather than silently mis-replaying old AOF files.
Source
Thrown at libs/server/AOF/LegacyRespCommand.cs:101
{
// Size covers NONE (0) plus all v3 built-in members (values 1..V3Order.Length).
var map = new RespCommand[V3Order.Length + 1];
map[0] = RespCommand.NONE;
for (var i = 0; i < V3Order.Length; i++)
{
if (!Enum.TryParse<RespCommand>(V3Order[i], out var current))
{
// A read-only/admin command that has since been removed from the enum (e.g. folded into DEBUG
// as a subcommand). It was never persisted in the AOF (only write commands are), so its v3
// numeric slot is never read back; map it to NONE. Removing a persisted (write) command is NOT
// permitted here and still fails fast.
if (RemovedNonPersistedV3Commands.Contains(V3Order[i]))
{
map[i + 1] = RespCommand.NONE;
continue;
}
throw new GarnetException(
$"v3 RespCommand '{V3Order[i]}' (legacy AOF value {i + 1}) no longer maps to a current RespCommand. " +
"Renaming or removing a persisted (write) command breaks v3 AOF compatibility; add an explicit mapping.");
}
map[i + 1] = current;
}
return map;
}
/// <summary>
/// Translate a RespCommand value read from a v3 (pre-v4) AOF entry to the current numbering.
/// Custom raw-string command ids (anchored to <see cref="RespCommand.INVALID"/>) and any value
/// outside the built-in range are unchanged.
/// </summary>
internal static RespCommand FromV3(RespCommand v3Command)
{
var value = (ushort)v3Command;View on GitHub (pinned to 951b0fc683)
Solutions
- If the removed command was a write command persisted in the AOF, add an explicit mapping in BuildMap (or keep the old enum value as an alias) rather than deleting it.
- If the command was read-only/admin and never persisted, add its old name to the RemovedNonPersistedV3Commands HashSet (which maps it to RespCommand.NONE).
- Run LegacyRespCommand.EnsureInitialized() in unit tests to catch the regression before deployment.
- Review the PR diff against V3Order to ensure no persisted write command was renamed or removed without a mapping.
Example fix
// before: renamed READCMD to GETREAD in RespCommand, breaking v3 compat
// after: add the old name to the non-persisted set (if read-only)
private static readonly HashSet<string> RemovedNonPersistedV3Commands = new(StringComparer.Ordinal) { "PURGEBP", "READCMD" };
// OR if it was a persisted write command, keep an explicit alias mapping in BuildMap. Defensive patterns
Strategy: validation
Validate before calling
// In the test suite, force early initialization so the regression fails at test time, not in prod Garnet.server.LegacyRespCommand.EnsureInitialized(); // call in a startup/unit test // During development, after any RespCommand enum change, verify all V3Order entries resolve: // - read-only/admin removals -> add to RemovedNonPersistedV3Commands // - persisted write command rename/removal -> add explicit mapping in BuildMap
Prevention
- Call LegacyRespCommand.EnsureInitialized() in unit tests to fail-fast on startup regressions.
- When renaming or removing a RespCommand, immediately audit V3Order and update mappings.
- Add a CI check that diffs V3Order against RespCommand enum members.
- Treat persisted write-command renames as breaking changes requiring explicit migration.
When it happens
Trigger: A developer renames or deletes a write command in the RespCommand enum without either (a) adding it to RemovedNonPersistedV3Commands (only valid if it was never persisted) or (b) adding an explicit v3->current mapping in BuildMap. The exception fires at process startup when V3ToCurrent is first accessed.
Common situations: Refactoring RespCommand during Garnet development; merging a PR that renames a command without updating the legacy compat table; this is a build-time/source-level regression, not an operator error.
Related errors
- Unsupported AOF header version {header.aofHeaderVersion}; th
- AofSizeLimit cannot be enforced with disabled AOF!
- Cannot use CommitFrequencyMs or CommitWait without EnableAOF
- Need to set CommitFrequencyMs to -1 (manual commits) with Fa
- Cannot use CommitWait with manual commits
AI-assisted analysis of microsoft/garnet@951b0fc683 (2026-08-13).
Data as JSON: /api/errors/e4d45bfe0dfb0fe1.
Report an issue: GitHub.