swarm-controller: separate the two paths that re-register a webhook

`ensure_hook` reaches its create call from two different places — the
list step failed, or it succeeded and matched nothing — and both were
logged at `debug!`. At the level the journal keeps, that made a repeated
registration indistinguishable from a first, correct one, and it is a
repeated registration that is being observed: the instance-scoped hook
logs the create arm on every process start while the repo-scoped one
correctly goes quiet.

The fold that keeps this harmless (`is_already_exists` swallowing a
duplicate create) is an assumption about the forge rather than a
guarantee, so the failed-list arm becomes a `warn!`, and the
matched-nothing arm logs the number of hooks it did see: `listed=0` is a
permission or scope problem, a non-zero count with no match means the
recorded url is not the one being compared.

No behaviour change — this makes the existing behaviour legible.
This commit is contained in:
atlas 2026-08-30 22:24:50 +02:00
commit b9d315dcd9

View file

@ -610,18 +610,36 @@ impl Client {
event: &str,
secret: &str,
) -> Result<()> {
// ⚠️ Both non-matching arms log at a level the journal keeps, because
// this idempotency check fails *silently*: when the list step does
// not recognise a hook that is already there, the create below runs
// again, and that stays harmless only while the forge rejects the
// duplicate. A forge that accepts it leaves one extra hook per
// process start, each delivering on every event — and the create
// arm's line reads exactly like a first, correct registration.
//
// The two ways to reach a create have different causes, so they are
// separated: `listed=0` is a permission or scope problem, a non-zero
// count with no match means the recorded url is not the one compared.
match scope.list_hook_urls(&self.api).await {
Ok(urls) => {
if urls.iter().any(|u| u == target_url) {
tracing::debug!(%target_url, "swarm forge: webhook already registered");
return Ok(());
}
tracing::info!(
%target_url,
listed = urls.len(),
"swarm forge: no listed hook matches; registering"
);
}
Err(e) => {
// Best-effort, same as the per-hive registrars: a forge that
// cannot be listed may still accept a create, and a
// duplicate create is folded into success below.
tracing::debug!(error = %e, %target_url, "swarm forge: listing hooks failed; attempting create");
// duplicate create is folded into success below. `warn!`
// because that fold is an assumption about the forge, not a
// guarantee — see the note above.
tracing::warn!(error = %e, %target_url, "swarm forge: listing hooks failed; registering blind");
}
}