Skip to content

Commit 3bb1874

Browse files
committed
clear pairings when gateway stops
When a gateway is stopped (stop_gateway or stop_all), all pairings for that platform are removed from the database. Users must re-pair on next start. Makes sense for Telegram where pairings are tied to a running goose session.
1 parent abc1676 commit 3bb1874

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

crates/goose-server/src/gateway/manager.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl GatewayManager {
122122
Ok(())
123123
}
124124

125-
/// Stop a gateway and remove its persisted config.
125+
/// Stop a gateway, clear its pairings, and remove its persisted config.
126126
pub async fn stop_gateway(&self, gateway_type: &str) -> anyhow::Result<()> {
127127
let instance = self
128128
.gateways
@@ -134,6 +134,20 @@ impl GatewayManager {
134134
instance.cancel.cancel();
135135
let _ = instance.handle.await;
136136

137+
match self
138+
.pairing_store
139+
.remove_all_for_platform(gateway_type)
140+
.await
141+
{
142+
Ok(count) if count > 0 => {
143+
tracing::info!(gateway = %gateway_type, count, "cleared pairings on stop");
144+
}
145+
Err(e) => {
146+
tracing::warn!(gateway = %gateway_type, error = %e, "failed to clear pairings on stop");
147+
}
148+
_ => {}
149+
}
150+
137151
Self::remove_saved_config(gateway_type);
138152
tracing::info!(gateway = %gateway_type, "gateway stopped");
139153
Ok(())
@@ -188,6 +202,13 @@ impl GatewayManager {
188202
for (gateway_type, instance) in instances {
189203
instance.cancel.cancel();
190204
let _ = instance.handle.await;
205+
if let Err(e) = self
206+
.pairing_store
207+
.remove_all_for_platform(&gateway_type)
208+
.await
209+
{
210+
tracing::warn!(gateway = %gateway_type, error = %e, "failed to clear pairings on stop");
211+
}
191212
tracing::info!(gateway = %gateway_type, "gateway stopped");
192213
}
193214
}

crates/goose-server/src/gateway/pairing.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,20 @@ impl PairingStore {
255255
.collect()
256256
}
257257

258+
pub async fn remove_all_for_platform(&self, platform: &str) -> anyhow::Result<usize> {
259+
let pool = self.ensure_initialized().await?;
260+
261+
let result = sqlx::query("DELETE FROM gateway_pairings WHERE platform = ?")
262+
.bind(platform)
263+
.execute(pool)
264+
.await?;
265+
266+
let mut pairings = self.pairings.write().await;
267+
pairings.retain(|user, _| user.platform != platform);
268+
269+
Ok(result.rows_affected() as usize)
270+
}
271+
258272
pub async fn list_paired_users(
259273
&self,
260274
gateway_type: &str,
@@ -433,4 +447,43 @@ mod tests {
433447
other => panic!("Expected Paired, got {:?}", other),
434448
}
435449
}
450+
451+
#[tokio::test]
452+
async fn test_remove_all_for_platform() {
453+
let tmp = TempDir::new().unwrap();
454+
let store = PairingStore::new(&tmp.path().join("test.db"));
455+
456+
let tg1 = test_user("telegram", "111");
457+
let tg2 = test_user("telegram", "222");
458+
let discord = test_user("discord", "333");
459+
460+
for user in [&tg1, &tg2, &discord] {
461+
store
462+
.set(
463+
user,
464+
PairingState::Paired {
465+
session_id: format!("s-{}", user.user_id),
466+
paired_at: 1000,
467+
},
468+
)
469+
.await
470+
.unwrap();
471+
}
472+
473+
let removed = store.remove_all_for_platform("telegram").await.unwrap();
474+
assert_eq!(removed, 2);
475+
476+
assert!(matches!(
477+
store.get(&tg1).await.unwrap(),
478+
PairingState::Unpaired
479+
));
480+
assert!(matches!(
481+
store.get(&tg2).await.unwrap(),
482+
PairingState::Unpaired
483+
));
484+
assert!(matches!(
485+
store.get(&discord).await.unwrap(),
486+
PairingState::Paired { .. }
487+
));
488+
}
436489
}

0 commit comments

Comments
 (0)