Skip to content

Stop CodexHub-managed Gateway when the app fully exits #112

Closed

Description

@NOirBRight

Reporter evidence

A user reports that after completely exiting CodexHub, its Gateway remains running. The expected result is that a Gateway started or restarted by the current CodexHub session shuts down with an orderly full exit, rather than being left running.

Problem

On the current main and dev branches, the tray Exit action calls app.exit(0) directly, while the explicit Stop Gateway action calls the existing ownership-aware proxy::stop() path. The application-update path similarly calls app.restart() directly. The Gateway is started as a child process and can therefore outlive the desktop app unless an orderly terminal path stops it first.

Closing the main window is intentionally different: it hides CodexHub to the system tray and must keep the Gateway available.

Outcome

Before every orderly terminal CodexHub action, stop only a Gateway managed by the current CodexHub session through the existing bounded, ownership-verified stop path. This covers Gateways started automatically on launch and those started or restarted from this app session. A full exit must not leave such a Gateway running; a window close to tray must not stop it.

Scope and safety invariants

  • Cover tray Exit and the app-update restart path, plus any other existing orderly terminal app path found during implementation.
  • Record session ownership only after a successful CodexHub-initiated start or restart, tied to the existing verified process identity.
  • If a Gateway was already running before the current CodexHub session, do not stop it on exit.
  • If the recorded process has been replaced, restarted externally, or no longer matches its verified identity, clear/refuse ownership rather than killing an unrelated process.
  • Reuse the existing proxy::stop() safeguards (PID, executable, and port verification); do not introduce broad process-name or port-based termination.
  • Cleanup failure must be bounded, produce sanitized diagnostics, and must not hang application exit indefinitely.

Acceptance criteria

  • From one CodexHub session, manually start the Gateway, then choose tray Exit; the session-owned Gateway is stopped through the verified stop path before the app exits.
  • With auto_start_gateway enabled, a newly auto-started Gateway is stopped on a true full exit.
  • With auto_start_gateway disabled, or when a Gateway predates the current CodexHub session, full exit does not stop that pre-existing Gateway.
  • Closing the main window to the system tray leaves a session-owned Gateway running.
  • An update-triggered app restart performs the same bounded cleanup before the old app process restarts; any Gateway subsequently started by the new session is distinct from the old session's process.
  • Repeated start/exit, second-instance, Gateway-restart, changed-process-identity, and stop-failure paths do not double-stop, kill an unrelated process, or leave stale ownership state.
  • Deterministic Rust lifecycle/state-machine tests cover pre-existing, manual start, auto-start, failed start, externally replaced process, tray close, full exit, update restart, and failed stop cases.
  • A packaged Windows smoke test proves both behaviors: tray-close retains the Gateway; tray Exit makes the current session's Gateway health endpoint unavailable and its recorded process exit.

Non-goals

  • Guaranteeing cleanup after an OS crash, power loss, Task Manager kill, or other forced process termination.
  • Stopping a Gateway that this CodexHub session did not start or cannot verify as owned.
  • Changing normal manual Gateway start/stop UX, except where needed to maintain accurate session ownership or report a bounded cleanup failure.
  • Replacing the existing ownership-verified Gateway stop model.

Verification

  • Add targeted deterministic Rust lifecycle tests for the acceptance cases above.
  • Run the repository-documented relevant Rust suite: cd src-tauri; cargo test.
  • Perform the packaged Windows smoke test named in the acceptance criteria.

Expected hotset

  • src-tauri/src/main.rs
  • src-tauri/src/app_updates.rs
  • src-tauri/src/proxy.rs
  • Rust lifecycle state and tests adjacent to those modules

Dependencies

Blocked by #143. #143 is blocked by #139.

#143 owns the approved user-requested shutdown contract: close admission, cancel active upstream work, best-effort emit user_requested_shutdown, allow at most two seconds for close/flush, then force-terminate only the ownership-verified current-session Gateway. This issue consumes that result for tray Exit and update restart.

#139 owns concurrent start/stop/restart serialization, singleflight startup, and PID/UI/listener reconciliation. This issue continues to own tray Exit, update restart, close-to-tray preservation, and terminal cleanup of only the current session-owned Gateway.

