diff --git a/swarm-controller/src/forge.rs b/swarm-controller/src/forge.rs index ca4271a9..af87462a 100644 --- a/swarm-controller/src/forge.rs +++ b/swarm-controller/src/forge.rs @@ -624,8 +624,11 @@ impl Client { // 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. + // separated: `listed=0` means the create is not landing in the set + // the list reads (a bucket or permission problem — see + // [`HookScope::extra_create_config`], which is where the instance + // scope's own version of that went wrong), 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) { @@ -650,6 +653,9 @@ impl Client { let mut additional = BTreeMap::new(); additional.insert("secret".to_owned(), secret.to_owned()); + for (key, value) in scope.extra_create_config() { + additional.insert((*key).to_owned(), (*value).to_owned()); + } let hook = CreateHookOption { active: Some(true), authorization_header: None, @@ -692,11 +698,33 @@ enum HookScope<'a> { /// Forgejo's "global (system) webhook" — fires for every repo in the /// instance, in every org, present or future. The `admin_*` API /// namespace; needs the same `write:admin` scope - /// `Client::ensure_agent_user` already requires on this token. + /// `Client::ensure_agent_user` already requires on this token, **and** + /// the `is_system_webhook` config key from + /// [`Self::extra_create_config`] — that endpoint's default is the + /// *other* kind of admin hook. Instance, } impl HookScope<'_> { + /// Config-map keys the create call needs beyond the shared ones. + /// + /// Instance scope carries `is_system_webhook`, and it is load-bearing + /// twice over. `POST /admin/hooks` reads that key **out of the config + /// map** and defaults it to `false`, which makes a forgejo *default* + /// webhook — a template copied into repos created later, not a live + /// hook — while `GET /admin/hooks` returns only webhooks with the flag + /// set. Omitting it therefore breaks both halves at once: the hook is + /// not the instance-wide one this scope exists for, and + /// [`Self::list_hook_urls`] can never see it, so every process start + /// creates another one — twelve in a day, `listed=0` each time, + /// before this key was sent. + fn extra_create_config(&self) -> &'static [(&'static str, &'static str)] { + match self { + Self::Repo { .. } | Self::Org { .. } => &[], + Self::Instance => &[("is_system_webhook", "true")], + } + } + /// The `url` config value of every hook currently on this scope. async fn list_hook_urls(&self, api: &Forgejo) -> Result, ForgejoError> { let hooks = match self {