{"record":{"id":"554eb734ec2fd0f8","repo":"linera-io/linera-protocol","slug":"linera-scan-loop-exited-unexpectedly-result","errorCode":null,"errorMessage":"Linera scan loop exited unexpectedly: {result:?}","messagePattern":"Linera scan loop exited unexpectedly: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"linera-bridge/src/relay/mod.rs","lineNumber":511,"sourceCode":"    // stream hiccup), and a missed `NewIncomingBundle` would otherwise strand\n    // its messages until the next one. Reuse the monitor's scan interval as the\n    // drain cadence; an empty inbox makes `process_inbox` a cheap no-op.\n    let mut inbox_drain_interval = tokio::time::interval(monitor_scan_interval);\n    // Consume the immediate first tick — the startup drain above already ran.\n    inbox_drain_interval.tick().await;\n\n    // ── Main loop: process chain operations + notifications ──\n    tracing::info!(\"Listening for chain operations and notifications...\");\n    loop {\n        tokio::select! {\n            result = &mut chain_listener_handle => {\n                anyhow::bail!(\"Chain listener exited unexpectedly: {result:?}\");\n            }\n            result = &mut evm_scan_handle => {\n                anyhow::bail!(\"EVM scan loop exited unexpectedly: {result:?}\");\n            }\n            result = &mut linera_scan_handle => {\n                anyhow::bail!(\"Linera scan loop exited unexpectedly: {result:?}\");\n            }\n            result = &mut retry_handle => {\n                anyhow::bail!(\"Retry loop exited unexpectedly: {result:?}\");\n            }\n            result = &mut http_server_handle => {\n                anyhow::bail!(\"HTTP server exited unexpectedly: {result:?}\");\n            }\n            result = &mut admin_server_handle => {\n                anyhow::bail!(\"Admin HTTP server exited unexpectedly: {result:?}\");\n            }\n            _ = inbox_drain_interval.tick() => {\n                // Periodic safety net for a missed `NewIncomingBundle`: sync and\n                // drain the inbox so stranded messages (e.g. user burns) are\n                // eventually processed even without a fresh notification.\n                if let Err(e) = chain_client.synchronize_from_validators().await {\n                    tracing::warn!(\"Periodic sync before inbox drain failed: {e}\");\n                } else {\n                    match chain_client.process_inbox().await {","sourceCodeStart":493,"sourceCodeEnd":529,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/relay/mod.rs#L493-L529","documentation":"In serve_loop's tokio::select!, the Linera scan loop task exited. This loop watches the bridge's own Linera chain for BurnEvents so withdrawals are relayed to the EVM side; if it dies (error return or panic), serve_loop fails fast because withdrawals would strand.","triggerScenarios":"The Linera scan future returns Err after exhausting its retries (chain client cannot synchronize with validators, chain query failures) or panics while parsing block events; the task is also prodded by scan_notify on every new block notification, so notification-driven code paths run inside it.","commonSituations":"Validators unreachable for an extended period; the bridge chain's client key not authorized (multisig leader changed); a malformed/unexpected event in a block after an application upgrade panics the decoder.","solutions":["Inspect relay logs for the Linera scan task's final error (it logs before exiting) — that is the root cause, this bail is only the propagation.","Restore connectivity/authorization to the Linera validators, then restart the relay.","If it was a panic decoding block events, fix the decoder for the new event shape and redeploy.","Run the relay under a supervisor (systemd Restart=always, docker restart policy) since serve_loop intentionally exits on any task death."],"exampleFix":"// before\nresult = &mut linera_scan_handle => {\n    anyhow::bail!(\"Linera scan loop exited unexpectedly: {result:?}\");\n}\n\n// after: include the outcome kind in the fatal error\nresult = &mut linera_scan_handle => {\n    let kind = match &result { Ok(()) => \"returned\", Err(j) if j.is_panic() => \"panicked\", Err(_) => \"aborted\" };\n    anyhow::bail!(\"Linera scan loop {kind}: {result:?}; see prior scan-loop logs for root cause\");\n}","handlingStrategy":"retry","validationCode":"// Before serving: one successful sync proves Linera connectivity/authorization\nchain_client.synchronize_from_validators().await?;","typeGuard":null,"tryCatchPattern":"// Fatal by design; restart the relay process. Log the scan task's last error for diagnosis.","preventionTips":["Keep validator endpoints stable and the client key authorized on the bridge chain.","Make event decoding defensive (skip + log unknown shapes) so one bad block cannot kill the scan loop."],"tags":["linera-bridge","relay","linera-chain","task-supervision","tokio","rust"],"backgroundTag":"background-task-exited","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}