A second window from --new-instance no longer duplicates or overwrites the first window's session (F9) - #609
Conversation
…verwrites the first window's session (F9) --new-instance skipped the single-instance slot entirely, so a second window restored the running window's saved open-tab list. It opened a copy of every tab, shared the running window's scratch buffer ids (so both windows wrote, dropped and swept the same files), and whichever window closed last overwrote the other's saved list. --new-instance now claims the slot first. If it is free, the launch is an ordinary one. If another instance holds it, this process is a secondary: it restores nothing (a file argument still opens, otherwise the usual new tab), never writes the saved list or a scratch buffer, never sweeps the buffer folder, and keeps the list already on disk when it saves settings. It loses crash recovery for its own tabs only; the unsaved-changes prompts are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
…RestoreOpenPlans is skipped The secondary tests now keep the settings file and buffer folder as the owner left them and compare them at start-up as well as at the end, so the sweep and the clear-and-save that an ordinary start does are caught on their own. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
The comment said crash recovery only. Its file tabs are not reopened at the next start after a clean close either, as the PR body already says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
|
Reviewed the diff. I found no correctness, untrusted-input or security problems, and no version, csproj or T-SQL convention issues. I did not build or run the tests. Two edge cases to consider. Neither blocks the merge.
Test coverage looks thorough for the changed paths. The read-failure test only runs on Windows and is skipped elsewhere, which is fine. |
The listener retries until it gets the pipe, so a secondary took it once the owner exited, and later launches handed their files to a window whose tabs are never saved. Without it, such a launch finds no pipe, claims the slot, and runs as the new owner. Also notes that --new-instance acts as an owner where named mutexes fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
|
Reviewed the diff. The design holds together: every writer of the open-tab list goes through Two minor points, neither blocking:
I found no problems on the repo conventions: no new warnings or |
…ner save The comment said the write hands the list back unchanged. An owner save between the read and the write is still lost, as the PR body already says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza
|
Round 2 notes: the comment in AppSettingsService.Save now says an owner save between the read and the write is still lost (the PR body already listed it under Not done). The unreadable-file path has a test, ASecondaryThatCannotReadTheSavedListDoesNotSave, which runs on Windows only. |
|
Reviewed the diff. I found no correctness, untrusted-input, or convention problems (no new warnings, and no version, T-SQL or Blazor-link changes). One low-severity behavior to be aware of: Test coverage of the changed paths looks thorough. |
Summary
A window started with
--new-instancebeside a running Studio restored the running window's saved tab list. It opened a copy of every tab. Its scratch tabs used the same buffer ids as the first window's tabs, so both windows wrote, dropped, and swept the same buffer files. The window that closed last overwrote the other window's saved list.--new-instancenow claims the single-instance slot first. If no other instance holds the slot, the launch is an ordinary one. If another instance holds it, this process runs as a secondary and leaves the saved session alone.This is review finding F9. There is no issue to close.
Behavior by launch kind
--new-instance, slot free--new-instance, slot heldThe
--new-instancelaunch never hands its work to a running instance, because the user asked for a window of their own. That is unchanged.What the secondary skips
The secondary loses one thing. Its own tabs are not remembered for the next start, after a clean close or after a crash. Nothing else changes for it. The unsaved-changes prompts do not depend on persistence. A test checks that closing a secondary with a dirty scratch tab still asks, and it does, so the prompt code is unchanged.
The settings save
Every whole-file settings save writes the
open_tabslist that the process read at startup. In a secondary, that list is stale. Recent plans, the Settings window, the server-filter toggle, and the format-settings migration all save this way.The smallest safe change is one guard in
AppSettingsService.Save, so it covers every one of those paths. In a secondary,Savefirst reads the settings file the wayLoaddoes, withSettingsFileStore.Read. It puts theopen_tabsfrom disk into the settings object, and then it writes the file.Savewrites an empty list. The read moves a bad file aside first. An empty list is not a stale one.Saveskips the write.Program.csalready accepts.How it works
Program.Maincalls the newProgram.ClaimSlotForNewInstancefor a--new-instancelaunch. The method setsSingleInstance.IsSecondaryInstancewhen another instance holds the slot. It takes a mutex name, so tests can use their own name and never meet the real slot.These places check the flag:
MainWindow.OpenFromStartupArgsskipsRestoreOpenPlansas a whole. That method also clears the saved list and sweeps the buffer folder, so a flag inside it is not enough.MainWindow.SaveOpenPlansreturns first. The debounce, the final write on close, and the update restart all come through it.MainWindow.RequestSessionPersistschedules nothing.MainWindow.HookScratchPersistencesubscribes to nothing. With no subscription, no buffer id exists, and every place that drops a buffer finds no id and does nothing.AppSettingsService.Savekeeps the list on disk, as described above.Tests
The new class
SecondaryInstanceTestshas 9 tests. Each one stages what a running owner leaves on disk. That is a list with a file entry and a scratch entry. It also holds the scratch tab's buffer and an old orphan buffer that an ordinary start sweeps.The tests compare the settings file and the buffer folder before and after. They compare content and last-write time, so a rewrite of identical bytes also fails. The test host arms no timers. So the tests call the flush methods themselves and close the window through the real close path.
The secondary tests build their scenario the way production does. A mutex stands for the running instance. The test calls
ClaimSlotForNewInstance, and then it builds a window.--new-instancelaunch beside a held slot runs as a secondary.--new-instancelaunch with no other owner claims the slot and restores normally.Fail without the fix: I reverted the four files that hold the guards,
MainWindow.axaml.cs,MainWindow.FileOps.cs,MainWindow.ScratchPersist.cs, andAppSettingsService.cs. I keptSingleInstance.csandProgram.cs, because the tests compile against them. Six of the nine tests failed and three passed. The three that passed are tests 7, 8, and 9. They check the slot claim and the unchanged ordinary start, so they must pass either way. I then restored the files.Full suite on this branch: total 1278, succeeded 1248, failed 0, skipped 30. The 30 skips are existing platform skips, and none are in the new class.
dotnet build PlanViewer.sln -c Debugreports 0 warnings and 0 errors.The new class is in the
SettingsFileStore serialcollection. The flag is process-wide and it changes whatSavewrites, so no other test class can run beside it.Not done
Program.Mainwhere a non-owner runs fully after the pipe hand-off failed is unchanged. That launch is not a secondary. It still restores and saves, and it is still last-write-wins.open_tabsstay last-write-wins in a secondary. The saved connections file and the integrations file also stay last-write-wins.--new-instancelaunch acts as an owner. It restores the saved session, as every such launch did before. A comment inProgram.ClaimSlotForNewInstancesays so.🤖 Generated with Claude Code
https://claude.ai/code/session_019n3G844aTidqrD6A6iMgza