Skip to content

feat: Sync library executable state during installed games scan#2328

Open
IsaiahBideshi wants to merge 9 commits into
hydralauncher:mainfrom
IsaiahBideshi:fix-clear-stale-executable-paths
Open

feat: Sync library executable state during installed games scan#2328
IsaiahBideshi wants to merge 9 commits into
hydralauncher:mainfrom
IsaiahBideshi:fix-clear-stale-executable-paths

Conversation

@IsaiahBideshi

@IsaiahBideshi IsaiahBideshi commented Jun 14, 2026

Copy link
Copy Markdown

Add a step during installed game scan that resets stale install metadata and disables automatic cloud sync when a game executable no longer exists.

Expose in preload.

Add UI elements that show which executables were cleared.

When submitting this pull request, I confirm the following (please check the boxes):

  • I have read the Hydra documentation.
  • I have checked that there are no duplicate pull requests related to this request.
  • I have considered, and confirm that this submission is valuable to others.
  • I accept that this submission may not be used and the pull request may be closed at the discretion of the maintainers.

Fill in the PR content:
There was some undesirable behaviour after deleting a game from your files and not in the launcher, the game is still marked as installed. This PR adds to the "Scan PC for Installed Games" feature, it removes any games in the library whose executable file no longer exists on the PC. I've also changed the wording on the button and in the modal to better match what the scan now does which is sync the library.

Add a step during installed game scan that resets stale install metadata and disables automatic cloud sync when a game executable no longer exists.

Expose in preload.

Add UI elements that show which executables were cleared.
@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends the "Scan PC for Installed Games" flow with a second step that clears stale executable paths (and disables automatic cloud sync) for library games whose executable file no longer exists on disk, then surfaces the results in the existing scan modal.

  • A new removeUninstalledGameExecutables IPC handler iterates all non-deleted library entries that have an executablePath, checks for file existence with fs.existsSync, and resets the path/metadata for any that are missing.
  • The renderer calls the new handler sequentially after scanInstalledGames and displays removed-game results in the modal alongside found-game results.
  • Locale strings and type declarations are updated to match the broader "sync" framing.

Confidence Score: 3/5

Safe to review further but needs the custom-game filter fixed before merging — the removal step will silently wipe executable paths for manually-added library entries.

The new removal handler omits the game.shop !== "custom" guard that the sibling scan handler explicitly includes, so any manually-added game with a recorded executable path will have that path cleared the first time a user runs the sync. This is a silent, data-mutating regression for a subset of users. All other changes — preload exposure, type declarations, modal rendering, and locale strings — look correct.

src/main/events/library/remove-uninstalled-game-executables.ts needs the missing shop filter; src/renderer/src/components/header/scan-games-modal.tsx has no loading indication while the removal step runs after the scan completes.

Important Files Changed

Filename Overview
src/main/events/library/remove-uninstalled-game-executables.ts New IPC handler that clears stale executable paths — misses the game.shop !== "custom" guard present in the sibling scan handler, which will unintentionally clear executables for manually-added games.
src/renderer/src/components/header/header.tsx Calls removeUninstalledGameExecutables sequentially after scanInstalledGames in the same try/finally block; scan results and removal results are tracked separately in state.
src/renderer/src/components/header/scan-games-modal.tsx Adds a removal-results section inside the existing scanResult conditional block; no loading indicator is shown while the removal step is in progress after the scan completes.
src/preload/index.ts Correctly exposes removeUninstalledGameExecutables via contextBridge; matches the declaration in declaration.d.ts.
src/locales/en/translation.json Adds two new locale keys for removal results; renames existing scan strings to reflect the broader sync behaviour.
src/main/events/library/index.ts Imports the new event module in the correct location alongside other library events.
src/renderer/src/declaration.d.ts Type declaration for removeUninstalledGameExecutables correctly matches the return shape from the main-process handler.

Sequence Diagram

sequenceDiagram
    participant UI as ScanGamesModal
    participant Header as Header (renderer)
    participant Preload as preload/ipcRenderer
    participant Scan as scanInstalledGames (main)
    participant Remove as removeUninstalledGameExecutables (main)
    participant DB as gamesSublevel (LevelDB)
    participant WM as WindowManager

    UI->>Header: onStartScan()
    Header->>Header: setIsScanning(true), setScanResult(null), setRemoveExeResult(null)
    Header->>Preload: scanInstalledGames(dirs, includeDefault)
    Preload->>Scan: IPC invoke
    Scan->>DB: iterator().all() filter !isDeleted and shop not custom
    Scan->>DB: put(key, updatedGame) for each found executable
    Scan->>WM: sendToAppWindows on-library-batch-complete
    Scan-->>Header: foundGames and total
    Header->>Header: setScanResult(result)
    Header->>Preload: removeUninstalledGameExecutables()
    Preload->>Remove: IPC invoke
    Remove->>DB: iterator().all() filter !isDeleted only missing shop guard
    Remove->>DB: put(key, cleared game) for each missing executable
    Remove->>WM: "sendToAppWindows on-library-batch-complete only if removed > 0"
    Remove-->>Header: removedGames and total
    Header->>Header: setRemoveExeResult(result) and setIsScanning(false)
    Header->>UI: render scan and removal results
