fix(#2369): skip statically-configured accounts in matrix token discovery to silence spurious sidecar warning

This commit is contained in:
damocles 2026-07-11 09:34:28 +02:00 committed by mara
commit 7069732e01

View file

@ -100,14 +100,13 @@ pub fn configured() -> anyhow::Result<Vec<AccountCfg>> {
// its `matrix-account-<name>.json` homeserver sidecar) that aren't
// already declared in config, so an account logged in via the dashboard
// form works without a `matrixAccounts` edit + rebuild. Explicit config
// wins on name collision.
// wins on name collision — pass the configured set so discovery skips
// those names silently (a statically-configured account keeps its token
// on disk but is brought up from config, not discovery, so it must not
// log a spurious "no homeserver sidecar" warning).
let configured: std::collections::HashSet<String> =
accounts.iter().map(|a| a.name.clone()).collect();
for disc in discover_token_accounts() {
if !configured.contains(&disc.name) {
accounts.push(disc);
}
}
accounts.extend(discover_token_accounts(&configured));
Ok(accounts)
}
@ -120,11 +119,25 @@ pub fn configured() -> anyhow::Result<Vec<AccountCfg>> {
/// Best-effort: an unreadable dir or malformed sidecar yields fewer
/// accounts, never an error — explicit `HIVE_MATRIX_ACCOUNTS` config stays
/// authoritative.
fn discover_token_accounts() -> Vec<AccountCfg> {
///
/// `configured` is the set of names already declared in
/// `HIVE_MATRIX_ACCOUNTS`; those are brought up from config regardless of
/// their on-disk sidecar, so discovery skips them silently rather than
/// warning about a missing sidecar for an account that isn't actually down.
fn discover_token_accounts(configured: &std::collections::HashSet<String>) -> Vec<AccountCfg> {
let token_path = paths::token_file();
let Some(state_dir) = token_path.parent() else {
return Vec::new();
};
discover_token_accounts_in(state_dir, configured)
}
/// Body of [`discover_token_accounts`] with the state dir injected, so the
/// scan (and the configured-name skip) is unit-testable against a tempdir.
fn discover_token_accounts_in(
state_dir: &Path,
configured: &std::collections::HashSet<String>,
) -> Vec<AccountCfg> {
let Ok(rd) = std::fs::read_dir(state_dir) else {
return Vec::new();
};
@ -142,6 +155,13 @@ fn discover_token_accounts() -> Vec<AccountCfg> {
if name.is_empty() {
continue;
}
// Already declared in config: it's brought up from `HIVE_MATRIX_ACCOUNTS`,
// not discovery, and its on-disk token needs no sidecar. Skip silently so
// a statically-configured account doesn't log a spurious missing-sidecar
// warning.
if configured.contains(name) {
continue;
}
let sidecar = state_dir.join(format!("matrix-account-{name}.json"));
let Some(homeserver) = read_account_homeserver(&sidecar) else {
tracing::warn!(
@ -392,7 +412,54 @@ fn write_accounts_snapshot_inner(
#[cfg(test)]
mod tests {
use super::{AccountStatus, heartbeat_accounts_snapshot, pick_name, write_accounts_snapshot};
use std::collections::HashSet;
use super::{
AccountStatus, discover_token_accounts_in, heartbeat_accounts_snapshot, pick_name,
write_accounts_snapshot,
};
#[test]
fn discovery_skips_configured_names_and_orphan_tokens() {
let dir = std::env::temp_dir().join(format!(
"hh-acct-disc-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&dir).unwrap();
// A statically-configured account: token on disk, NO sidecar.
std::fs::write(dir.join("matrix-token-catgirl"), "tok").unwrap();
// An orphan dashboard token: no sidecar, not configured.
std::fs::write(dir.join("matrix-token-orphan"), "tok").unwrap();
// A fully dashboard-provisioned account: token + sidecar.
std::fs::write(dir.join("matrix-token-good"), "tok").unwrap();
std::fs::write(
dir.join("matrix-account-good.json"),
r#"{"homeserver":"https://good.example"}"#,
)
.unwrap();
// The hive account's own token must never be treated as an extra.
std::fs::write(dir.join("matrix-token"), "tok").unwrap();
let configured: HashSet<String> = ["main", "catgirl"]
.iter()
.map(|s| (*s).to_owned())
.collect();
let mut found: Vec<String> = discover_token_accounts_in(&dir, &configured)
.into_iter()
.map(|a| a.name)
.collect();
found.sort();
// catgirl (configured) + orphan (no sidecar) skipped; only `good` discovered.
assert_eq!(found, vec!["good".to_owned()]);
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn pick_name_single_account_defaults_to_primary() {