gclo opens one window per data folder. Starting it again brings the open window to the front and ends.
WindowClaim in gclo.ViewModels: a file in the data folder (window.lock) held open and shared with nobody while the window is open, and a named event that asks the holder to come to the front.
Program.Main makes the claim after Velopack's hooks and before any window; a second start signals the first and returns.
MainWindow comes to the front when asked (restores from minimized), and gives the claim up first thing when it closes.
The command line makes no claim. Another data folder (dev channel, GCLO_DATA_DIR) is another claim.
Why
Closes#216. Two windows each kept their own copy of the settings and accounts; the one that saved last undid the other. The owner decided on one window per data folder instead of making every shared file safe for two writers.
A lock file instead of a named mutex: no thread ownership to get wrong, released by Windows however the process ends, and keyed by the folder itself.
How it was tested
WindowClaimTests: first claim is the only one, a second is not, another folder is another claim, a claim given up is free, a request reaches the holder once per request and no other folder's holder, a folder that cannot be written to stops no window, the key is the same for every spelling of a folder.
UI suite: StartingGcloAgain_OnTheSameDataFolder_EndsAtOnce_AndBringsTheOpenWindowBack minimizes the window, starts a second gclo on the same data folder, and checks that it ends by itself with exit code 0 and that the window comes back.
Gates from PowerShell: engine and view models 1,482, CLI 249, line coverage 100% on the three gated assemblies, format clean, UI suite 19 of 19.
Checklist
Commits are signed and signed off (git commit -S -s)
Zero build warnings; format and lint are clean
Tests added or updated; the 100% coverage gate still passes
Docs updated for user-visible changes; threat model updated if the attack surface changed (README, threat model review log, CLAUDE.md)
No secrets, credentials, or personal data
New dependencies are justified below, or none were added (none)
Substantial AI assistance, if any, is noted here: written with Claude Code.
## What
gclo opens one window per data folder. Starting it again brings the open window to the front and ends.
- `WindowClaim` in `gclo.ViewModels`: a file in the data folder (`window.lock`) held open and shared with nobody while the window is open, and a named event that asks the holder to come to the front.
- `Program.Main` makes the claim after Velopack's hooks and before any window; a second start signals the first and returns.
- `MainWindow` comes to the front when asked (restores from minimized), and gives the claim up first thing when it closes.
- The command line makes no claim. Another data folder (dev channel, `GCLO_DATA_DIR`) is another claim.
## Why
Closes #216. Two windows each kept their own copy of the settings and accounts; the one that saved last undid the other. The owner decided on one window per data folder instead of making every shared file safe for two writers.
A lock file instead of a named mutex: no thread ownership to get wrong, released by Windows however the process ends, and keyed by the folder itself.
## How it was tested
- `WindowClaimTests`: first claim is the only one, a second is not, another folder is another claim, a claim given up is free, a request reaches the holder once per request and no other folder's holder, a folder that cannot be written to stops no window, the key is the same for every spelling of a folder.
- UI suite: `StartingGcloAgain_OnTheSameDataFolder_EndsAtOnce_AndBringsTheOpenWindowBack` minimizes the window, starts a second gclo on the same data folder, and checks that it ends by itself with exit code 0 and that the window comes back.
- Gates from PowerShell: engine and view models 1,482, CLI 249, line coverage 100% on the three gated assemblies, format clean, UI suite 19 of 19.
## Checklist
- [x] Commits are signed and signed off (`git commit -S -s`)
- [x] Zero build warnings; format and lint are clean
- [x] Tests added or updated; the 100% coverage gate still passes
- [x] Docs updated for user-visible changes; threat model updated if the attack surface changed (README, threat model review log, CLAUDE.md)
- [x] No secrets, credentials, or personal data
- [x] New dependencies are justified below, or none were added (none)
- [x] Substantial AI assistance, if any, is noted here: written with Claude Code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Two windows on one data folder each kept their own copy of the settings and
accounts, and the one that saved last undid what the other had saved. The
owner's decision (#216): one window per data folder.
- WindowClaim (gclo.ViewModels): a window holds window.lock in its data
folder while it is open, shared with nobody. Windows gives it up when the
process ends, however it ends. A folder that cannot be written to holds no
claim and stops no window from opening.
- A second start finds the claim, asks the open window to come to the front
through an event named after the folder, and ends before any window or
file of its own exists. The open window comes out of the taskbar and
before the other windows.
- The claim is given up first thing when the window closes, so a start right
after closing opens a window of its own.
- Another data folder is another claim: the dev channel beside the stable
one, and GCLO_DATA_DIR. The command line makes no claim.
- README, threat model (review log), CLAUDE.md.
Closes#216
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
From the review of #217. This code runs at every start, before a window or
the log exists.
- The event that asks the open window to come to the front may be taken by
something this process cannot use (a gclo that runs elevated made it) or
that is no event. That ended the start with an unhandled exception. The
claim now stands as the lock file says and only the request is not made;
whatever else fails while the claim is made, the window opens without one.
- A window that could not be made gives the claim up: the process can live
on without a window, and every later start would have ended at once with
nothing to bring to the front.
- A request to come to the front that arrives while the window closes is
dropped, and is queued on the dispatcher captured at start, not read from
a window that may be gone.
Refs #216
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
One independent review: merge after fixes. It ran the claim under load (eight processes started at once, 25 rounds: one winner every time; a killed holder frees the folder; a restart after update is not swallowed, by reading Velopack). Fixed in 15f2636: the event that asks the open window forward can be unusable (a gclo that runs elevated made it) and no longer ends the start; a window that could not be made gives the claim up; a request that arrives while the window closes is dropped. Gates after the fixes: engine and view models 1,483, CLI 249, line coverage 100%, format clean, UI suite 19 of 19.
Accepted, not fixed (rare and short-lived, per the review): another program holding window.lock open (a leftover file being scanned) turns a start away until it lets go; a file left pending delete lets a second window open once; the same folder reached through another session, another machine on a shared GCLO_DATA_DIR, or a junction spelling is not recognised as the same claim's event. Not checked by anyone: whether the window really takes the foreground when gclo is started from Explorer (the UI test checks that it leaves the taskbar).
One independent review: **merge after fixes**. It ran the claim under load (eight processes started at once, 25 rounds: one winner every time; a killed holder frees the folder; a restart after update is not swallowed, by reading Velopack). Fixed in 15f2636: the event that asks the open window forward can be unusable (a gclo that runs elevated made it) and no longer ends the start; a window that could not be made gives the claim up; a request that arrives while the window closes is dropped. Gates after the fixes: engine and view models 1,483, CLI 249, line coverage 100%, format clean, UI suite 19 of 19.
Accepted, not fixed (rare and short-lived, per the review): another program holding window.lock open (a leftover file being scanned) turns a start away until it lets go; a file left pending delete lets a second window open once; the same folder reached through another session, another machine on a shared GCLO_DATA_DIR, or a junction spelling is not recognised as the same claim's event. Not checked by anyone: whether the window really takes the foreground when gclo is started from Explorer (the UI test checks that it leaves the taskbar).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
gclo opens one window per data folder. Starting it again brings the open window to the front and ends.
WindowClaimingclo.ViewModels: a file in the data folder (window.lock) held open and shared with nobody while the window is open, and a named event that asks the holder to come to the front.Program.Mainmakes the claim after Velopack's hooks and before any window; a second start signals the first and returns.MainWindowcomes to the front when asked (restores from minimized), and gives the claim up first thing when it closes.GCLO_DATA_DIR) is another claim.Why
Closes #216. Two windows each kept their own copy of the settings and accounts; the one that saved last undid the other. The owner decided on one window per data folder instead of making every shared file safe for two writers.
A lock file instead of a named mutex: no thread ownership to get wrong, released by Windows however the process ends, and keyed by the folder itself.
How it was tested
WindowClaimTests: first claim is the only one, a second is not, another folder is another claim, a claim given up is free, a request reaches the holder once per request and no other folder's holder, a folder that cannot be written to stops no window, the key is the same for every spelling of a folder.StartingGcloAgain_OnTheSameDataFolder_EndsAtOnce_AndBringsTheOpenWindowBackminimizes the window, starts a second gclo on the same data folder, and checks that it ends by itself with exit code 0 and that the window comes back.Checklist
git commit -S -s)🤖 Generated with Claude Code
70bee9e21fto15f263676eOne independent review: merge after fixes. It ran the claim under load (eight processes started at once, 25 rounds: one winner every time; a killed holder frees the folder; a restart after update is not swallowed, by reading Velopack). Fixed in
15f2636: the event that asks the open window forward can be unusable (a gclo that runs elevated made it) and no longer ends the start; a window that could not be made gives the claim up; a request that arrives while the window closes is dropped. Gates after the fixes: engine and view models 1,483, CLI 249, line coverage 100%, format clean, UI suite 19 of 19.Accepted, not fixed (rare and short-lived, per the review): another program holding window.lock open (a leftover file being scanned) turns a start away until it lets go; a file left pending delete lets a second window open once; the same folder reached through another session, another machine on a shared GCLO_DATA_DIR, or a junction spelling is not recognised as the same claim's event. Not checked by anyone: whether the window really takes the foreground when gclo is started from Explorer (the UI test checks that it leaves the taskbar).