## Summary
- Normalize cloud core URLs so users can paste a core base URL like `https://example.trycloudflare.com` and still reach the JSON-RPC endpoint.
- Apply the same normalization in the cloud-mode picker, persisted URL reads/writes, restored core mode state, and direct RPC probing.
- Add regression coverage for Cloudflare-style base URLs, existing `/rpc` URLs, and previously persisted base URLs.
## Problem
- Users connecting the desktop client to a self-hosted core through Cloudflare Tunnel may paste the tunnel base URL instead of the `/rpc` endpoint.
- The core root is reachable, but JSON-RPC calls belong on `/rpc`; using the base URL can make the connection flow fail even though the tunnel itself is healthy.
- The issue report surfaced this as a 405 during remote-core connection setup.
## Solution
- Extend `normalizeRpcUrl` to append `/rpc` when the input URL has no path, while preserving existing `/rpc` URLs and non-root paths.
- Reuse `normalizeRpcUrl` across `BootCheckGate`, `coreRpcClient`, `configPersistence`, and `coreModeSlice` so test connection, boot check, cached URL resolution, and localStorage restoration all agree.
- Keep existing HTTP restrictions unchanged: public cloud URLs still require HTTPS, while local/private HTTP hosts remain allowed.
## Submission Checklist
> If a section does not apply to this change, mark the item as `N/A` with a one-line reason. Do not delete items.
- [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement)
- [x] **Diff coverage >= 80%** — focused Vitest coverage was added for the changed URL normalization paths; CI will enforce the merged diff-coverage gate.
- [x] Coverage matrix updated — N/A: behaviour-only cloud URL normalization fix; no feature matrix row added/removed/renamed.
- [x] All affected feature IDs from the matrix are listed in the PR description under `## Related` — N/A: no coverage-matrix feature ID touched.
- [x] No new external network dependencies introduced (mock backend used per [Testing Strategy](../gitbooks/developing/testing-strategy.md#mock-policy))
- [x] Manual smoke checklist updated if this touches release-cut surfaces ([`docs/RELEASE-MANUAL-SMOKE.md`](../docs/RELEASE-MANUAL-SMOKE.md)) — N/A: no release-cut smoke checklist surface changed.
- [x] Linked issue closed via `Closes #NNN` in the `## Related` section
## Impact
- Runtime/platform: desktop/web app cloud-core connection setup and RPC URL resolution.
- Compatibility: existing stored `/rpc` URLs continue to resolve unchanged; previously stored base URLs now self-heal on read.
- Security: public HTTP cloud URLs are still rejected; no auth behavior or token storage behavior changes.
## Related
- Closes#2467
- Follow-up PR(s)/TODOs: none
---
## AI Authored PR Metadata (required for Codex/Linear PRs)
> Keep this section for AI-authored PRs. For human-only PRs, mark each field `N/A`.
### Linear Issue
- Key: N/A
- URL: N/A
### Commit & Branch
- Branch: `yuhao/fix-remote-core-cloudflare-2467`
- Commit SHA: `5e95aeed8a97acee5823d73b6dc8e92f04af00fb`
### Validation Run
- [x] `pnpm --filter openhuman-app format:check`
- [x] `pnpm typecheck`
- [x] Focused tests: `pnpm --dir app exec vitest run --config test/vitest.config.ts src/services/__tests__/coreRpcClient.test.ts src/utils/__tests__/configPersistence.test.ts src/store/coreModeSlice.test.ts src/components/BootCheckGate/__tests__/BootCheckGate.test.tsx` — 200 passed
- [x] Rust fmt/check (if changed): N/A: no Rust source changes; app format gate still ran Rust format checks.
- [x] Tauri fmt/check (if changed): N/A: no Tauri shell source changes; app format gate still ran Tauri Rust format checks.
### Validation Blocked
- `command:` N/A
- `error:` N/A
- `impact:` N/A
### Behavior Changes
- Intended behavior change: cloud core base URLs with no path are normalized to `/rpc`.
- User-visible effect: users can paste a Cloudflare Tunnel base URL into the cloud runtime picker without manually appending `/rpc`.
### Parity Contract
- Legacy behavior preserved: existing `/rpc` URLs, auth token handling, RPC POST envelopes, and public-HTTP rejection behavior are unchanged.
- Guard/fallback/dispatch parity checks: focused tests cover picker continuation, test connection, cached URL resolution, persisted URL reads/writes, and core-mode localStorage restoration.
### Duplicate / Superseded PR Handling
- Duplicate PR(s): none found for #2467 by current open issue/PR review.
- Canonical PR: this PR.
- Resolution (closed/superseded/updated): N/A.
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
* **Bug Fixes**
* Consistently normalize cloud RPC URLs: trims input, handles trailing slashes, and ensures the /rpc endpoint across input, storage, retrieval, and connection probes.
* Safer RPC logging: credentials/query/hash are redacted for logged URLs.
* **Tests**
* Expanded coverage for URL normalization across connection flows, storage/readback, and boot checks.
* **Localization**
* Added German translations for subconscious and MCP server/settings UI strings.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2480?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: YUHAO-corn <godcorn001@outlook.com>
Co-authored-by: M3gA-Mind <megamind@mahadao.com>
## Summary
- Narrows `is_session_expired_error` in `src/core/jsonrpc.rs` so `DomainEvent::SessionExpired` only fires for **confirmed OpenHuman session expiry**, not for downstream provider 401s.
- Adds `is_downstream_provider_auth_error` helper for diagnostic logging only (no session side-effects).
- Adds `'provider_auth'` error kind to `CoreRpcErrorKind` in `coreRpcClient.ts`; tightens `classifyRpcError` with the same HTTP-method-prefix logic.
- Fixes Discord card-click logout (issue #2285) as a direct consequence.
## Root Cause
`is_session_expired_error` used a loose `"401 + unauthorized"` string match. Discord bot-token failures arrive as `"Discord API error: Discord list guilds failed (401): Unauthorized"` — which contains both "401" and "unauthorized" — causing the full user session to be cleared on every Discord card interaction.
## Fix
OpenHuman backend errors (from `authed_json` in `src/api/rest.rs`) always use the format `"{HTTP_METHOD} /path failed (401 Unauthorized): {body}"`. Provider errors start with the provider name. The fix keeps the `"401 + unauthorized"` branch only when the message starts with an HTTP method verb, which matches backend paths while excluding Discord, OpenAI, Anthropic, Composio, etc.
## Test plan
- [x] `src/core/jsonrpc_tests.rs` — 10 `is_session_expired_error` tests covering: HTTP-method-prefix matches, Discord exclusion, BYO-key exclusion, Composio exclusion, explicit markers still match
- [x] `app/src/services/__tests__/coreRpcClient.test.ts` — 3 new `test.each` rows: Discord/OpenAI/Anthropic 401 → `provider_auth`; existing `GET /teams failed (401 Unauthorized)` → `auth_expired` preserved
- [x] `cargo test -p openhuman is_session_expired` — 10/10 pass
- [x] `pnpm test:coverage` — full Vitest suite pass
- [x] `pnpm compile` + `cargo check` — clean
- [x] `pnpm format:check` — clean
## Submission Checklist
- [x] Tests added or updated (happy path + at least one failure / edge case)
- [x] Diff coverage ≥ 80% — all new/changed lines in `jsonrpc.rs` and `coreRpcClient.ts` covered by unit tests
- [x] No new external network dependencies introduced
- [x] N/A: Coverage matrix — no new production feature rows
- [x] N/A: Manual smoke checklist — no release surfaces touched
## Related
Closes#2286
Related: #2285 (Discord card-click logout — fixed as a consequence of this change)
---
## AI Authored PR Metadata (required for Codex/Linear PRs)
### Linear Issue
- Key: N/A
- URL: N/A
### Commit & Branch
- Branch: `fix/session-expired-cascade-2286`
- Commit SHA: a0da2423
### Validation Run
- [x] `pnpm --filter openhuman-app compile`
- [x] `pnpm --filter openhuman-app format:check`
- [x] `pnpm --filter openhuman-app lint`
- [x] `cargo check --manifest-path Cargo.toml`
- [x] `pnpm test:coverage` (Vitest full suite)
- [x] `cargo test -p openhuman is_session_expired` (10/10 pass)
### Validation Blocked
- `command:` N/A
- `error:` N/A
- `impact:` N/A
### Behavior Changes
- Intended behavior change: provider-auth 401s (Discord, OpenAI BYO-key, Composio direct-mode) no longer clear the user session
- User-visible effect: clicking the Discord channel card no longer logs the user out; BYO-key misconfiguration no longer forces re-auth
### Parity Contract
- Legacy behavior preserved: OpenHuman backend 401s (`GET /teams failed (401 Unauthorized)`) still trigger session expiry
- Guard/fallback/dispatch parity checks: `api_error` in `inference/provider/ops.rs` still publishes SessionExpired directly for backend auth failures (independent of this fix)
### Duplicate / Superseded PR Handling
- Duplicate PR(s): none
- Canonical PR: this PR
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved error classification to distinguish user session expiry from external provider authentication failures, reducing mistaken session terminations and improving recovery and logging behavior.
* **Tests**
* Expanded and tightened test coverage to ensure confirmed session-expiry signals are detected while external API 401/unauthorized responses do not trigger session-expiry handling.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2356?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: M3gA-Mind <megamind@mahadao.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>