fix(#2851): warn when the state read breaks but the write still works

argus's review note: current_room_state collapsed absent-state,
transport failure and an unparseable body into one None, so a
systematically failing GET was unobservable.

Only one of the three actually hides. A transport error takes the PUT
down with it one line later, and a 404 is the expected first-setup
case — both stay at debug. A non-404 HTTP failure is the silent one:
the read is broken while the write still succeeds, so the guard
switches off and the sweep resumes emitting with nothing to show for
it. That case, and only that case, warns.

Keeping the warn narrow is the point: one that also fired on every
expected 404 would train the reader to skip the line.
This commit is contained in:
atlas 2026-08-09 21:31:04 +02:00
commit f4c470881e

View file

@ -1234,16 +1234,44 @@ fn state_needs_write(current: Option<&serde_json::Value>, desired: &serde_json::
/// Read the room's current content for one state event. `None` on any
/// failure — absent state, transport error, or an unparseable body are
/// all "we don't know", and [`state_needs_write`] turns that into a write.
///
/// One failure is louder than the rest, and it is the only one that hides:
/// **a non-404 HTTP failure means the read broke while the write still
/// works**, so the guard degrades to writing every time and the sweep
/// resumes waking the hive with nothing else to show for it. A transport
/// error needs no warning of its own — the PUT immediately after it fails
/// too, loudly — and a 404 is the expected first-setup case.
async fn current_room_state(
client: &reqwest::Client,
admin_token: &str,
url: &str,
) -> Option<serde_json::Value> {
let resp = client.get(url).bearer_auth(admin_token).send().await.ok()?;
if !resp.status().is_success() {
let resp = match client.get(url).bearer_auth(admin_token).send().await {
Ok(resp) => resp,
Err(e) => {
tracing::debug!(error = ?e, url, "matrix: state read unreachable; writing");
return None;
}
};
let status = resp.status();
if !status.is_success() {
if status != reqwest::StatusCode::NOT_FOUND {
tracing::warn!(
%status,
url,
"matrix: state read failed while writes still work — the \
skip-if-unchanged guard is off and every sweep will re-emit"
);
}
return None;
}
resp.json::<serde_json::Value>().await.ok()
match resp.json::<serde_json::Value>().await {
Ok(body) => Some(body),
Err(e) => {
tracing::debug!(error = ?e, url, "matrix: state read body unparseable; writing");
None
}
}
}
/// PUT a state event into `room_id` using the admin token, **skipping the