Skip to content

fix(desktop): coordinate orphan reaping across profile backends - #65059

Closed
metamindedu wants to merge 2 commits into
NousResearch:mainfrom
metamindedu:fix/desktop-profile-orphan-ownership
Closed

fix(desktop): coordinate orphan reaping across profile backends#65059
metamindedu wants to merge 2 commits into
NousResearch:mainfrom
metamindedu:fix/desktop-profile-orphan-ownership

Conversation

@metamindedu

@metamindedu metamindedu commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Prevents one Desktop profile backend from ending a durable session while another live Desktop backend process still owns and runs that same session.

The Desktop can temporarily serve one profile from two Python backends:

  • the primary backend, using a per-session profile_home; and
  • the profile sidecar, using that profile as its process-level HERMES_HOME.

Both can hold a runtime for the same durable session. Process-local orphan checks cannot see the sibling backend, so a WebSocket disconnect could previously mark the shared profile state.db row ended and interrupt durable-key delegations even while the primary worker was still producing messages.

This PR uses the profile-local active-session registry as a cross-process ownership signal. Desktop sessions retain liveness leases even when max_concurrent_sessions is disabled; other surfaces keep the existing unlimited/no-op behavior. Automatic Desktop finalization atomically releases the local lease and checks sibling ownership while holding the registry lock across the durable DB decision.

The protected automatic reasons are:

  • ws_orphan_reap
  • ws_disconnect
  • idle_timeout
  • lru_evict
  • tui_shutdown

If another live lease exists—or registry/PID ownership cannot be inspected safely—the local runtime is cleaned up but the durable row and sibling delegation work are preserved. A sole owner still ends normally, and explicit user-close semantics are unchanged.

This is complementary to #44102 and #49900, which guard same-process running/agent-build/pending-work state. It also differs from #60609 and #63207: those protect gateway-owned messaging sessions, while this bug involves separate source=desktop backend processes.

Related Issue

Related to #60609 and #63207, but this is a distinct cross-process Desktop profile-backend ownership case.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

hermes_cli/active_sessions.py

  • Route registry reads/writes through an explicit profile home.
  • Track Desktop liveness independently from concurrency-limit enforcement.
  • Preserve malformed/unreadable registries instead of treating them as empty, including valid JSON with invalid entry identity/types or conflicting lease IDs.
  • Distinguish dead, live, and unknown PID ownership; PID reuse is checked with process start time when available.
  • Make strict liveness acquire/inspect/transfer/release errors visible to callers.
  • Keep failed releases retryable and prevent a release-vs-transfer race from resurrecting an entry.
  • Add a lock-held local-release + sibling-liveness guard for automatic finalization.

tui_gateway/server.py

  • Fail Desktop create/resume/branch closed when liveness ownership cannot be registered; preserve existing TUI behavior.
  • Carry profile_home, profile DB, and lease ownership through initial session publication.
  • Route profile branch persistence and agent construction through the owning profile DB.
  • Guard all five automatic Desktop end reasons against live sibling leases.
  • Preserve durable rows and durable-key delegations when sibling ownership exists or inspection is unknown.
  • Retry transient liveness-release failures without dropping the lease reference.
  • Keep compression key rotation quiescent until lease transfer succeeds, preventing old/new durable-key mismatch from leaking into the next RPC or queued prompt.
  • Key live-session reuse and delegated-child mirrors by (profile_home, durable_key) so equal keys in different profiles remain isolated.
  • Route live title/status/history, retry truncation, /undo, pending titles, and auto-title through the owning profile DB.
  • Hold notification-driven turns behind the same ownership-transfer barrier as foreground RPCs, and roll back running when another consumer already owns delivery.

Tests

  • Real Windows subprocess/file-lock coverage for profile-local sibling ownership.
  • Lock-boundary handshakes for acquire/finalize serialization.
  • Automatic-reason matrix for sibling preservation and sole-owner completion.
  • Corrupt/structurally invalid registry, conflicting lease ID, read/write/replace failure, PID unknown/reuse, acquire fail-closed, transfer failure, release retry, and release-vs-transfer race coverage.
  • Same-key cross-profile live identity plus profile DB create/resume/branch/title/status/history/retry/undo/auto-title routing coverage.
  • Notification ownership-transfer failure/retry and delivery-claim rollback coverage.
  • Existing gateway-owned, delegation lifecycle, protocol, and session-limit regressions.