Loading

Reviews (1): Last reviewed commit: "feat: Sync library executable state duri..." | Re-trigger Greptile

Comment on lines +13 to +18
.iterator()
.all()
.then((results) =>
results
.filter(([_key, game]) => game.isDeleted === false)
.map(([key, game]) => ({ key, game }))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Custom-shop games incorrectly included in executable removal

The filter here does not exclude games with shop === "custom", but scanInstalledGames explicitly does (game.isDeleted === false && game.shop !== "custom"). This inconsistency means the removal step will clear executable paths for custom (manually-added) games that the scan never touched, silently breaking those library entries for users who have manually configured them.

Comment on lines +40 to +43
if (removedGames.length > 0) {
WindowManager.sendToAppWindows("on-library-batch-complete");
}
return { removedGames: removedGames, total: gamesToCheck.length };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 There is no progress indicator during the executable-removal step. After scanInstalledGames completes, isScanning is still true but scanResult is now set, so the spinner (isScanning && !scanResult) is hidden. The user sees scan results but has no indication that the second step (removal) is still running. Consider adding a separate isRemovingExecutables flag and a corresponding loading state in the modal.

Suggested change
if (removedGames.length > 0) {
WindowManager.sendToAppWindows("on-library-batch-complete");
}
return { removedGames: removedGames, total: gamesToCheck.length };
WindowManager.sendToAppWindows("on-library-batch-complete");
return { removedGames: removedGames, total: gamesToCheck.length };

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@chubbygrannychaser chubbygrannychaser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found one ordering issue in the library sync flow.

setScanResult(result);
setIsScanning(false);
setIsRemovingExecutables(true);
const exeResult =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cleanup runs after scanInstalledGames, but the scan only considers games where !game.executablePath. A game with a stale executable path is therefore skipped by this scan, then cleared here, so if it is actually installed elsewhere the same sync pass won't rediscover it; the user has to run sync a second time. Please clear missing executable paths before calling scanInstalledGames, or have the scan include entries whose current executable path no longer exists.

@chubbygrannychaser chubbygrannychaser left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous ordering issue is fixed, but I found one remaining modal state issue.

clickOutsideToClose={!isScanning && !isRemovingExecutables}
>
<div className="scan-games-modal">
{!scanResult && !isScanning && (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When isRemovingExecutables is true, isScanning is already false and scanResult is still null, so this initial options block remains visible at the same time as the new removal spinner below. Please also gate this on !isRemovingExecutables so users don't see the scan options/buttons while the cleanup step is running.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I hide the button while scanning and cleaning up? Right now, on the release version, the "Start Scan" button is shown but disabled while scanning. I think it's best to just remove it from view completely during both steps.
image

@Hachi-R Hachi-R left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are still a few sonar issues left. could you please fix them before this can be merged?

Comment thread src/locales/en/translation.json Outdated
"scan_games_detection_warning": "Detection relies on a community-maintained list of known games and may be outdated, so some games might not be found."
"scan_games_detection_warning": "Detection relies on a community-maintained list of known games and may be outdated, so some games might not be found.",
"remove_executables_in_progress": "Checking for uninstalled game executables...",
"remove_executables_result": "Removed {{removed}} of {{total}} uninstalled game executables",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

total seems to be the number of games checked, not the number of uninstalled executables, so removed {{removed}} of {{total}} uninstalled game executables can sound like all checked games were uninstalled.

could you please adjust this text to something like removed {{removed}} stale executable paths from {{total}} checked games or simply removed {{removed}} stale executable paths?

@Hachi-R Hachi-R added enhancement New feature or request waiting changes labels Jun 22, 2026
@Hachi-R Hachi-R self-assigned this Jun 22, 2026
@sonarqubecloud

Copy link
Copy Markdown

@IsaiahBideshi IsaiahBideshi requested a review from Hachi-R June 22, 2026 13:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request waiting changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants