fix(#2095): retry secondary matrix account bring-up on transient failure

On a transient error (network blip, DNS not ready, homeserver 5xx) a
secondary account's bring-up was immediately skipped for the entire
daemon lifetime. This bit janet's catgirl account repeatedly when the
host DNS resolver wasn't ready at daemon start — the account would
silently disappear until the next restart.

Add a PermanentBringUpError sentinel in client.rs so callers can
distinguish M_UNKNOWN_TOKEN (stale/expired token — permanent, don't
retry) from transient network/homeserver errors.

In main.rs, replace the immediate skip with a bounded retry loop for
secondary accounts: up to 4 attempts with 2s/5s/15s/30s backoffs
(~52s total wait). On a transient error the daemon now stays alive
serving the primary and any other healthy accounts while the failing
secondary gets another chance. Permanent failures (PermanentBringUpError)
still skip immediately with no retry.

The primary account keep its existing behaviour: fatal on non-permanent
error so systemd restarts the whole daemon (systemd is the right retry
mechanism for primary bring-up failure).
This commit is contained in:
atlas 2026-07-04 12:16:44 +02:00 committed by mara
commit a1cd50610a
2 changed files with 97 additions and 8 deletions

View file

@ -29,6 +29,25 @@ use matrix_sdk::{
use serde::Deserialize;
use tokio::fs;
/// Sentinel returned when `build_and_restore` detects that the token is
/// permanently invalid (M_UNKNOWN_TOKEN). The token has already been
/// removed from disk. Callers should NOT retry — the account needs
/// re-provisioning by hive-c0re.
///
/// Distinct from the general `anyhow::Error` path so callers can use
/// `err.downcast_ref::<PermanentBringUpError>()` to distinguish "retry
/// won't help" from a transient network/DNS/5xx failure.
#[derive(Debug)]
pub struct PermanentBringUpError(pub String);
impl std::fmt::Display for PermanentBringUpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for PermanentBringUpError {}
/// Subset of the `/_matrix/client/v3/account/whoami` response we care
/// about. matrix-spec field names; `device_id` is optional per spec
/// (servers MAY omit it for legacy bearer scopes) but tuwunel always
@ -112,11 +131,11 @@ pub async fn build_and_restore(
// Secondary: token removed (so it's cleanly skipped next boot
// rather than re-erroring); leave the sdk state in place in case
// the operator re-provisions a fresh token for the same device.
// Return Err so the caller logs + skips this one account and the
// daemon keeps serving the primary and any other healthy account.
return Err(anyhow!(
"matrix token rejected (M_UNKNOWN_TOKEN); removed stale token, skipping account"
));
// Return a PermanentBringUpError so the caller can distinguish
// "don't retry" from a transient network/DNS failure.
return Err(anyhow::Error::new(PermanentBringUpError(
"matrix token rejected (M_UNKNOWN_TOKEN); removed stale token, skipping account".into(),
)));
}
return Err(e);
}