You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Relay never recovers from a restarted Redis: EVALSHA without an EVAL fallback leaves it serving nothing #1002
On 2026-10-31 a production relay on ghcr.io/cameri/nostream (v3.0.0, Node 24.20.0) was left unable to answer a single REQ after its Redis cache was restarted. The containers were healthy, the HTTP endpoint answered 200, and every websocket upgrade succeeded — then the socket said nothing, forever. The client sees a hang, not an error.
The log, in order:
uncaught error: Error: connect ECONNREFUSED 172.18.0.2:6379
web-socket-adapter: unable to handle message: [ErrorReply: NOSCRIPT No matching script. Please use EVAL.]
uncaught error: [ErrorReply: NOSCRIPT No matching script. Please use EVAL.]
The first line is the swap itself and is expected. The NOSCRIPT lines never stop: the relay keeps calling EVALSHA against a script cache that a restarted Redis no longer has, and there is no fallback path. Restarting the relay re-registers the scripts and it recovers immediately.
Why it matters beyond this one host
Restarting a cache is routine: an upgrade, a memory-driven restart, a failover, a docker compose up -d cache that recreates only the cache container. nostream's own compose makes the trap easy to hit, because the cache and the relay are separate services and up -d recreates only the one whose definition changed — so the relay is left running against a cache that forgot its scripts.
The failure is also self-concealing. HTTP health checks pass, the process is up, and nothing in the container is unhealthy; only a client that sends REQ sees it. On the host where this was found, the relay had been answering 200 for minutes while serving zero events.
Expected
A NOSCRIPT reply should be handled the way any Redis client binding handles it: re-send the same script body with EVAL (ioredis does this automatically for commands defined through defineCommand, which is the usual reason this is invisible everywhere else). The relay should never enter a state where it cannot recover from a flushed script cache without a process restart.
How it was reproduced
docker compose up -d nostream-cache # recreate only the cache# relay stays up; https://<relay>/ still answers 200# a websocket REQ returns nothing, and the log repeats NOSCRIPT
sudo docker restart nostream # recovers
Notes
Worth checking whether the scripts are issued through defineCommand (which caches script bodies client-side and retries with EVAL) or through a raw evalsha, and whether the error is being swallowed by a worker's uncaught error handler rather than surfacing to the connection.
A second, smaller gap observed in the same incident: connect ECONNREFUSED is logged as uncaught error at level 50 two or three times before the client reconnects. The reconnect works, but an uncaught error in a worker is the wrong classification for a transient dependency.
Root cause measured, and it is not a gap in the code on main.
The EVAL fallback this issue asks for has existed since #920 (abd4a1b, 2026-10-21): src/adapters/redis-adapter.ts catches NOSCRIPT via isNoScriptError (:143), re-loads the script with scriptLoad and retries once (:157–:167).
The relay in the report was running v3.0.0 from an image built 2026-09-03, five minor versions behind the then-current latest (3.8.0, built 2026-10-24), so it predated that fix. What was missing was not the retry but the deployment: the host now runs 3.8.0 and has been clean since.
The genuine gap was coverage — nothing proved the worker recovers at the path that failed. #1003 adds an integration scenario that publishes, flushes the script cache with SCRIPT FLUSH, and publishes again, with a hook that forces a message rate limit so the Lua path is actually exercised (both limiters return early on an empty list, so without it the scenario would pass whether or not the retry exists). It fails without the retry and passes with it.
Closing this as fixed upstream; #1003 is the regression guard and is open for review.
Activity
test(redis): a client keeps publishing after the cache loses its scripts
chappie-daemon commentedon Oct 31, 2026
Root cause measured, and it is not a gap in the code on
main.The EVAL fallback this issue asks for has existed since #920 (
abd4a1b, 2026-10-21):src/adapters/redis-adapter.tscatches NOSCRIPT viaisNoScriptError(:143), re-loads the script withscriptLoadand retries once (:157–:167).The relay in the report was running v3.0.0 from an image built 2026-09-03, five minor versions behind the then-current
latest(3.8.0, built 2026-10-24), so it predated that fix. What was missing was not the retry but the deployment: the host now runs 3.8.0 and has been clean since.The genuine gap was coverage — nothing proved the worker recovers at the path that failed. #1003 adds an integration scenario that publishes, flushes the script cache with
SCRIPT FLUSH, and publishes again, with a hook that forces a message rate limit so the Lua path is actually exercised (both limiters return early on an empty list, so without it the scenario would pass whether or not the retry exists). It fails without the retry and passes with it.Closing this as fixed upstream; #1003 is the regression guard and is open for review.
test(redis): prove the relay keeps publishing after the cache loses i…