From 0475dbcbcc5d5d7c0da21c5d56da4a844ea9ce9c Mon Sep 17 00:00:00 2001 From: oxoxDev Date: Mon, 1 Jun 2026 14:38:35 +0530 Subject: [PATCH 1/7] observability(sentry): add set/clear scope user helpers (#3135) --- src/core/observability.rs | 43 +++++++++++++++++++++++ src/core/observability_tests.rs | 60 +++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/src/core/observability.rs b/src/core/observability.rs index 7a264d0061..3aa7b5f5cc 100644 --- a/src/core/observability.rs +++ b/src/core/observability.rs @@ -1749,6 +1749,49 @@ pub(crate) fn report_error_message( ); } +/// Bind the signed-in user's id to the process-global Sentry scope so every +/// subsequently-reported event carries `user.id`. +/// +/// Why this exists: the `before_send` hooks in `src/main.rs` and the Tauri +/// shell already pluck `user.id` from `app_state::peek_cached_current_user_identity()`, +/// but that cache is only warmed by the frontend-driven `app_state_snapshot` +/// RPC. Errors raised from background loops — e.g. the periodic Composio sync +/// tick that emits the `[composio-direct]` shapes — fire with an empty cache, +/// so the event lands with no user attribution (userCount=0). Setting the +/// scope user eagerly at the session boundary (login / account switch) makes +/// the id available regardless of whether the snapshot cache was ever warmed, +/// mirroring how the backend tags every event at the session boundary. +/// +/// Only the `id` is carried — never email, name, or IP — to stay consistent +/// with `send_default_pii: false` in the Sentry client options. The `id` is +/// the backend's mongo ObjectId for the user; it is recorded only on the +/// Sentry scope and never logged here. +/// +/// No-op when the Sentry client is a no-op guard (missing DSN) — the scope +/// mutation is simply never flushed. +pub fn set_sentry_user_id(user_id: &str) { + let trimmed = user_id.trim(); + if trimmed.is_empty() { + return; + } + let owned = trimmed.to_string(); + sentry::configure_scope(|scope| { + scope.set_user(Some(sentry::User { + id: Some(owned.clone()), + ..Default::default() + })); + }); +} + +/// Clear the signed-in user from the process-global Sentry scope. +/// +/// Called on logout so events reported after sign-out (or for a different +/// account before the next `set_sentry_user_id`) are not misattributed to the +/// previous user. Mirrors `set_sentry_user_id` — no-op under a no-op guard. +pub fn clear_sentry_user() { + sentry::configure_scope(|scope| scope.set_user(None)); +} + /// Returns true when a Sentry event is a per-attempt provider HTTP failure /// that the reliable-provider layer already handles via retry + fallback. /// diff --git a/src/core/observability_tests.rs b/src/core/observability_tests.rs index 1a1b772b05..ec2a4b71bf 100644 --- a/src/core/observability_tests.rs +++ b/src/core/observability_tests.rs @@ -3547,3 +3547,63 @@ fn report_error_or_expected_routes_channel_supervisor_restart_through_expected_p &[("channel", "discord")], ); } + +#[test] +fn set_sentry_user_id_attaches_id_to_reported_events() { + use std::sync::Arc; + + use sentry::test::TestTransport; + + // Stand up an isolated hub with a TestTransport so we can observe the + // user the captured event carries. Bound to the current thread only via + // `HubSwitchGuard`, so it never bleeds into other tests. + let transport = TestTransport::new(); + let options = sentry::ClientOptions { + dsn: Some("https://public@sentry.invalid/1".parse().unwrap()), + transport: Some(Arc::new(transport.clone())), + ..Default::default() + }; + let hub = Arc::new(sentry::Hub::new( + Some(Arc::new(options.into())), + Arc::new(Default::default()), + )); + let _guard = sentry::HubSwitchGuard::new(hub); + + // Mimics the mongo ObjectId the backend hands us at the session boundary. + set_sentry_user_id("507f1f77bcf86cd799439011"); + report_error( + "simulated direct-mode failure", + "composio", + "list_connections", + &[], + ); + + let events = transport.fetch_and_clear_events(); + assert_eq!(events.len(), 1, "expected exactly one captured event"); + let user = events[0] + .user + .as_ref() + .expect("captured event should carry scope user"); + assert_eq!(user.id.as_deref(), Some("507f1f77bcf86cd799439011")); + // PII-minimal: only the id is ever set on the scope user. + assert!(user.email.is_none()); + assert!(user.username.is_none()); + + // Logout clears the scope; subsequent events must not be misattributed. + clear_sentry_user(); + report_error("post-logout failure", "composio", "list_connections", &[]); + let events = transport.fetch_and_clear_events(); + assert_eq!(events.len(), 1); + assert!( + events[0].user.is_none(), + "scope user must be cleared after clear_sentry_user()" + ); +} + +#[test] +fn set_sentry_user_id_ignores_blank_input() { + // Blank / whitespace-only ids are dropped rather than attaching an empty + // `user.id` that would still count as a distinct user in Sentry. + set_sentry_user_id(""); + set_sentry_user_id(" "); +} From abb6cc942e0214504ba7735cfaf45588ea07da53 Mon Sep 17 00:00:00 2001 From: oxoxDev Date: Mon, 1 Jun 2026 14:38:35 +0530 Subject: [PATCH 2/7] feat(credentials): bind Sentry user id on login, clear on logout (#3135) --- src/openhuman/credentials/ops.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/openhuman/credentials/ops.rs b/src/openhuman/credentials/ops.rs index 8077661b9c..aea1c69d9d 100644 --- a/src/openhuman/credentials/ops.rs +++ b/src/openhuman/credentials/ops.rs @@ -171,6 +171,15 @@ pub async fn store_session( // Determine user_id so we can scope the openhuman directory to this user. let resolved_user_id = metadata.get("user_id").cloned(); + // Bind the signed-in user's id to the Sentry scope as early as possible + // (on fresh login and on account switch, both of which route through here) + // so events reported by background loops carry `user.id` even before the + // frontend `app_state_snapshot` RPC has warmed the identity cache. Only + // the id is sent — never email/name/IP. See `set_sentry_user_id`. + if let Some(ref uid) = resolved_user_id { + crate::core::observability::set_sentry_user_id(uid); + } + // If we know the user_id, activate the user-scoped directory BEFORE storing // the auth profile so that credentials land in the correct place. let mut logs = if local_session { @@ -364,6 +373,10 @@ pub async fn clear_session(config: &Config) -> Result Date: Mon, 1 Jun 2026 15:54:58 +0530 Subject: [PATCH 3/7] observability(sentry): warm user scope from restored session on boot (#3135) store_session only binds the Sentry user on fresh login/account-switch. A restored session (app reopened while signed in) never routes through it, so background-loop errors (e.g. the periodic Composio sync tick) reported with user = None. Add warm_sentry_user_from_active_session() reading active_user.toml + binding the scope, with unit coverage for the present/absent branches. --- src/openhuman/credentials/ops.rs | 14 ++++++++++++++ src/openhuman/credentials/ops_tests.rs | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/openhuman/credentials/ops.rs b/src/openhuman/credentials/ops.rs index aea1c69d9d..c64064a5d7 100644 --- a/src/openhuman/credentials/ops.rs +++ b/src/openhuman/credentials/ops.rs @@ -394,6 +394,20 @@ pub async fn clear_session(config: &Config) -> Result Option { + let uid = read_active_user_id(root_dir)?; + crate::core::observability::set_sentry_user_id(&uid); + Some(uid) +} + pub async fn auth_get_state( config: &Config, ) -> Result, String> { diff --git a/src/openhuman/credentials/ops_tests.rs b/src/openhuman/credentials/ops_tests.rs index ad29990fbe..eb647fc696 100644 --- a/src/openhuman/credentials/ops_tests.rs +++ b/src/openhuman/credentials/ops_tests.rs @@ -736,3 +736,26 @@ async fn each_account_workspace_holds_its_own_credential_data() { assert_eq!(result_a.value[0].provider, "anthropic"); assert_eq!(result_b.value[0].provider, "anthropic"); } + +// ── warm_sentry_user_from_active_session (boot-restore) ──────── + +/// Boot-restore path: when `active_user.toml` holds an id, the warmer returns +/// it (and binds the Sentry scope) so background-loop errors carry `user.id` +/// without a fresh `store_session`. +#[test] +fn warm_sentry_user_from_active_session_returns_active_id() { + let tmp = TempDir::new().unwrap(); + let root = tmp.path(); + write_active_user_id(root, "507f1f77bcf86cd799439011").unwrap(); + + let id = warm_sentry_user_from_active_session(root); + assert_eq!(id.as_deref(), Some("507f1f77bcf86cd799439011")); +} + +/// No on-disk session (signed out / fresh install): the warmer is a no-op and +/// returns `None` so the boot path defers service startup until login. +#[test] +fn warm_sentry_user_from_active_session_returns_none_without_active_user() { + let tmp = TempDir::new().unwrap(); + assert_eq!(warm_sentry_user_from_active_session(tmp.path()), None); +} From ca2e1230fca170b57a4b38d0267b257a96749eff Mon Sep 17 00:00:00 2001 From: oxoxDev Date: Mon, 1 Jun 2026 15:54:58 +0530 Subject: [PATCH 4/7] observability(sentry): warm Sentry user on boot-restore path (#3135) Wire warm_sentry_user_from_active_session() into the existing-session boot branch so the scope is set before login-gated background services (incl. the periodic Composio sync) start. --- src/core/jsonrpc.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/core/jsonrpc.rs b/src/core/jsonrpc.rs index 3a7060df6f..72ae0fc1cb 100644 --- a/src/core/jsonrpc.rs +++ b/src/core/jsonrpc.rs @@ -1668,9 +1668,18 @@ async fn run_server_inner( }); // Check if a user is already logged in from a previous session. + // Resolving the id also warms the Sentry scope on boot-restore + // (via `warm_sentry_user_from_active_session`) so background-loop + // errors — e.g. the periodic Composio sync tick — carry the user + // id. `store_session` only fires on a fresh login, so without + // this a restored session would report `user = None`. let already_logged_in = crate::openhuman::config::default_root_openhuman_dir() .ok() - .and_then(|root| crate::openhuman::config::read_active_user_id(&root)) + .and_then(|root| { + crate::openhuman::credentials::ops::warm_sentry_user_from_active_session( + &root, + ) + }) .is_some(); if already_logged_in { From 11dc5cfb3b836098c99fa1da73a2c18fada2acf3 Mon Sep 17 00:00:00 2001 From: oxoxDev Date: Mon, 1 Jun 2026 16:36:05 +0530 Subject: [PATCH 5/7] =?UTF-8?q?observability(sentry):=20address=20review?= =?UTF-8?q?=20=E2=80=94=20bind=20user=20post-persist,=20assert=20blank-inp?= =?UTF-8?q?ut,=20add=20breadcrumbs=20(#3135)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - store_session: move set_sentry_user_id to after store_provider_token succeeds, so a failed persist no longer leaves the global scope bound. - set_sentry_user_id_ignores_blank_input: assert the captured event carries no user (test previously had no assertion). - set/clear: add PII-safe debug breadcrumbs (id length only, never the id). --- src/core/observability.rs | 14 ++++++++++++++ src/core/observability_tests.rs | 27 ++++++++++++++++++++++++++- src/openhuman/credentials/ops.rs | 20 +++++++++++--------- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/core/observability.rs b/src/core/observability.rs index 3aa7b5f5cc..c32ca294c5 100644 --- a/src/core/observability.rs +++ b/src/core/observability.rs @@ -1772,6 +1772,10 @@ pub(crate) fn report_error_message( pub fn set_sentry_user_id(user_id: &str) { let trimmed = user_id.trim(); if trimmed.is_empty() { + tracing::debug!( + target: REPORT_ERROR_TRACING_TARGET, + "[observability] set_sentry_user_id skipped: blank id" + ); return; } let owned = trimmed.to_string(); @@ -1781,6 +1785,12 @@ pub fn set_sentry_user_id(user_id: &str) { ..Default::default() })); }); + // PII-safe: log only the id length, never the id itself. + tracing::debug!( + target: REPORT_ERROR_TRACING_TARGET, + id_len = owned.len(), + "[observability] Sentry scope user bound" + ); } /// Clear the signed-in user from the process-global Sentry scope. @@ -1790,6 +1800,10 @@ pub fn set_sentry_user_id(user_id: &str) { /// previous user. Mirrors `set_sentry_user_id` — no-op under a no-op guard. pub fn clear_sentry_user() { sentry::configure_scope(|scope| scope.set_user(None)); + tracing::debug!( + target: REPORT_ERROR_TRACING_TARGET, + "[observability] Sentry scope user cleared" + ); } /// Returns true when a Sentry event is a per-attempt provider HTTP failure diff --git a/src/core/observability_tests.rs b/src/core/observability_tests.rs index ec2a4b71bf..0a26e2b2b0 100644 --- a/src/core/observability_tests.rs +++ b/src/core/observability_tests.rs @@ -3602,8 +3602,33 @@ fn set_sentry_user_id_attaches_id_to_reported_events() { #[test] fn set_sentry_user_id_ignores_blank_input() { + use sentry::test::TestTransport; + use std::sync::Arc; + // Blank / whitespace-only ids are dropped rather than attaching an empty - // `user.id` that would still count as a distinct user in Sentry. + // `user.id` that would still count as a distinct user in Sentry. Assert the + // emitted event carries no user — without this the test would pass even if + // a blank id were bound to the scope. + let transport = TestTransport::new(); + let options = sentry::ClientOptions { + dsn: Some("https://public@sentry.invalid/1".parse().unwrap()), + transport: Some(Arc::new(transport.clone())), + ..Default::default() + }; + let hub = Arc::new(sentry::Hub::new( + Some(Arc::new(options.into())), + Arc::new(Default::default()), + )); + let _guard = sentry::HubSwitchGuard::new(hub); + set_sentry_user_id(""); set_sentry_user_id(" "); + report_error("blank-id failure", "composio", "list_connections", &[]); + + let events = transport.fetch_and_clear_events(); + assert_eq!(events.len(), 1, "expected exactly one captured event"); + assert!( + events[0].user.is_none(), + "blank id must not attach a scope user" + ); } diff --git a/src/openhuman/credentials/ops.rs b/src/openhuman/credentials/ops.rs index c64064a5d7..d132238a68 100644 --- a/src/openhuman/credentials/ops.rs +++ b/src/openhuman/credentials/ops.rs @@ -171,15 +171,6 @@ pub async fn store_session( // Determine user_id so we can scope the openhuman directory to this user. let resolved_user_id = metadata.get("user_id").cloned(); - // Bind the signed-in user's id to the Sentry scope as early as possible - // (on fresh login and on account switch, both of which route through here) - // so events reported by background loops carry `user.id` even before the - // frontend `app_state_snapshot` RPC has warmed the identity cache. Only - // the id is sent — never email/name/IP. See `set_sentry_user_id`. - if let Some(ref uid) = resolved_user_id { - crate::core::observability::set_sentry_user_id(uid); - } - // If we know the user_id, activate the user-scoped directory BEFORE storing // the auth profile so that credentials land in the correct place. let mut logs = if local_session { @@ -288,6 +279,17 @@ pub async fn store_session( logs.push("session stored".to_string()); + // Bind the signed-in user's id to the Sentry scope only AFTER the session + // is persisted above — a failed `store_provider_token` must not leave the + // global scope pointing at a user whose login never completed. Covers + // fresh login and account switch (both route through here) so background + // -loop errors carry `user.id` even before the frontend `app_state_snapshot` + // RPC warms the identity cache. Only the id is sent — never email/name/IP. + // See `set_sentry_user_id`. + if let Some(ref uid) = resolved_user_id { + crate::core::observability::set_sentry_user_id(uid); + } + match crate::openhuman::memory::global::init(effective_config.workspace_dir.clone()) { Ok(_) => logs.push(format!( "memory client bound to workspace {}", From 1d5e3b1bbe5d53118631fb5975ee490696984873 Mon Sep 17 00:00:00 2001 From: oxoxDev Date: Mon, 1 Jun 2026 18:02:41 +0530 Subject: [PATCH 6/7] docs(credentials): note warm_sentry_user double-duty (id + scope side effect) --- src/openhuman/credentials/ops.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/openhuman/credentials/ops.rs b/src/openhuman/credentials/ops.rs index d132238a68..f078c0e17e 100644 --- a/src/openhuman/credentials/ops.rs +++ b/src/openhuman/credentials/ops.rs @@ -404,6 +404,11 @@ pub async fn clear_session(config: &Config) -> Result Option { let uid = read_active_user_id(root_dir)?; crate::core::observability::set_sentry_user_id(&uid); From ad1342b6c18980861840ba773b1a5a03f3202c07 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Tue, 2 Jun 2026 01:33:01 -0400 Subject: [PATCH 7/7] fix(sentry): deduplicate scope-user binding after #3176 merged on main PR #3176 landed on main while this PR was in review, implementing the same Sentry user-scope binding at session boundaries via `credentials::sentry_scope`. Remove the duplicate functions (`set_sentry_user_id`, `clear_sentry_user`, `warm_sentry_user_from_active_session`) and their call sites to avoid double-binding the scope at login, logout, and boot. --- src/core/jsonrpc.rs | 11 +-- src/core/observability.rs | 132 ------------------------- src/openhuman/credentials/ops.rs | 34 ------- src/openhuman/credentials/ops_tests.rs | 23 ----- 4 files changed, 1 insertion(+), 199 deletions(-) diff --git a/src/core/jsonrpc.rs b/src/core/jsonrpc.rs index c10a3869db..945ac56ae7 100644 --- a/src/core/jsonrpc.rs +++ b/src/core/jsonrpc.rs @@ -1684,18 +1684,9 @@ async fn run_server_inner( }); // Check if a user is already logged in from a previous session. - // Resolving the id also warms the Sentry scope on boot-restore - // (via `warm_sentry_user_from_active_session`) so background-loop - // errors — e.g. the periodic Composio sync tick — carry the user - // id. `store_session` only fires on a fresh login, so without - // this a restored session would report `user = None`. let already_logged_in = crate::openhuman::config::default_root_openhuman_dir() .ok() - .and_then(|root| { - crate::openhuman::credentials::ops::warm_sentry_user_from_active_session( - &root, - ) - }) + .and_then(|root| crate::openhuman::config::read_active_user_id(&root)) .is_some(); if already_logged_in { diff --git a/src/core/observability.rs b/src/core/observability.rs index a23cb5a179..fb1c093ec8 100644 --- a/src/core/observability.rs +++ b/src/core/observability.rs @@ -1550,63 +1550,6 @@ pub(crate) fn report_error_message( ); } -/// Bind the signed-in user's id to the process-global Sentry scope so every -/// subsequently-reported event carries `user.id`. -/// -/// Why this exists: the `before_send` hooks in `src/main.rs` and the Tauri -/// shell already pluck `user.id` from `app_state::peek_cached_current_user_identity()`, -/// but that cache is only warmed by the frontend-driven `app_state_snapshot` -/// RPC. Errors raised from background loops — e.g. the periodic Composio sync -/// tick that emits the `[composio-direct]` shapes — fire with an empty cache, -/// so the event lands with no user attribution (userCount=0). Setting the -/// scope user eagerly at the session boundary (login / account switch) makes -/// the id available regardless of whether the snapshot cache was ever warmed, -/// mirroring how the backend tags every event at the session boundary. -/// -/// Only the `id` is carried — never email, name, or IP — to stay consistent -/// with `send_default_pii: false` in the Sentry client options. The `id` is -/// the backend's mongo ObjectId for the user; it is recorded only on the -/// Sentry scope and never logged here. -/// -/// No-op when the Sentry client is a no-op guard (missing DSN) — the scope -/// mutation is simply never flushed. -pub fn set_sentry_user_id(user_id: &str) { - let trimmed = user_id.trim(); - if trimmed.is_empty() { - tracing::debug!( - target: REPORT_ERROR_TRACING_TARGET, - "[observability] set_sentry_user_id skipped: blank id" - ); - return; - } - let owned = trimmed.to_string(); - sentry::configure_scope(|scope| { - scope.set_user(Some(sentry::User { - id: Some(owned.clone()), - ..Default::default() - })); - }); - // PII-safe: log only the id length, never the id itself. - tracing::debug!( - target: REPORT_ERROR_TRACING_TARGET, - id_len = owned.len(), - "[observability] Sentry scope user bound" - ); -} - -/// Clear the signed-in user from the process-global Sentry scope. -/// -/// Called on logout so events reported after sign-out (or for a different -/// account before the next `set_sentry_user_id`) are not misattributed to the -/// previous user. Mirrors `set_sentry_user_id` — no-op under a no-op guard. -pub fn clear_sentry_user() { - sentry::configure_scope(|scope| scope.set_user(None)); - tracing::debug!( - target: REPORT_ERROR_TRACING_TARGET, - "[observability] Sentry scope user cleared" - ); -} - /// Returns true when a Sentry event is a per-attempt provider HTTP failure /// that the reliable-provider layer already handles via retry + fallback. /// @@ -4979,79 +4922,4 @@ mod tests { &[("channel", "discord")], ); } - - #[test] - fn set_sentry_user_id_attaches_id_to_reported_events() { - use std::sync::Arc; - - use sentry::test::TestTransport; - - let transport = TestTransport::new(); - let options = sentry::ClientOptions { - dsn: Some("https://public@sentry.invalid/1".parse().unwrap()), - transport: Some(Arc::new(transport.clone())), - ..Default::default() - }; - let hub = Arc::new(sentry::Hub::new( - Some(Arc::new(options.into())), - Arc::new(Default::default()), - )); - let _guard = sentry::HubSwitchGuard::new(hub); - - set_sentry_user_id("507f1f77bcf86cd799439011"); - report_error( - "simulated direct-mode failure", - "composio", - "list_connections", - &[], - ); - - let events = transport.fetch_and_clear_events(); - assert_eq!(events.len(), 1, "expected exactly one captured event"); - let user = events[0] - .user - .as_ref() - .expect("captured event should carry scope user"); - assert_eq!(user.id.as_deref(), Some("507f1f77bcf86cd799439011")); - assert!(user.email.is_none()); - assert!(user.username.is_none()); - - clear_sentry_user(); - report_error("post-logout failure", "composio", "list_connections", &[]); - let events = transport.fetch_and_clear_events(); - assert_eq!(events.len(), 1); - assert!( - events[0].user.is_none(), - "scope user must be cleared after clear_sentry_user()" - ); - } - - #[test] - fn set_sentry_user_id_ignores_blank_input() { - use sentry::test::TestTransport; - use std::sync::Arc; - - let transport = TestTransport::new(); - let options = sentry::ClientOptions { - dsn: Some("https://public@sentry.invalid/1".parse().unwrap()), - transport: Some(Arc::new(transport.clone())), - ..Default::default() - }; - let hub = Arc::new(sentry::Hub::new( - Some(Arc::new(options.into())), - Arc::new(Default::default()), - )); - let _guard = sentry::HubSwitchGuard::new(hub); - - set_sentry_user_id(""); - set_sentry_user_id(" "); - report_error("blank-id failure", "composio", "list_connections", &[]); - - let events = transport.fetch_and_clear_events(); - assert_eq!(events.len(), 1, "expected exactly one captured event"); - assert!( - events[0].user.is_none(), - "blank id must not attach a scope user" - ); - } } diff --git a/src/openhuman/credentials/ops.rs b/src/openhuman/credentials/ops.rs index 436bfd365b..d95273d46a 100644 --- a/src/openhuman/credentials/ops.rs +++ b/src/openhuman/credentials/ops.rs @@ -279,17 +279,6 @@ pub async fn store_session( logs.push("session stored".to_string()); - // Bind the signed-in user's id to the Sentry scope only AFTER the session - // is persisted above — a failed `store_provider_token` must not leave the - // global scope pointing at a user whose login never completed. Covers - // fresh login and account switch (both route through here) so background - // -loop errors carry `user.id` even before the frontend `app_state_snapshot` - // RPC warms the identity cache. Only the id is sent — never email/name/IP. - // See `set_sentry_user_id`. - if let Some(ref uid) = resolved_user_id { - crate::core::observability::set_sentry_user_id(uid); - } - match crate::openhuman::memory::global::init(effective_config.workspace_dir.clone()) { Ok(_) => logs.push(format!( "memory client bound to workspace {}", @@ -384,10 +373,6 @@ pub async fn clear_session(config: &Config) -> Result Result Option { - let uid = read_active_user_id(root_dir)?; - crate::core::observability::set_sentry_user_id(&uid); - Some(uid) -} - pub async fn auth_get_state( config: &Config, ) -> Result, String> { diff --git a/src/openhuman/credentials/ops_tests.rs b/src/openhuman/credentials/ops_tests.rs index b521f5e5e7..9dcaeb522f 100644 --- a/src/openhuman/credentials/ops_tests.rs +++ b/src/openhuman/credentials/ops_tests.rs @@ -737,26 +737,3 @@ async fn each_account_workspace_holds_its_own_credential_data() { assert_eq!(result_a.value[0].provider, "anthropic"); assert_eq!(result_b.value[0].provider, "anthropic"); } - -// ── warm_sentry_user_from_active_session (boot-restore) ──────── - -/// Boot-restore path: when `active_user.toml` holds an id, the warmer returns -/// it (and binds the Sentry scope) so background-loop errors carry `user.id` -/// without a fresh `store_session`. -#[test] -fn warm_sentry_user_from_active_session_returns_active_id() { - let tmp = TempDir::new().unwrap(); - let root = tmp.path(); - write_active_user_id(root, "507f1f77bcf86cd799439011").unwrap(); - - let id = warm_sentry_user_from_active_session(root); - assert_eq!(id.as_deref(), Some("507f1f77bcf86cd799439011")); -} - -/// No on-disk session (signed out / fresh install): the warmer is a no-op and -/// returns `None` so the boot path defers service startup until login. -#[test] -fn warm_sentry_user_from_active_session_returns_none_without_active_user() { - let tmp = TempDir::new().unwrap(); - assert_eq!(warm_sentry_user_from_active_session(tmp.path()), None); -}