diff --git a/hive-matrix-mcp/src/client.rs b/hive-matrix-mcp/src/client.rs index 97a6fc75..41121b7a 100644 --- a/hive-matrix-mcp/src/client.rs +++ b/hive-matrix-mcp/src/client.rs @@ -60,7 +60,35 @@ pub async fn build_and_restore( return Err(anyhow!("matrix token at {} is empty", token_file.display())); } - let (user_id, device_id) = whoami(homeserver, &token).await?; + let (user_id, device_id) = match whoami(homeserver, &token).await { + Ok(ids) => ids, + Err(e) => { + let msg = format!("{e:#}"); + if msg.contains("M_UNKNOWN_TOKEN") { + // Homeserver rejected our token — stale session after a homeserver + // state wipe or token expiry. Delete the token file (and the + // matrix-sdk sqlite state keyed to the now-invalid session) so + // hive-c0re's periodic `ensure_all` sweep re-provisions the account. + // Exit 0: systemd's Restart=on-failure must not loop us here; the + // systemd.paths watcher restarts us once the new token file appears. + tracing::warn!( + path = %token_file.display(), + "matrix token rejected (M_UNKNOWN_TOKEN); deleting stale token + \ + sdk state for re-provisioning" + ); + let _ = fs::remove_file(token_file).await; + if let Err(re) = fs::remove_dir_all(state_dir).await { + tracing::warn!( + path = %state_dir.display(), + err = %re, + "failed to remove sdk state dir; next startup may fail with stale state" + ); + } + std::process::exit(0); + } + return Err(e); + } + }; fs::create_dir_all(state_dir) .await diff --git a/hive-matrix-mcp/src/main.rs b/hive-matrix-mcp/src/main.rs index 5c2e9a21..24b551e8 100644 --- a/hive-matrix-mcp/src/main.rs +++ b/hive-matrix-mcp/src/main.rs @@ -14,14 +14,8 @@ //! systemd's `ConditionPathExists=` doesn't have to be perfectly //! synced with hive-c0re's token-provisioning timing. //! -//! Stale-token recovery: when the homeserver rejects the token with -//! `M_UNKNOWN_TOKEN` (e.g. after a homeserver state wipe or session -//! expiry), the daemon deletes both the token file and the matrix-sdk -//! sqlite state dir, then exits 0. hive-c0re's periodic -//! `matrix::ensure_all` sweep detects the missing token file and -//! re-provisions the account (login with the stored password → new -//! token). The systemd.paths watcher on the token file path then -//! restarts this daemon with a valid token. +//! Stale-token recovery: handled in `client::build_and_restore` — see +//! that module for the M_UNKNOWN_TOKEN detection + cleanup flow. use anyhow::{Context, Result}; use matrix_sdk::config::SyncSettings; @@ -66,38 +60,9 @@ async fn main() -> Result<()> { "hive-matrix-daemon starting" ); - let matrix_client = - match client::build_and_restore(&homeserver, &token_file, &state_dir).await { - Ok(c) => c, - Err(e) => { - let msg = format!("{e:#}"); - if msg.contains("M_UNKNOWN_TOKEN") { - // Homeserver rejected our token — stale session after a - // homeserver state wipe or token expiry. Delete the token - // file (and the matrix-sdk sqlite state which is keyed to - // the now-invalid session) so hive-c0re's next - // `ensure_all` sweep re-provisions the account. Exit 0 so - // systemd's Restart=on-failure doesn't loop us here — - // the systemd.paths watcher will restart us once the new - // token file appears. - tracing::warn!( - path = %token_file.display(), - "matrix token rejected (M_UNKNOWN_TOKEN); deleting stale \ - token + sdk state for re-provisioning" - ); - let _ = tokio::fs::remove_file(&token_file).await; - if let Err(e) = tokio::fs::remove_dir_all(&state_dir).await { - tracing::warn!( - path = %state_dir.display(), - err = %e, - "failed to remove sdk state dir; next startup may fail with stale state" - ); - } - return Ok(()); - } - return Err(e.context("build matrix client")); - } - }; + let matrix_client = client::build_and_restore(&homeserver, &token_file, &state_dir) + .await + .context("build matrix client")?; timeline::install_message_handler(&matrix_client, hyperhive_socket.clone()); timeline::install_invite_handler(&matrix_client, hyperhive_socket);