Skip to content

[BUG] DuplicateIds crash when clicking NPM packages rapidly #1

@thisoceanfox

Description

@thisoceanfox

Bug Description

Application crashes with DuplicateIds exception when selecting NPM packages in the tree view.

Steps to Reproduce

  1. Open devops application
  2. Navigate to NPM tab
  3. Click on an NPM package (e.g., @masonator/get-mcp-keys)
  4. Quickly click on the same or another NPM package
  5. Application crashes with DuplicateIds error

Expected Behavior

Should display package details without crashing, even when rapidly switching between packages.

Actual Behavior

Application crashes with the following error:

DuplicateIds: Tried to insert a widget with ID 'npm-uninstall-1770075339377', but a widget already 
exists with that ID (Button(id='npm-uninstall-1770075339377', classes='-error -style-default', 
variant='error')); ensure all child widgets have a unique ID.

Stack Trace:

File: /opt/homebrew/Cellar/devops/0.4.0/libexec/lib/python3.12/site-packages/devops/widgets/detail_panel.py:568
In show_npm_package()

self.mount(
    Button(
        f"Uninstall {name}",
        id=f"npm-uninstall-{int(time.time() * 1000)}",
        variant="error",
    )
)

Root Cause

The show_npm_package() method in detail_panel.py (line 568) generates button IDs using int(time.time() * 1000). When the method is called multiple times within the same millisecond:

  1. The timestamp-based ID is identical
  2. _clear_buttons() may not complete removal before new buttons are mounted
  3. Duplicate IDs are created, violating Textual's unique ID requirement

Proposed Fix

Replace timestamp-based IDs with UUID for guaranteed uniqueness:

import uuid

def show_npm_package(self, pkg: dict, pkg_type: str, project_path: str = "") -> None:
    self._clear_buttons()
    # ... existing code ...
    
    unique_id = str(uuid.uuid4())[:8]  # Short unique identifier
    
    if is_outdated and is_global:
        self.mount(
            Button(
                f"Upgrade {name}",
                id=f"npm-upgrade-{unique_id}",
                variant="success",
            )
        )

    self.mount(
        Button(
            f"Uninstall {name}",
            id=f"npm-uninstall-{unique_id}",
            variant="error",
        )
    )

Environment

  • Version: 0.4.0
  • OS: macOS (Homebrew installation)
  • Python: 3.12
  • Installation: brew install jamesrisberg/devops/devops

Impact

Severity: High - Crashes application and prevents NPM package management

Additional Notes

This same pattern exists in other methods throughout detail_panel.py that use int(time.time() * 1000) for button IDs. Consider applying the UUID fix consistently across all button-generating methods to prevent similar crashes.

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