The former GLM-5.2 priority path (#105 and #108) is complete and no longer blocks scheduling.

Execution contract

Execution-Contract: v3
Execution-Mode: paseo-agent
Agent-Role: implementation
Role-Category: impl
Integration-Branch: dev
Done-When: Tray Exit and update restart stop only the current-session-owned Gateway through the existing ownership-verified bounded stop path before the app exits; close-to-tray keeps it running; pre-existing or identity-mismatched Gateways are never stopped; deterministic Rust lifecycle tests and the packaged Windows smoke pass.
Verification-Class: strict
Verification-Commands: cd src-tauri; cargo test --locked; cd src-tauri; cargo clippy --locked --all-targets -- -D warnings
Manual-Evidence: Packaged Windows smoke test: from one app session start the Gateway, close the window and verify health remains available, then choose tray Exit and verify the session-owned Gateway health endpoint is unavailable and its recorded process has exited.
Architecture-Decision: resolved
Review-Owner: orchestrator

Activity

  1. added
    bugSomething isn't working
    ready-for-agentFully specified, ready for an AFK agent
    on Jul 13, 2026
  2. changed the title [-]Stop session-owned auto-started Gateway when CodexHub fully exits[/-] [+]Stop CodexHub-managed Gateway when the app fully exits[/+] on Jul 14, 2026
  3. NOirBRight commented on Jul 16, 2026

    @NOirBRight
    OwnerAuthor

    Installer file-lock impact reproduced during 0.1.5 replacement

    While replacing the prior Debug installation with the signed Normal 0.1.5 installer, the CodexHub desktop process was absent but its embedded-Python Gateway remained alive, owned port 9099, and had <install-root>\python\_bz2.pyd loaded. The installer then stopped with:

    Error opening file for writing: <install-root>\python\_bz2.pyd

    A deterministic exclusive-open probe failed because another process held the file. After identity-verifying and terminating only that exact CodexHub Gateway child, the same probe passed, port 9099 had no listener, and the installer continued successfully through Retry.

    This rules out a corrupt 0.1.5 package or file ACL as the observed cause and records a direct update/replacement impact of #112. The packaged Windows lifecycle acceptance should include one full-exit-then-replacement leg proving that no session-owned Gateway or embedded-Python file lock survives to block the installer.

  4. NOirBRight commented on Jul 19, 2026

    @NOirBRight
    OwnerAuthor

    Dispatched by campaign \c-016-lifecycle\ (orchestrator 3b6c57b6) after #143 merged (dev f8cae58). dispatch_id=\dispatch-issue-112-a1; worker=21f9bf33-b341-42d1-9f94-d7fc613e220e (codex/gpt-5.6-terra, thinking max, full-access); branch \work/issue-112-exit-cleanup\ from base f8cae58; PR target \dev; verification class strict (packaged Windows smoke executed in worker worktree: tray-close retains Gateway, tray Exit stops session-owned Gateway). Room: \gwo-c-016-lifecycle, START signal \d-112-a1-start-001.

  5. NOirBRight commented on Jul 19, 2026

    @NOirBRight
    OwnerAuthor

    [Coordinator handoff record — 2026-07-19]

    Verified candidate, WAITING_INTEGRATION; recovery ownership transferred to successor Repository Coordinator 0269bc32-a266-498f-af6f-24877d8cf8ca.

    • Candidate: b4471bc8e5fcd8a9b33eacab024e9fdf306121f2 on base f8cae58b (one commit: app_updates.rs, main.rs, proxy.rs +449/-120) — PR #112 Stop session-owned Gateway on terminal app exit #184 (OPEN, MERGEABLE, not draft).
    • CI: 7/7 green (run 29672552930).
    • Strict verification incl. packaged Windows smoke both legs (tray-close retains Gateway; tray Exit stops session Gateway) — executed on Windows, evidence in campaign record.
    • Dual-axis locked review: quality pass (room 9ae4fc1b, signal d-review-112-a1-quality-result-001) + spec pass (room 1167d810, signal d-review-112-a1-spec-result-001); identical lock (candidate/base/diff/acceptance sha256 match); non-blocking notes only.
    • WORKER_DONE be69cbb7 (signal d-112-a1r-done-001).
    • Runtime anomaly: Worker 21f9bf33 was archived prematurely at 2026-07-19T04:12:06.855Z; Campaign Orchestrator 3b6c57b6 archive was reported at 04:12:14Z, but Coordinator readback at handoff observes it idle (no archivedAt). No COMPLETED / CAMPAIGN_CLOSED was posted; do not treat the campaign as normally closed.
    • Remaining gate: Coordinator Integration Lease merge of PR #112 Stop session-owned Gateway on terminal app exit #184 into dev only — all other gates green.

    — Retiring Repository Coordinator 568ab644-bf73-42e6-8da5-a4fd07d75aec (campaign c-016-lifecycle)

  6. added a commit that references this issue on Jul 19, 2026
  7. NOirBRight commented on Jul 19, 2026

    @NOirBRight
    OwnerAuthor

    Integrated via PR #184 into dev as merge commit eff1d99c81bed96f6b0ce421d6f2900938a08300 (2026-07-19T04:24:22Z).

    Recovery readback:

    • candidate b4471bc8e5fcd8a9b33eacab024e9fdf306121f2;
    • GitHub CI 7/7 passed;
    • locked Quality and Spec review axes both passed with zero blockers;
    • packaged Windows smoke proved close-to-tray retains the session Gateway and the actual tray Exit removes the health listener, PID file, Gateway process, and app process;
    • integrated tree hash equals the reviewed candidate tree hash.

    The premature Paseo archive was recovered without creating a successor Worker or changing the candidate. Closing the Issue on the verified dev integration.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingwayfinder:taskWayfinder implementation task

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions