Skip to content

RedisKvStore.set() rejects any TTL that is not a whole number of seconds #1028

Description

@heeoneie

RedisKvStore.set() passes options.ttl.total("second") straight to Redis SETEX, which requires a whole number of seconds. Any Temporal.Duration that is not an exact number of seconds is rejected by the server, so the write fails instead of storing the value with a rounded expiry.

This is reachable from application code: KvStoreSetOptions.ttl is public API and accepts any Temporal.Duration. It also became easier to hit in 2.4, where FederationOptions.publicKeyTtl and FederationOptions.httpMessageSignaturesSpecTtl let an application choose the cache lifetimes directly (#1027). Fedify's own defaults are all whole seconds, so nothing in the library trips it today.

Environment

  • @fedify/redis 2.4.0 (main, commit 621958b)
  • Deno 2.9.5, macOS 26.6.2 (aarch64)
  • Redis 7 (redis:7-alpine in Docker)

Steps to reproduce

import { RedisKvStore } from "@fedify/redis/kv";
import { Redis } from "ioredis";

const store = new RedisKvStore(new Redis("redis://127.0.0.1:6379"));

await store.set(["k"], "v", { ttl: Temporal.Duration.from({ seconds: 1 }) });
await store.set(["k"], "v", { ttl: Temporal.Duration.from({ milliseconds: 500 }) });

Expected behavior

Both writes store the value. A sub-second TTL is expected to be normalized to the smallest expiry Redis can express rather than rejected.

Actual behavior

The first write succeeds. The second throws ReplyError: ERR value is not an integer or out of range.

The failure is not limited to sub-second durations. Every duration whose total is not an integer number of seconds fails the same way, and a zero duration fails with a different message:

TTL ttl.total("second") Result
1 second 1 stored
1500 milliseconds 1.5 ERR value is not an integer or out of range
500 milliseconds 0.5 ERR value is not an integer or out of range
1 millisecond 0.001 ERR value is not an integer or out of range
0 0 ERR invalid expire time in 'setex' command

Suggested fix

Normalize the argument to a positive integer before calling SETEX, so that a sub-second TTL becomes one second rather than an error. RedisKvStore has no cas() method, so set() is the only affected call site.

The adapter tests are gated on REDIS_URL and skip entirely without a live server, so a regression test for this needs a Redis instance to be meaningful.

AI usage disclosure

Per AI_POLICY.md: this report was AI-assisted. The problem was first raised by CodeRabbit on #1027; Claude Code drafted this report and ran the reproduction above against a local Redis 7 container. I checked the reproduction output and edited the text before filing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    component/kvKey–value store relateddriver/redisRedis driver (@fedify/redis)

    Type

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions