Actors stay unreachable ("Actor not found") after an engine node is lost, with the Postgres driver in multi-node mode
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 22/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- kubernetes, postgresql, rust, sqlite
- Área
- backend, databases, distributed-systems
Línea de trabajo
Start by reading the related open issues #5859, #5894, #5896, and #5908, then trace actor event dispatch, envoy reconnection, and remote SQLite commit handling in the engine and runner. The report supplies reproduction steps and logs, but the cause and responsible component are unconfirmed. Done means reproducing the stuck actor state, identifying why recovery fails, and verifying affected actors become reachable after node loss.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
Human: This is a real issue i have reproduced multiple time. Below the report made by claude opus on this problem.
With two engine nodes on Postgres and NATS, killing the engine node that holds the runners' (envoys') WebSocket connections while actors are busy leaves some actors unreachable for at least 5 minutes (up to about 15 minutes checked), until the runners restart. Requests return Actor not found; the runner logs received request for unknown actor; the engine still reports the actor as connectable. Neither POST /actors/{id}/sleep nor POST /actors/{id}/reschedule recovers it. Restarting the runner processes does.
It happened in 7 of 46 kills. The affected actors use @rivet-dev/pi (Pi Durable on the actor's SQLite). It did not happen in 30 kills with plain actors that only write to their SQLite.
Report written with the help of an AI coding assistant; the runs and logs below are real.
Versions
- Engine:
rivetdev/engine:slim-2.3.30 rivetkit2.3.30 (envoy mode,RIVETKIT_RUNTIME_MODEdefault)@rivet-dev/pi0.5.3,@earendil-works/pi-durable1.3.0- Node 24.22.1
- PostgreSQL 18.6 (single instance)
- NATS 2.15.1 (3-node cluster)
Environment
Local Kubernetes 1.36 (Docker Desktop, kind, 3 nodes, arm64). Engine config, all defaults otherwise:
{
"postgres": { "url": "postgresql://<user>:<password>@postgres:5432/rivet?sslmode=disable" },
"nats": { "addresses": ["nats-0:4222", "nats-1:4222", "nats-2:4222"], "username": "<user>", "password": "<password>" },
"auth": { "admin_token": "<token>" },
"telemetry": { "enabled": false }
}
- Two engine replicas (Deployment, required pod anti-affinity), behind one ClusterIP Service.
- Two runner pods (
RIVET_ENDPOINTpointing at that Service,RIVET_NAMESPACE,RIVET_TOKENa namespace JWT withrunner: create), so each runner's WebSocket lands on either engine node. - One namespace, runner config
{"datacenters":{"default":{"normal":{}}}}.
Steps to reproduce
- Create 100 actors (one Pi Durable agent each,
crash_policydefaultsleep). From one client, send each actor 6 sequentialpromptactions; each prompt runs one tool call, so each action commits several times to the actor's SQLite. - While this load runs, find the engine pod holding the most runner WebSockets (from its
envoy websocket connected/closedlog lines) and delete it:kubectl delete pod <engine-pod> --force --grace-period=0. - When the load ends, call a trivial action on each of the 100 actors. Repeat 5 minutes after the kill.
Expected
Actors interrupted by the lost node are rescheduled and answer within roughly the envoy lost threshold (15 s) plus start time, as most do.
Actual
In 7 of 46 runs, between 1 and 89 of the 100 actors answer Actor not found at the end of the load and still 5 minutes after the kill (the longest wait checked was about 15 minutes). For one of them, GET /actors?namespace=<ns>&name=agent&key=<key> returned:
"connectable_ts": <set, time of the kill>, "sleep_ts": null, "reschedule_ts": null, "destroy_ts": null, "crash_policy": "sleep"
POST /actors/{id}/sleep returned 200; the next call then timed out. POST /actors/{id}/reschedule returned 200; calls still returned Actor not found. Restarting the runner pods made every actor answer again, with its state intact.
Logs
One stuck actor (<actor-1>, key agent-52) from a run on 2026-10-24, UTC. Runner (the pod that hosted it):
01:11:14.895 error sqlite atomic commit failed actor_id=<actor-1> err="remote sqlite commit result is indeterminate after envoy disconnect"
01:11:29.899 warn stopping all actors due to envoy lost threshold
01:11:29.899 error sqlite flush commit failed actor_id=<actor-1> err="envoy shut down"
01:11:29.900 warn slow sqlite transaction database=<actor-1> duration_ms=15069
warn pi durable stopped after a failed commit, reopening it from storage actorKey=["agent-52"]
01:11:34.148 warn actor framework action error response actor_id=<actor-1> generation=1 code=internal_error
warn pi durable busy check failed error="Database is closed. ..."
01:11:34.198 error graceful cleanup callback failed actor_id=<actor-1> error="Session failed after a storage error; close and reopen it"
01:11:34 .. 01:16:46 warn received request for unknown actor actor_id=<actor-1> actor_generation=None (17 times)
Surviving engine node, same seconds:
01:11:33.644 info envoy websocket connected envoy_key=<envoy-A>
01:11:34.018 warn actor not allocated, ignoring events envoy_key=<envoy-A> events=[... EventActorStateUpdate { state: ActorStateRunning } ...]
The engine logged many actor not allocated, ignoring events lines after runners reconnected, including batches with ActorStateStopped. None of them names <actor-1>, so the logs do not show what happened to this actor's own events.
Frequency
| Setup | Engine node killed | Runs | Runs with actors stuck > 5 min |
|---|---|---|---|
| Pi Durable actors, runners connected to the engine Service | the one holding most runner WebSockets | 46 | 7 |
| Same, runners behind a TCP proxy (socat) in their pod | same | 18 | 2 |
| Same, runners direct | a random engine pod | 4 | 0 |
| Same, no node lost: runner proxies frozen for 5 s and 10 s | none | 2 | 0 |
Plain actors (actor() with db(), one INSERT per action), runners direct |
the one holding most runner WebSockets | 30 | 0 |
No acknowledged action was lost and no tool effect was applied twice in any run.
Workaround
Restart the runner processes. We now run a guard in the runner that restarts it when it keeps receiving requests for an actor it does not know; in 72 further kills, the 5 stuck cases recovered on their own in 43 to 58 s.
Possibly related (hypothesis)
- #5859 (open): a failed actor event dispatch drops later events, including "stopped". The engine warnings above (events ignored,
ActorStateStoppedamong them) point the same way. - #5894 (open): an existing actor stays unreachable after a runner restart.
- #5896 (open): stale envoy routing survives after a
pegboard_actor2workflow dies withLeaseFenceMismatch; Sleep does not revive the actor there either. That report is single-node RocksDB and was seen once. - #5908 (open): a lost runner parks v2 actors (Sleep) regardless of
crash_policy. Different symptom (the actor sleeps, it is not stuck as running), same area. - The failing cleanup is the actor's Pi Durable session, run by
@rivet-dev/pi(published from rivet-dev/agents,packages/pi); the error text "Session failed after a storage error" comes from@earendil-works/pi-durable. Part of the problem may be there rather than in the engine.
The common factor in the stuck cases is an actor whose SQLite commit becomes indeterminate when the runner's connection to the dead engine node drops, and whose cleanup then fails. Plain actors did not reach that state in our runs. We have not confirmed this in the engine source.
Not tested
- A reproduction without
@rivet-dev/pi: the plain-actor variant above did not trigger it. - The file-system (single-node) driver.
- Engine versions before 2.3.30, and the patch from #5859.
@earendil-works/pi-durable1.4.0, released since; only 1.3.0 was used.
- Lenguaje dominante
- Rust
- Estrellas
- 6.6k
- Forks
- 304
- Merge medio
- 18 h 5 min
- PR fusionados (30 d)
- 31
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de rivet-dev/rivet
-
rivetkit client: ActorConn debug log prints action argumentsQuizá libre de nuevo Un pull request para esta issue se cerró sin fusionarse. Abierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 80/100
rivet-dev/rivet#5955 · 4 comentarios ·
Los mantenedores suelen responder en 1 día
-
rivetkit 3.0: HTTP 400 overloaded on node pool upsert is treated as fatal and exits the processAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
rivet-dev/rivet#5933 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
effect: action on a missing actor instance fails as internal_error instead of actor.abortedPosiblemente ocupada @kunalgoesbyken la tomó hace 11 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
rivet-dev/rivet#5891 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Rust client drops connection-level errors when actionId is null (JSON/CBOR)Posiblemente ocupada @DibbayajyotiRoy la tomó hace 34 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
rivet-dev/rivet#5819 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Queue docs say completable messages are removed on receive, but they stay stored until complete()Posiblemente ocupada @Saisharathchandranandnetha la tomó hace 45 días. Abierto
Dificultad 1/5 1-3 horas Aptitud para principiantes 91/100
rivet-dev/rivet#5762 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de rivet-dev/rivet
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
NSPG13/agent-bounties#1749 ·
Los mantenedores suelen responder en 1 día
-
area: web bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
kinqsradio/orbyn-engineering#227 ·
Los mantenedores suelen responder en 1 día
-
agentic-workflows automation repo-assist
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
shanselman/winget-tui#483 ·
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
lightonai/next-plaid#205 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
apache/datafusion-iceberg#100 ·
Los mantenedores suelen responder en 1 día