How to Test

  1. Run the ownership and related gateway regressions in a clean non-Desktop environment:

    env -u PYTHONPATH -u HERMES_DESKTOP -u HERMES_DESKTOP_TERMINAL \
      uv run pytest \
        tests/hermes_cli/test_active_sessions.py \
        tests/hermes_cli/test_cli_active_session_limit.py \
        tests/tui_gateway/test_cross_process_orphan_ownership.py \
        tests/tui_gateway/test_profile_session_ownership.py \
        tests/tui_gateway/test_gateway_owned_session_reap.py \
        tests/tui_gateway/test_delegation_session_lifecycle.py \
        tests/tui_gateway/test_protocol.py \
        -q -o addopts=

    Result on Windows 10 / CPython 3.11: 137 passed.

  2. Run the full TUI gateway server regression file:

    env -u PYTHONPATH -u HERMES_DESKTOP -u HERMES_DESKTOP_TERMINAL \
      uv run pytest tests/test_tui_gateway_server.py -q -o addopts=

    Result: 325 passed.

  3. Run the separate compaction status regressions:

    env -u PYTHONPATH -u HERMES_DESKTOP -u HERMES_DESKTOP_TERMINAL \
      uv run pytest tests/tui_gateway/test_compaction_status.py -q -o addopts=

    Result: 4 passed.

  4. Run static checks:

    uv run ruff check \
      hermes_cli/active_sessions.py \
      tui_gateway/server.py \
      tests/hermes_cli/test_active_sessions.py \
      tests/tui_gateway/test_cross_process_orphan_ownership.py \
      tests/tui_gateway/test_profile_session_ownership.py
    
    env -u PYTHONPATH uv run ty check \
      hermes_cli/active_sessions.py \
      tests/hermes_cli/test_active_sessions.py \
      tests/tui_gateway/test_cross_process_orphan_ownership.py \
      tests/tui_gateway/test_profile_session_ownership.py
    
    env -u PYTHONPATH uv run python -m py_compile \
      hermes_cli/active_sessions.py tui_gateway/server.py
    
    git diff --check origin/main...HEAD

    Results: Ruff, py_compile, diff check, and the focused ty check passed. tui_gateway/server.py retains existing repository ty diagnostics; none fall on this PR's changed lines.

A total of 466 related tests passed locally. The repository-wide suite is deferred to CI.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched open/closed PRs and issues for duplicates
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q and all tests pass locally (466 related tests passed; full suite deferred to CI)
  • I've added tests for my changes
  • I've tested on Windows 10 / CPython 3.11

Documentation & Housekeeping

  • Relevant API docstrings were updated; no user-facing docs or config keys changed
  • Cross-platform impact was considered: Windows exercises msvcrt locally; CI will exercise POSIX flock
  • Tool descriptions/schemas are not affected

Screenshots / Logs

No UI changes. The spawned-process regression tests are the executable reproduction and verification evidence.

@alt-glitch alt-glitch added type/bug Something isn't working comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/cli CLI entry point, hermes_cli/, setup wizard sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state P2 Medium — degraded but workaround exists labels Jul 15, 2026
@metamindedu
metamindedu force-pushed the fix/desktop-profile-orphan-ownership branch 3 times, most recently from a82a711 to 1f8ded8 Compare July 15, 2026 22:57
@metamindedu
metamindedu force-pushed the fix/desktop-profile-orphan-ownership branch 2 times, most recently from dfb39fd to c6eaeb3 Compare July 16, 2026 00:29
@metamindedu
metamindedu force-pushed the fix/desktop-profile-orphan-ownership branch from c6eaeb3 to 9389d65 Compare July 16, 2026 00:36
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused cross-process ownership work. The premise remains present on current main: unlimited sessions retain only a no-op active-session lease (hermes_cli/active_sessions.py:246-254), profile-backed Desktop creation does not pass profile_home into _claim_active_session_slot (tui_gateway/server.py:5233-5263), and automatic finalization can end the durable row after only local cleanup (tui_gateway/server.py:545-641).

The diff adds profile-local, fail-closed Desktop liveness leases and holds the registry lock through the automatic lifecycle decision. It also correctly preserves explicit-close behavior and keeps gateway-owned-session protections separate. The new process/lock-boundary and profile-identity coverage matches the affected paths.

No substantive defect was identified in this review. This is an automated hermes-sweeper review.

@OutThisLife

Copy link
Copy Markdown
Collaborator

Superseded by #96511.

That salvage keeps the Desktop liveness lease and lock-held sibling check, rebased onto current main, and:

  • writes a real lease for Desktop even when the session cap is off
  • preserves durable rows on automatic ends when another live backend still holds the session
  • drops the profile-DB routing that already landed on main, and the stale tui_gateway/server.py rewrite

You're credited via Co-authored-by. Thanks for the cross-process ownership design — the original PR was right about the bug, just too far behind to merge.

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

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping comp/cli CLI entry point, hermes_cli/, setup wizard comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants