# Root Cause Analysis: nwave-ai v2.2.0 shows v1.5.0 after install

**Date**: 2026-03-08
**Reporter**: svitale
**Analyst**: Rex (nw-troubleshooter)
**Status**: Analysis complete -- 3 root causes identified, 1 confirmed most likely

---

## Problem Statement

User installed `nwave-ai` v2.2.0 via pipx. `pipx list` confirms v2.2.0 is installed. However, running `nwave-ai install` displays "nWave v1.5.0 installed and healthy!". Uninstalling and reinstalling via pipx does not resolve the issue.

**Scope**: Version detection in the installer subprocess chain.
**Impact**: User sees wrong version; may believe they have stale software.

---

## Evidence Trail

| Evidence | Source | Value |
|----------|--------|-------|
| pipx shows v2.2.0 | User report | Confirmed |
| Installer shows v1.5.0 | User report | Confirmed |
| nwave-ai v2.2.0 exists on PyPI | PyPI JSON API | `nwave_ai-2.2.0-py3-none-any.whl` (937 KB) |
| nwave-ai v1.5.0 existed on PyPI | PyPI JSON API | `nwave_ai-1.5.0-py3-none-any.whl`, published 2026-02-28 |
| nwave (unrelated) on PyPI is v0.1.2 | PyPI JSON API | Audio package by ionite34 -- irrelevant |
| Dev repo pyproject.toml has `name = "nwave"` | `pyproject.toml:9` | NOT "nwave-ai" |
| `_get_version()` fallback tries "nwave" second | `install_nwave.py:81` | `for pkg_name in ("nwave-ai", "nwave"):` |

---

## Toyota 5 Whys Analysis

### Branch A: Stale "nwave" package in resolution path (MOST LIKELY)

**WHY 1A**: Installer displays v1.5.0 instead of v2.2.0.
[Evidence: User report -- `show_installation_summary()` at `install_nwave.py:587` prints `__version__` which is set at line 107 via `_get_version()`]

**WHY 2A**: `_get_version()` returns "1.5.0" instead of "2.2.0".
[Evidence: `install_nwave.py:76-104` -- function tries `importlib.metadata.version("nwave-ai")` first, then `importlib.metadata.version("nwave")`, then pyproject.toml fallback]

**WHY 3A**: `importlib.metadata.version("nwave-ai")` raises `PackageNotFoundError`, causing fallback to `importlib.metadata.version("nwave")` which finds a stale dev package at v1.5.0.
[Evidence: The dev repo's `pyproject.toml:9` declares `name = "nwave"` (not "nwave-ai"). If user previously installed from the dev repo (e.g., `pip install -e .` in nwave-dev at the v1.5.0 tag), that package registers as "nwave" v1.5.0 in system site-packages.]

**WHY 4A**: The subprocess started by `cli.py` can see packages outside the pipx venv.
[Evidence: This occurs when:
  (a) The user has `PYTHONPATH` set to include a path containing `nwave-1.5.0.dist-info`, OR
  (b) The user invoked `nwave-ai install` directly via their system Python (e.g., `python -m nwave_ai.cli install`) rather than through the pipx shim, OR
  (c) The pipx venv was created with `--system-site-packages`]

**WHY 5A**: The `_get_version()` function has a dangerous fallback to package name "nwave" that can match an unrelated dev installation.
[Evidence: `install_nwave.py:81` -- `for pkg_name in ("nwave-ai", "nwave"):`. The dev repo package name is "nwave", and any dev install at any version will be picked up as a fallback. This is a design flaw: the public package ("nwave-ai") and dev package ("nwave") share a fallback chain despite being distinct distributions.]

**ROOT CAUSE A**: The `_get_version()` function in `install_nwave.py` falls back to `importlib.metadata.version("nwave")` when "nwave-ai" is not found. In environments where the user has a dev installation of "nwave" (from the nwave-dev repo) visible to the subprocess Python, this returns the dev version instead of "0.0.0".

**SOLUTION A**:
- *Immediate mitigation*: Remove the "nwave" fallback from `install_nwave.py:_get_version()`. Only look for "nwave-ai".
- *Permanent fix*: Same -- the "nwave" fallback serves no valid purpose in the public installer. The dev repo package (name="nwave") should never be resolved by the public CLI tool.

---

### Branch B: Subprocess Python isolation failure

**WHY 1B**: Same as WHY 1A.

**WHY 2B**: The subprocess launched by `cli.py:_run_script()` uses a Python interpreter that can see packages outside the pipx venv.
[Evidence: `cli.py:38` -- `cmd = [sys.executable, str(script_path)]`. If `sys.executable` resolves correctly to the pipx venv Python, isolation should hold. But if the user runs the CLI outside pipx, `sys.executable` is the system Python.]

**WHY 3B**: No validation that the subprocess Python is isolated or that the resolved version matches expectations.
[Evidence: There is no assertion or logging that shows which Python is executing or what `importlib.metadata.version()` returned for each candidate name. The version is silently resolved at module import time (`install_nwave.py:107`)]

**WHY 4B**: The installer was designed for a pipx-only deployment model but has no runtime guard against being invoked from a non-isolated environment.
[Evidence: `cli.py:_run_script()` passes `sys.executable` without verifying it is a venv Python. `install_nwave.py` adds `_project_root` to `sys.path` (line 20-21) which in a wheel install adds `site-packages/` -- benign but shows no thought given to path isolation.]

**WHY 5B**: Missing defensive design for cross-environment execution.
[Evidence: No environment validation, no version-source logging, no fallback warning.]

**ROOT CAUSE B**: The installer does not validate or log the execution environment, making it impossible to diagnose version resolution issues and vulnerable to environment contamination.

**SOLUTION B**:
- *Immediate mitigation*: Add debug logging showing which `_get_version()` branch resolved and what value was returned.
- *Permanent fix*: Add `--verbose` flag that logs the Python executable path, sys.path, and which package name resolved the version.

---

### Branch C: `nwave_ai/__init__.py` fallback returns stale value

**WHY 1C**: Same as WHY 1A.

**WHY 2C**: `cli.py:_get_version()` (lines 8-20) has a distinct fallback chain: try metadata, then fall back to `nwave_ai.__version__`.
[Evidence: `cli.py:18` -- `from nwave_ai import __version__; return __version__`. `nwave_ai/__init__.py:3` has `__version__ = "0.0.0-dev"` which is stamped to the release version only during the PyPI build.]

**WHY 3C**: This branch is less likely to produce "1.5.0" specifically, but it demonstrates that `cli.py` and `install_nwave.py` have DIFFERENT fallback chains, potentially producing different versions.
[Evidence: `cli.py` falls back to `nwave_ai.__init__.__version__` (hardcoded). `install_nwave.py` falls back to `importlib.metadata.version("nwave")` then to pyproject.toml on disk. These are completely different resolution strategies.]

**WHY 4C**: The two `_get_version()` functions were written independently without a shared version resolution module.
[Evidence: Both files define their own `_get_version()` with different logic. No shared utility.]

**WHY 5C**: No single source of truth for runtime version resolution.

**ROOT CAUSE C**: Two independent `_get_version()` implementations with divergent fallback chains create inconsistency risk. The `install_nwave.py` version is what the user sees (it produces the "installed and healthy" message), while the `cli.py` version is used for `nwave-ai version` output -- they can disagree.

**SOLUTION C**:
- *Permanent fix*: Extract a single `resolve_version()` function into a shared module (e.g., `nwave_ai/_version.py`) used by both `cli.py` and `install_nwave.py`.

---

## Cross-Validation

| Root Cause | Explains v1.5.0? | Explains pipx-list-shows-2.2.0? | Explains reinstall-doesnt-fix? |
|------------|------------------|----------------------------------|-------------------------------|
| A (stale "nwave" fallback) | YES -- dev nwave v1.5.0 is found | YES -- pipx metadata shows nwave-ai 2.2.0 correctly | YES -- reinstall does not remove the dev nwave package |
| B (subprocess isolation) | YES (enables A) | YES | YES |
| C (divergent _get_version) | PARTIALLY -- shows inconsistency mechanism | YES | YES |

Root Causes A and B are complementary: B (lack of isolation) enables A (wrong package resolved). Together they fully explain the symptom. Root Cause C is an independent code quality issue that increases the risk of similar bugs.

---

## Verification Questions for the User (svitale)

To confirm Root Cause A, ask:
1. Do you have the nwave-dev repo cloned? Have you ever run `pip install -e .` or `pipenv install` from it?
2. Run `pip show nwave` in your system Python -- does it show v1.5.0?
3. Run `nwave-ai version` -- does it show 2.2.0 or 1.5.0?
4. Run: `python3 -c "from importlib.metadata import version; print(version('nwave-ai')); print(version('nwave'))"` -- what does each print?

---

## Solution Summary

| Priority | Type | Action | Root Cause |
|----------|------|--------|------------|
| P0 | Immediate mitigation | Remove "nwave" from `_get_version()` fallback in `install_nwave.py:81` | A |
| P0 | Immediate mitigation | Remove "nwave" from `_get_version()` fallback in `cli.py:12` | A |
| P1 | Permanent fix | Unify `_get_version()` into single shared module | C |
| P2 | Early detection | Add version-source logging to installer (`_get_version()` should log which branch resolved) | B |
| P2 | Prevention | Add `--verbose` / `--debug` flag to surface Python path, sys.path, and metadata resolution | B |
| P3 | Prevention | Consider adding a runtime assertion: if resolved version != pipx-reported version, warn the user | A+B |

---

## `nWave/VERSION` File Usage Audit

**Finding**: `nWave/VERSION` is NOT read at runtime by any code path.

| Usage | File | Purpose |
|-------|------|---------|
| Written by commitizen | `pyproject.toml:277` (`version_files = ["nWave/VERSION", ...]`) | Kept in sync during `cz bump` |
| Included in wheel via force-include | `patch_pyproject.py:118` | Packaged as static artifact |
| Referenced in docs/tests | Various | Documentation and test assertions only |

No runtime code in `src/des/`, `scripts/install/`, `nwave_ai/`, or `nWave/agents/` reads this file to determine the running version. It is purely a static metadata artifact synced by commitizen during releases. The current value (`1.7.0`) is stale relative to the actual version (`2.1.0` in pyproject.toml) -- this itself is a minor inconsistency, but has zero runtime impact.
