From b9d315dcd989b5c356038e5aab70974a4685a26a Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 30 Aug 2026 22:24:50 +0200 Subject: [PATCH] swarm-controller: separate the two paths that re-register a webhook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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. --- swarm-controller/src/forge.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/swarm-controller/src/forge.rs b/swarm-controller/src/forge.rs index e90cdef4..74f38b25 100644 --- a/swarm-controller/src/forge.rs +++ b/swarm-controller/src/forge.rs @@ -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"); } }