{"id":"a9056da91a844bd4","repo":"redis/redis","slug":"ni-o-error-n","errorCode":null,"errorMessage":"\\nI/O error\\n","messagePattern":"\\\\nI/O error\\\\n","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/redis-cli.c","lineNumber":1620,"sourceCode":"    tcsetattr(STDIN_FILENO, TCSANOW, &mode);\n}\n\n/*------------------------------------------------------------------------------\n * Networking / parsing\n *--------------------------------------------------------------------------- */\n\n/* Send AUTH command to the server */\nstatic int cliAuth(redisContext *ctx, char *user, char *auth) {\n    redisReply *reply;\n    if (auth == NULL) return REDIS_OK;\n\n    if (user == NULL)\n        reply = redisCommand(ctx,\"AUTH %s\",auth);\n    else\n        reply = redisCommand(ctx,\"AUTH %s %s\",user,auth);\n\n    if (reply == NULL) {\n        fprintf(stderr, \"\\nI/O error\\n\");\n        return REDIS_ERR;\n    }\n\n    int result = REDIS_OK;\n    if (reply->type == REDIS_REPLY_ERROR) {\n        result = REDIS_ERR;\n        fprintf(stderr, \"AUTH failed: %s\\n\", reply->str);\n    }\n    freeReplyObject(reply);\n    return result;\n}\n\n/* Send SELECT input_dbnum to the server */\nstatic int cliSelect(void) {\n    redisReply *reply;\n    if (config.conn_info.input_dbnum == config.dbnum) return REDIS_OK;\n\n    reply = redisCommand(context,\"SELECT %d\",config.conn_info.input_dbnum);","sourceCodeStart":1602,"sourceCodeEnd":1638,"githubUrl":"https://github.com/redis/redis/blob/3acc0c49cf5ad2af9425d333e62728342dd6159b/src/redis-cli.c#L1602-L1638","documentation":"Printed by cliAuth() in src/redis-cli.c:1620 when redisCommand(ctx, \"AUTH ...\") returns a NULL reply, meaning the hiredis context is already in an error state before the server could respond to AUTH. It is not an authentication rejection (that path produces error 71) but a transport-level failure: the underlying socket broke, timed out, or the connection's read/write failed while the AUTH command was in flight.","triggerScenarios":"cliAuth() is invoked during cliConnect() (line 1771) right after a TCP/unix socket is established. The NULL reply occurs when config.conn_info.auth is non-NULL and the hiredis context has context->err set (REDIS_ERR_IO, REDIS_ERR_EOF, or REDIS_ERR_TIMEOUT) at the moment redisCommand() is called.","commonSituations":"Server killed or restarted between accept() and AUTH; a proxy/load-balancer (e.g. Envoy, HAProxy) closing idle connections before AUTH completes; TLS handshake that left the context in an error state but was not caught; network partition on a slow link where the connection succeeds TCP-wise but drops before the first command round-trip.","solutions":["Verify the server is still running and reachable right before the client connects: redis-cli ping or redis-cli -h <host> -p <port> ping.","Check that no proxy or firewall between client and server is terminating the connection mid-handshake; increase idle/proxy timeouts.","Reproduce with a longer connect timeout (--connect-timeout) to rule out REDIS_ERR_TIMEOUT masquerading as I/O error.","If using TLS, confirm the TLS negotiation succeeded first (error 78 would normally surface, but a half-broken context can leak here).","File a reproducible packet capture if the socket genuinely closes between accept and AUTH."],"exampleFix":"// before: implicit short timeout may drop mid-AUTH\nredis-cli -h host -p 6379 -a secret\n// after: explicit, generous connect timeout + retry\nredis-cli -h host -p 6379 -a secret --connect-timeout 30","handlingStrategy":"retry","validationCode":"import socket\nimport redis\n\ndef assert_reachable(host, port, timeout=2.0):\n    # Cheap liveness probe before issuing AUTH so a dropped socket\n    # is caught as a connect error, not an I/O error mid-handshake.\n    try:\n        with socket.create_connection((host, port), timeout=timeout):\n            return True\n    except OSError:\n        return False\n\n# Call before constructing the client:\nif not assert_reachable(redis_host, redis_port):\n    raise ConnectionError(f\"Redis {redis_host}:{redis_port} unreachable\")\nclient = redis.Redis(host=redis_host, port=redis_port, socket_keepalive=True,\n                     socket_connect_timeout=2.0, socket_timeout=2.0,\n                     health_check_interval=30)","typeGuard":null,"tryCatchPattern":"# An I/O error on AUTH means the server closed/reset the socket during the\n# handshake (reply == NULL). Treat as transient: reconnect + retry with\n# bounded exponential backoff.\nfrom redis.exceptions import ConnectionError as RedisConnectionError\nimport time\n\ndef auth_with_retry(factory, password, attempts=5, base=0.1):\n    last = None\n    for i in range(attempts):\n        client = factory()\n        try:\n            client.ping()\n            return client\n        except RedisConnectionError as e:\n            last = e\n            time.sleep(base * (2 ** i))\n    raise last\n","preventionTips":["Keep AUTH credentials non-empty and correctly scoped; an empty/wrong password is the most common cause of a reset right after AUTH.","Enable socket_keepalive and a short socket_timeout so a half-open socket fails fast instead of producing a mid-handshake I/O error.","Set socket_connect_timeout distinctly from socket_timeout so a dead host is detected during connect, before AUTH is ever sent.","Validate reachability with a TCP probe before constructing the client when operating over an unstable network.","Use a connection pool with a health_check_interval so stale connections are reaped before AUTH rather than failing on it."],"tags":["network","redis-cli","connection","auth","hiredis"],"analyzedSha":"3acc0c49cf5ad2af9425d333e62728342dd6159b","analyzedAt":"2026-08-01T22:07:56.715Z","schemaVersion":2}