From 80a613b25f312c8677596fda30b82d3746e9abfc Mon Sep 17 00:00:00 2001 From: hugocasa Date: Sat, 26 Sep 2026 19:04:07 +0200 Subject: [PATCH] fix: skip triggers moved since their scope check when disconnecting Co-Authored-By: Claude Opus 5.5 --- ...a7abd1836ec53fdc8086aa160735843f62b9.json} | 5 +-- ...ceee71c5f0ed6aca603e7ec7aae47f7cdade.json} | 5 +-- .../src/workspace_integrations.rs | 32 ++++++++++++++----- 3 files changed, 30 insertions(+), 12 deletions(-) rename backend/.sqlx/{query-e4407cb73d41652088bb6dcef2503e91a190217cc7875ebb07928b25720ae20b.json => query-59e359ec5307da46b7fb75d0a2ada7abd1836ec53fdc8086aa160735843f62b9.json} (68%) rename backend/.sqlx/{query-c0a782d90433c27c97eeb49cf620e993320337f046e95efbd5bc21adf69e3d7d.json => query-f59c0f10c2dc7c0b578c764545e2ceee71c5f0ed6aca603e7ec7aae47f7cdade.json} (78%) diff --git a/backend/.sqlx/query-e4407cb73d41652088bb6dcef2503e91a190217cc7875ebb07928b25720ae20b.json b/backend/.sqlx/query-59e359ec5307da46b7fb75d0a2ada7abd1836ec53fdc8086aa160735843f62b9.json similarity index 68% rename from backend/.sqlx/query-e4407cb73d41652088bb6dcef2503e91a190217cc7875ebb07928b25720ae20b.json rename to backend/.sqlx/query-59e359ec5307da46b7fb75d0a2ada7abd1836ec53fdc8086aa160735843f62b9.json index def86ca96f..802c83dd7f 100644 --- a/backend/.sqlx/query-e4407cb73d41652088bb6dcef2503e91a190217cc7875ebb07928b25720ae20b.json +++ b/backend/.sqlx/query-59e359ec5307da46b7fb75d0a2ada7abd1836ec53fdc8086aa160735843f62b9.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "DELETE FROM native_trigger\n WHERE workspace_id = $1 AND service_name = $2 AND ($3::text[] IS NULL OR external_id = ANY($3))\n RETURNING webhook_token_hash", + "query": "DELETE FROM native_trigger\n WHERE workspace_id = $1 AND service_name = $2 AND ($3::text[] IS NULL\n OR (external_id, script_path) IN (SELECT * FROM unnest($3::text[], $4::text[])))\n RETURNING webhook_token_hash", "describe": { "columns": [ { @@ -24,6 +24,7 @@ } } }, + "TextArray", "TextArray" ] }, @@ -31,5 +32,5 @@ false ] }, - "hash": "e4407cb73d41652088bb6dcef2503e91a190217cc7875ebb07928b25720ae20b" + "hash": "59e359ec5307da46b7fb75d0a2ada7abd1836ec53fdc8086aa160735843f62b9" } diff --git a/backend/.sqlx/query-c0a782d90433c27c97eeb49cf620e993320337f046e95efbd5bc21adf69e3d7d.json b/backend/.sqlx/query-f59c0f10c2dc7c0b578c764545e2ceee71c5f0ed6aca603e7ec7aae47f7cdade.json similarity index 78% rename from backend/.sqlx/query-c0a782d90433c27c97eeb49cf620e993320337f046e95efbd5bc21adf69e3d7d.json rename to backend/.sqlx/query-f59c0f10c2dc7c0b578c764545e2ceee71c5f0ed6aca603e7ec7aae47f7cdade.json index 013720b6ea..da21731ae1 100644 --- a/backend/.sqlx/query-c0a782d90433c27c97eeb49cf620e993320337f046e95efbd5bc21adf69e3d7d.json +++ b/backend/.sqlx/query-f59c0f10c2dc7c0b578c764545e2ceee71c5f0ed6aca603e7ec7aae47f7cdade.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT external_id, connection_path FROM native_trigger\n WHERE workspace_id = $1 AND service_name = $2 AND ($3::text[] IS NULL OR external_id = ANY($3))", + "query": "SELECT external_id, connection_path FROM native_trigger\n WHERE workspace_id = $1 AND service_name = $2 AND ($3::text[] IS NULL\n OR (external_id, script_path) IN (SELECT * FROM unnest($3::text[], $4::text[])))", "describe": { "columns": [ { @@ -29,6 +29,7 @@ } } }, + "TextArray", "TextArray" ] }, @@ -37,5 +38,5 @@ true ] }, - "hash": "c0a782d90433c27c97eeb49cf620e993320337f046e95efbd5bc21adf69e3d7d" + "hash": "f59c0f10c2dc7c0b578c764545e2ceee71c5f0ed6aca603e7ec7aae47f7cdade" } diff --git a/backend/windmill-native-triggers/src/workspace_integrations.rs b/backend/windmill-native-triggers/src/workspace_integrations.rs index ff2dee431f..db621af978 100644 --- a/backend/windmill-native-triggers/src/workspace_integrations.rs +++ b/backend/windmill-native-triggers/src/workspace_integrations.rs @@ -348,22 +348,26 @@ async fn fetch_nextcloud_user_id(base_url: &str, access_token: &str) -> anyhow:: Ok(ocs.ocs.data.id) } -/// Delete the native triggers of a workspace+service — only those in `external_ids` when given — -/// including remote webhook cleanup. +/// Delete the native triggers of a workspace+service, including remote webhook cleanup. When `only` +/// gives `(external_ids, script_paths)`, just the triggers still at those paths: a trigger moved +/// since its path was checked is left alone. /// This is best-effort: errors during remote cleanup or token deletion are logged but ignored. #[cfg(feature = "native_trigger")] async fn delete_triggers_for_service( db: &DB, workspace_id: &str, service_name: ServiceName, - external_ids: Option<&[String]>, + only: Option<(&[String], &[String])>, ) { + let (external_ids, script_paths) = only.unzip(); let triggers = sqlx::query!( "SELECT external_id, connection_path FROM native_trigger - WHERE workspace_id = $1 AND service_name = $2 AND ($3::text[] IS NULL OR external_id = ANY($3))", + WHERE workspace_id = $1 AND service_name = $2 AND ($3::text[] IS NULL + OR (external_id, script_path) IN (SELECT * FROM unnest($3::text[], $4::text[])))", workspace_id, service_name as ServiceName, external_ids, + script_paths, ) .fetch_all(db) .await; @@ -423,11 +427,13 @@ async fn delete_triggers_for_service( // disconnected integration keep starting jobs. let deleted_token_hashes = sqlx::query_scalar!( "DELETE FROM native_trigger - WHERE workspace_id = $1 AND service_name = $2 AND ($3::text[] IS NULL OR external_id = ANY($3)) + WHERE workspace_id = $1 AND service_name = $2 AND ($3::text[] IS NULL + OR (external_id, script_path) IN (SELECT * FROM unnest($3::text[], $4::text[]))) RETURNING webhook_token_hash", workspace_id, service_name as ServiceName, external_ids, + script_paths, ) .fetch_all(db) .await @@ -1085,7 +1091,8 @@ async fn delete_connection( } // Disconnecting deletes these triggers, so a path-scoped token must cover each of them. Only - // the rows checked here are deleted: one created meanwhile is never removed unchecked. + // the rows checked here, at the paths checked, are deleted: one created or moved meanwhile is + // never removed unchecked. let triggers = sqlx::query!( "SELECT external_id, script_path FROM native_trigger WHERE workspace_id = $1 AND service_name = $2 AND connection_path = $3", @@ -1100,10 +1107,19 @@ async fn delete_connection( format!("native_triggers:write:{}", trigger.script_path) })?; } - let external_ids: Vec = triggers.into_iter().map(|t| t.external_id).collect(); + let (external_ids, script_paths): (Vec, Vec) = triggers + .into_iter() + .map(|t| (t.external_id, t.script_path)) + .unzip(); // Before the connection goes: removing the registrations needs its token. - delete_triggers_for_service(&db, &workspace_id, service_name, Some(&external_ids)).await; + delete_triggers_for_service( + &db, + &workspace_id, + service_name, + Some((&external_ids, &script_paths)), + ) + .await; let mut tx = user_db.begin(&authed).await?; let account = cleanup_connection(&mut *tx, &workspace_id, &path).await?;