Skip to content

Tropy file watcher leaks across project switches, can trigger resync on wrong project #34

Description

@inactinique

Summary

Every time TropyService.init() runs for a project switch, it unconditionally replaces this.watcher with a fresh TropyWatcher instance — without ever calling .unwatch() on the previous one. The orphaned watcher's 'change' handler keeps running and reads the service's current (now-different) state at call time, so a file-system change to the old project's .tpy file after switching away can trigger a resync of whichever project is now open.

Found while auditing the ClioDeck wiki against the actual code (pass 15, brainstorm & integrations cluster, project-switch-mid-operation angle).

Current behavior (verified against code)

src/main/services/tropy-service.ts:

  • init(projectPath) (line ~82-86) does this.watcher = new TropyWatcher(); directly, with no prior .unwatch() call on any existing watcher — contrast with the same method's explicit await this.disposePreviousRegistry(); a few lines later, which shows the developer already applies a "clean up the previous instance first" pattern elsewhere in this exact function, just not to the watcher.
  • startWatching() (line ~535) sets up this.watcher.on('change', ...) — a closure over the shared TropyService singleton this, not over any project-specific snapshot.
  • If a previous project's watcher was actively watching (existingProject.autoSync === true, per the auto-start logic at line ~113-119) and the user switches to a different project, the old TropyWatcher instance is simply dropped (garbage-collectable in principle, but its underlying OS-level file watch is never explicitly torn down via .unwatch()), so it can keep firing.
  • Because this is the same shared singleton instance across projects, an orphaned old watcher's 'change' callback firing after a switch reads this.vectorStore/this.projectPath, which by then point at the new project — not the one the watcher was originally set up for.

Impact

A resource leak (an old file watcher never explicitly closed) with a spurious-trigger risk: an edit to the previous project's .tpy file (e.g. still open in the Tropy desktop app) made after switching ClioDeck to a different project could trigger an unwanted resync against the wrong (currently open) project's primary-source store.

Suggested fix

Call this.watcher?.unwatch() before reassigning this.watcher = new TropyWatcher() in init(), matching the same defensive pattern already used for the provider registry in the same method.

Evidence trail

  • src/main/services/tropy-service.tsinit(), watcher reassignment without prior teardown; startWatching(), the 'change' handler closure.
  • Wiki page corrected in the same pass: 1.6-Tropy-Integration-Guide.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions