Windows: a running older agent is never replaced, stranding the GUI on "waiting for the agent"
- Dominant language
- Rust
- Stars
- 21k
- Forks
- 675
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 172
Description
On Windows a newer agent cannot take over from an older one holding
`agent.lock`, so the GUI waits forever for a replacement that never arrives.
This is the same shape as #621 / #644 — a leftover process holding a lock it
cannot speak the current protocol through — except those were the Actions Ring
overlay and were fixed by #645. The agent has the same gap on Windows, and
nothing closes it there.
## What happens
An installed OpenLogi was running (`%LOCALAPPDATA%\Programs\OpenLogi`, IPC
protocol 17) while a newer build started from another directory (protocol 18):
```
WARN openlogi_gui::ipc_client: agent IPC protocol is older —
waiting for the agent to be replaced agent=17 gui=18
INFO openlogi_gui::ipc_client: agent not running — launched it
path=...\openlogi-agent.exe
INFO openlogi_agent: another openlogi-agent is already running — exiting
path=...\.config\openlogi\agent.lock
```
The GUI's spawn retry starts the new agent, the new agent loses the singleton
lock and exits, and the loop repeats. The only way out is killing the old agent
by hand.
## Why
`takeover.rs` exists for exactly this, but its Windows arm is a deliberate no-op
(`crates/openlogi-agent/src/takeover.rs:147`):
```rust
/// No Windows release has ever shipped (or auto-started) the agent, so there
/// is no pre-watcher population to migrate; from the first shipped build
/// onward, `self_restart` exits on update and the GUI's spawn retry starts
/// the new binary.
#[cfg(windows)]
fn replace_stale() -> Option {
None
}
```
The premise no longer holds: Windows releases have shipped and are installed in
the wild. The Unix arm already does the right thing — connect as an IPC client,
ask the lock holder for its `protocol_version` (method 0, wire-stable across
versions), and replace it only if it is provably older.
## Scope
`self_restart` still covers the ordinary update path, where the installed binary
is replaced in place. This gap is specifically about a *running* agent older
than the one trying to start. Two ways in:
- Running a development or portable build alongside an installed one (how I hit
it — two different install locations, so `self_restart` never applies).
- Any future `PROTOCOL_VERSION` bump reaching a machine whose old agent is still
alive at the time.
## Possible fix
Port the Unix `replace_stale` handshake to Windows. The IPC client half is
already cross-platform; only "terminate the holder" is Unix-specific (`SIGTERM`
via `sysinfo::Process::kill`), and `tray_windows.rs::quit` already terminates
processes on Windows through the same `sysinfo` API — so the primitive exists.
The `cfg!(debug_assertions)` guard in `try_replace_stale` should stay as it is:
a dev build must never displace the user's production agent.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in crates/openlogi-agent/src/takeover.rs at the Windows replace_stale arm and compare it with the Unix implementation and try_replace_stale. Review tray_windows.rs::quit for the existing Windows process-termination primitive. Done means an older running agent can be replaced only when its protocol is older, while the debug-build guard remains unchanged and the GUI no longer loops waiting for a replacement.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- desktop, distributed-systems, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100