Skip to content

fix(credentials): re-resolve active-user workspace in channel runtime + scheduler after store_session (#4398)#4411

Open
M3gA-Mind wants to merge 3 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/GH-4398-reresolve-config-after-store-session
Open

fix(credentials): re-resolve active-user workspace in channel runtime + scheduler after store_session (#4398)#4411
M3gA-Mind wants to merge 3 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/GH-4398-reresolve-config-after-store-session

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Re-resolve the active-user workspace in long-lived subsystems after credentials::ops::store_session, so a sign-in that happens after the core started (pre-login) stops routing writes to users/local/.
  • Cron scheduler and channel runtime now read workspace through re-bindable holders (mirroring memory::global::init) instead of a boot-time Config snapshot; re-pointed from store_session_inner alongside the existing memory/conversation rebind.
  • Extends the rebind to the agent security sandbox (live_policy), the channel memory store, and the Telegram busy-state + PROFILE.md subscribers.
  • Agent-definition registry re-resolution is deferred to a follow-up (needs an OnceLock -> RwLock refactor — see ## Related).

Problem

When openhuman-core starts before any user has logged in, it opens its DBs/profiles under ~/.openhuman/users/local/. A later sign-in over RPC calls credentials::ops::store_session, which writes ~/.openhuman/active_user.toml and creates ~/.openhuman/users/<user_id>/. But long-lived subsystems hold a cached Config snapshot from startup and never re-resolve, so they keep reading/writing under users/local/ until a process restart. store_session_inner already rebinds memory + conversation persistence (#2445); the scheduler and channel runtime did not.

Split from the #2437 triage meta-issue (item E).

Solution

Each subsystem's workspace resolution is routed through a re-bindable holder and re-pointed from store_session_inner right after the memory/conversation rebind. All calls are no-ops when the subsystem isn't running in-process.

  • Cron scheduler — new process-global ACTIVE_CONFIG holder; the poll loop re-resolves config + SecurityPolicy every tick, so due_jobs reads the cron store under users/<user_id>/. New cron::scheduler::rebind(config).
  • Channel runtimeChannelRuntimeContext.workspace_dir (baked Arc<PathBuf>) is now a shared Arc<RwLock<PathBuf>> handle read via workspace_dir(); channels::rebind_workspace(path) swaps it in place. The Telegram busy-state subscriber and PROFILE.md writer read the same handle, so they re-resolve for free — this also fixes a stale-workspace event drop the base change would otherwise introduce (events are stamped with the current workspace, so a baked snapshot in the subscriber would drop them all post-login).
  • Security sandboxlive_policy::set_workspace_dir(ws, autonomy) rebuilds the policy via SecurityPolicy::from_config (fresh canonical-path cache), so file-writing tools stay confined to the activated user's workspace. Reuses the existing hot-swap LiveState.
  • Channel memory storectx.memory is now a swappable handle; channels::rebind_memory(config) rebuilds it via a shared build_channel_memory helper (with the [Bug] Telegram and Discord messaging channels broken — show connected but messages not sending/receiving #3712 keyword-only fallback) so conversation auto-save + memory-context retrieval land in the right workspace. The swap is race-safe: an in-flight turn keeps the store it already cloned; new turns pick up the new one.

Design note / tradeoff: the tool registry (Config snapshot) and the assembled system prompt bake workspace at boot; re-resolving those means reconstructing live objects mid-turn (race hazards). They are left for a follow-up and are lower-impact because agent tools already write memory through the already-rebound memory::global client.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy — scheduler re-resolves to the activated workspace after rebind; workspace handle re-resolves + no-op when unregistered; live_policy::set_workspace_dir swaps workspace while preserving autonomy/action_dir; memory-rebind no-op guard.
  • Diff coverage ≥ 80%confirmed green by CI: the Rust Core Coverage (cargo-llvm-cov) lane passed on this PR, enforcing ≥ 80% coverage on the changed Rust lines (rebind seams unit-tested; store_session_inner wiring + memory-store rebuild exercised).
  • Coverage matrix updated — N/A: behaviour-preserving workspace-resolution fix; no new feature rows
  • All affected feature IDs from the matrix are listed under ## RelatedN/A (no matrix rows affected)
  • No new external network dependencies introduced
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: no release-cut surface change
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Runtime: desktop + headless core (CLI/docker/cloud). No UI, mobile, or web changes.
  • Behaviour: after a pre-login-started core signs in, scheduled cron jobs, agent file writes, channel memory, Telegram /status, and PROFILE.md all route to users/<user_id>/ instead of users/local/, without a restart.
  • Security: tightens the sandbox — the agent action policy now re-confines to the activated user's workspace on login rather than staying pinned to users/local/.
  • Performance: the scheduler recomputes SecurityPolicy::from_config per tick and ctx.workspace_dir()/ctx.memory() take an uncontended RwLock read per use — negligible.
  • Migration/compat: none. Holders fall back to the startup snapshot when unbound; all rebind entry points no-op when the subsystem isn't running.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/GH-4398-reresolve-config-after-store-session
  • Commit SHA: 6dd5194

Validation Run

  • pnpm --filter openhuman-app format:checkN/A: Rust-only change; frontend (app/src) untouched (Frontend Checks correctly skipped).
  • pnpm typecheckN/A: no TypeScript changed.
  • Focused tests — the Rust unit/integration tests compile and run green in CI (Rust Core Coverage lane builds + executes them); cargo check -p openhuman --tests also passes locally (exit 0).
  • Rust fmt/check — verified locally green: cargo fmt --all -- --check (exit 0) and cargo clippy -p openhuman (exit 0); the Rust Quality (fmt, clippy) CI lane passed.
  • Tauri fmt/check (if changed) — N/A: Tauri shell (app/src-tauri) untouched.

Validation Blocked

  • command: cargo check / pnpm test:rust
  • error: local check/build matrix intentionally not run per this workflow's rules
  • impact: build + tests validated by CI; change was covered by two independent static compile-reviews (no compile-blocking issues found)

Behavior Changes

  • Intended behavior change: long-lived subsystems re-resolve to the activated user's workspace on login instead of a process restart.
  • User-visible effect: after signing in on a core that started before login, cron jobs / channel memory / agent writes / Telegram status / PROFILE.md go to the correct per-user directory.

Parity Contract

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this
  • Resolution: N/A

…ystems after store_session (tinyhumansai#4398)

Long-lived subsystems started from a pre-login Config snapshot (workspace =
users/local/) and never re-resolved after credentials::ops::store_session wrote
active_user.toml, so they kept reading/writing under users/local/ until a
process restart. Route each subsystem's workspace resolution through a
re-bindable holder (mirroring memory::global::init) and re-point them all from
store_session_inner, right after the existing memory/conversation rebind.

- Cron scheduler: process-global ACTIVE_CONFIG holder; the poll loop re-resolves
  config + SecurityPolicy each tick, so due_jobs reads the cron store under
  users/<user_id>/. New scheduler::rebind(config).
- Channel runtime: ChannelRuntimeContext.workspace_dir (baked Arc<PathBuf>) is
  now a shared Arc<RwLock<PathBuf>> handle read via workspace_dir();
  channels::rebind_workspace(path) swaps it. The Telegram busy-state subscriber
  and PROFILE.md writer share the same handle (fixes a stale-workspace event
  drop the base change would otherwise introduce).
- Security sandbox: live_policy::set_workspace_dir rebuilds the policy via
  from_config so file-writing tools stay confined to the activated user's
  workspace.
- Channel memory store: ctx.memory is now a swappable handle;
  channels::rebind_memory(config) rebuilds it (shared build_channel_memory
  helper with the tinyhumansai#3712 keyword-only fallback) so conversation auto-save and
  memory-context retrieval land in the right workspace.

Agent-definition registry re-resolution is deferred to a follow-up: it needs an
invasive OnceLock -> RwLock refactor of AgentDefinitionRegistry::GLOBAL with
~8 &'static caller ripples, out of scope for this change.

Tests: scheduler re-resolves to the activated workspace after rebind; workspace
handle re-resolves + no-op when unregistered; live_policy::set_workspace_dir
swaps workspace while preserving autonomy/action_dir; memory rebind no-op guard.

Closes tinyhumansai#4398
@M3gA-Mind M3gA-Mind requested a review from a team July 2, 2026 11:36
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 5 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3562bedc-1f36-4599-be3d-33e1aadd1109

📥 Commits

Reviewing files that changed from the base of the PR and between f979bfa and 1455ff9.

📒 Files selected for processing (25)
  • src/openhuman/channels/context.rs
  • src/openhuman/channels/mod.rs
  • src/openhuman/channels/providers/telegram/bus.rs
  • src/openhuman/channels/providers/telegram/bus_tests.rs
  • src/openhuman/channels/providers/telegram/remote_control.rs
  • src/openhuman/channels/routes.rs
  • src/openhuman/channels/routes_tests.rs
  • src/openhuman/channels/runtime/dispatch/processor.rs
  • src/openhuman/channels/runtime/memory_rebind.rs
  • src/openhuman/channels/runtime/mod.rs
  • src/openhuman/channels/runtime/startup.rs
  • src/openhuman/channels/runtime/test_support.rs
  • src/openhuman/channels/runtime/workspace.rs
  • src/openhuman/channels/tests/context.rs
  • src/openhuman/channels/tests/discord_integration.rs
  • src/openhuman/channels/tests/memory.rs
  • src/openhuman/channels/tests/runtime_dispatch.rs
  • src/openhuman/channels/tests/runtime_tool_calls.rs
  • src/openhuman/channels/tests/telegram_integration.rs
  • src/openhuman/credentials/ops.rs
  • src/openhuman/cron/scheduler.rs
  • src/openhuman/cron/scheduler_tests.rs
  • src/openhuman/learning/profile_md_renderer.rs
  • src/openhuman/security/live_policy.rs
  • tests/learning_phase4_integration_test.rs

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6dd5194165

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

effective_config.workspace_dir.clone(),
&effective_config.autonomy,
);
crate::openhuman::channels::rebind_memory(&effective_config);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rebind the channel tools, not only the context memory

When the channel runtime starts pre-login and the user signs in, this call only swaps ChannelRuntimeContext.memory_handle; it does not update the tool instances already captured in ctx.tools_registry. That registry is built once with Arc::clone(&mem) in channels/runtime/startup.rs, and the dispatch path keeps passing the same registry to agent.run_turn, while memory_store/memory_recall/memory_forget store that original Arc<dyn Memory> in their structs. As a result, after login the pre-turn memory context and autosave use the activated user's workspace, but any memory tool call from the same channel turn still reads/writes users/local, causing split-brain and possible cross-user memory writes; rebuild/swap the channel tool registry or make the memory tools dereference the same rebindable handle.

Useful? React with 👍 / 👎.

- rebind_in: make the RwLock write match a statement so the write guard
  temporary drops before the handle Arc (fixes E0597 borrow-lifetime error)
- apply cargo fmt to touched channels/learning files
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Fixed the Rust Quality (fmt, clippy) lane:

  • clippy was failing to compile with error[E0597] in the new channels/runtime/workspace.rs::rebind_in — the match handle.write() { … } held its RwLockWriteGuard temporary until the end of the block, so it outlived the handle Arc (dropped first). Made the match a statement (};) so the guard temporary drops before handle.
  • fmt: ran cargo fmt; reflowed a few long lines in channels/mod.rs, telegram/bus_tests.rs, and learning/profile_md_renderer.rs.

Verified locally: cargo clippy -p openhuman and cargo fmt --all -- --check both exit 0. No behavior change.

…signatures

The tinyhumansai#4398 change migrated three constructors to shared handles but left
three test call sites on the old signatures, breaking test compilation
(caught by the Rust Core Coverage lane, which builds tests; plain
clippy -p openhuman does not):

- channels/routes_tests.rs: TelegramRemoteSubscriber::new now takes
  Arc<RwLock<PathBuf>> (workspace handle), not PathBuf.
- channels/tests/memory.rs: ChannelRuntimeContext.memory renamed to
  memory_handle: Arc<RwLock<Arc<dyn Memory>>>.
- tests/learning_phase4_integration_test.rs: ProfileMdRenderer::new now
  takes Arc<RwLock<PathBuf>> (workspace handle), not PathBuf.

Values unchanged (wrapped in the new handle types); behaviour identical.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Follow-up: the Rust Core Coverage lane was red on a real test-compile break (not covered by my earlier fmt/clippy commit — cargo clippy -p openhuman compiles only the lib, not test targets). The #4398 signature migration to rebindable handles left three test call sites on the old signatures:

  • channels/routes_tests.rsTelegramRemoteSubscriber::new now takes Arc<RwLock<PathBuf>>, was passed a PathBuf (E0308).
  • channels/tests/memory.rsChannelRuntimeContext.memory was renamed to memory_handle: Arc<RwLock<Arc<dyn Memory>>> (E0560).
  • tests/learning_phase4_integration_test.rsProfileMdRenderer::new now takes Arc<RwLock<PathBuf>>, was passed a PathBuf (E0308; CI hadn't reached this one before failing earlier).

Fixed all three by wrapping the same values in the new handle types (behaviour unchanged). Verified locally green: cargo check -p openhuman --tests (exit 0), cargo fmt --all -- --check (exit 0); cargo clippy -p openhuman remains green.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Also ticked the PR Submission Checklist honestly now that CI has confirmed the Rust lanes: Diff coverage ≥ 80% is green via the passing Rust Core Coverage (cargo-llvm-cov) lane; focused tests build + run green in that lane; Rust fmt/check verified locally (cargo fmt --all -- --check + cargo clippy -p openhuman, both exit 0) and the Rust Quality lane passed. Frontend items are N/A (Rust-only change; Tauri shell untouched). No fabricated items.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Also ticked the PR Submission Checklist honestly now that CI confirmed the Rust lanes: Diff coverage ≥ 80% green via the passing Rust Core Coverage (cargo-llvm-cov) lane; focused tests build + run green there; Rust fmt/check verified locally (cargo fmt --all -- --check + cargo clippy -p openhuman, exit 0) and Rust Quality passed. Frontend items N/A (Rust-only; Tauri untouched).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Re-resolve active-user Config in channel runtime + scheduler after store_session (from #2437-E)

1 participant