Severity: medium · packages/engine/src/engine/node.ts:512-565
The node-offline transition writes nodes, nodeProviders, agents, then calls rescheduleInvocationsForLostNode — four sequential await db.update(...) calls plus the reschedule, each its own statement, with no runAtomic wrapper. Contrast registerNode's broker path (node.ts:394-412) and bindAgentToNode (node.ts:1000), which are transactional. serializeNodeOp gives isolation but no atomicity/rollback.
Failure scenario: node marked offline and providers zeroed, but the process dies before rescheduleInvocationsForLostNode — in-flight invocations dispatched to the dead node stay dispatched and are only recovered by the dispatch-timeout sweep (up to ACTION_DISPATCH_TIMEOUT_MS + sweep interval of added latency), and agents can be left offline while their agent_node_bindings row is still active.
Fix: wrap the transition in runAtomic like its siblings.
Found during a multi-agent reliability review of the relay tool/protocol.
Severity: medium ·
packages/engine/src/engine/node.ts:512-565The node-offline transition writes
nodes,nodeProviders,agents, then callsrescheduleInvocationsForLostNode— four sequentialawait db.update(...)calls plus the reschedule, each its own statement, with norunAtomicwrapper. ContrastregisterNode's broker path (node.ts:394-412) andbindAgentToNode(node.ts:1000), which are transactional.serializeNodeOpgives isolation but no atomicity/rollback.Failure scenario: node marked
offlineand providers zeroed, but the process dies beforerescheduleInvocationsForLostNode— in-flight invocations dispatched to the dead node staydispatchedand are only recovered by the dispatch-timeout sweep (up toACTION_DISPATCH_TIMEOUT_MS+ sweep interval of added latency), and agents can be leftofflinewhile theiragent_node_bindingsrow is stillactive.Fix: wrap the transition in
runAtomiclike its siblings.Found during a multi-agent reliability review of the relay tool/protocol.