Skip to content

fix(wake): fail closed on unsupported openWakeWord/tflite-runtime Python versions - #94197

Closed
vadelma-agent wants to merge 5 commits into
NousResearch:mainfrom
vadelma-agent:fix/wake-openwakeword-python-compat
Closed

vadelma-agent wants to merge 5 commits into
NousResearch:mainfrom
vadelma-agent:fix/wake-openwakeword-python-compat

Conversation

@vadelma-agent

@vadelma-agent vadelma-agent commented Aug 24, 2026 •

Copy link
Copy Markdown

What does this PR do?

Fixes a real dependency-resolution failure in the optional wake extra and
adds an honest, fail-closed compatibility boundary instead of a silent
broken state.

Bug (observed behavior). openwakeword==0.6.0 requires tflite-runtime
on Linux, and tflite-runtime==2.14.0 on PyPI only ships CPython 3.11
wheels for Linux x86_64/aarch64/armv7l — there is no cp312/cp313/cp314
wheel and no sdist. Before this PR:

$ uv sync --extra wake --locked --python 3.14 --dry-run --no-install-project
Using CPython 3.14.7
error: Distribution `tflite-runtime==2.14.0 @ registry+https://pypi.org/simple`
can't be installed because it doesn't have a source distribution or wheel
for the current platform

hint: You're using CPython 3.14 (`cp314`), but `tflite-runtime` (v2.14.0)
only has wheels with the following Python ABI tag: `cp311`

The same failure occurs on Python 3.12 and 3.13 on Linux. It is a wheel
availability failure, not a [all] regression — wake is intentionally
excluded from [all], so ordinary uv sync --extra all --extra dev
installs are unaffected — but any Linux user on Python 3.12+ who explicitly
opts into wake (or runs --all-extras) hits an opaque resolver error with
no remediation. It also silently blocks upgrading the project's own
requires-python ceiling toward 3.14 while wake's Linux dependency chain
stays capped at 3.11.

Fix. Add an explicit, platform-aware compatibility boundary:

  • The wake extra's openwakeword==0.6.0 pin now carries the marker
    python_version < '3.12' or sys_platform != 'linux', matching exactly
    where the locked tflite-runtime wheels (Linux) and the existing
    macOS bridge / Windows ONNX path actually work.
  • tools/lazy_deps.py gets a single shared openwakeword_supported() /
    openwakeword_unsupported_reason() predicate used by both the lazy
    installer and the wake runtime, so the resolver marker can't drift from
    first-use behavior.
  • On Linux CPython 3.12+, both the requirements probe
    (check_wake_word_requirements) and engine construction
    (_OpenWakeWordEngine.__init__) fail closed before any lazy
    pip/uv install is attempted, with an actionable message: use Python
    3.11, pick another configured wake provider, or wait for the upstream
    LiteRT-based release.
  • The existing macOS ARM64 ai-edge-litert bridge is preserved and
    regression-tested on simulated CPython 3.12 and 3.13 — this PR does not
    touch or replace that code path.
  • Windows remains supported through openWakeWord's ONNX path; the Linux-only
    TFLite boundary does not remove openwakeword from Windows Python 3.12+
    installs, and a windows_only regression test exercises that path on the
    real Windows CI runner.
  • [all] is unchanged; this only affects the wake extra's own marker and
    the corresponding uv.lock marker.

Why this approach. ai-edge-litert cannot become a silent drop-in
replacement for Linux tflite-runtime yet: the current PyPI release has no
armv7l wheel (tracked upstream in
google-ai-edge/LiteRT#6043),
so switching Linux to it now would quietly drop 32-bit Raspberry Pi support.
Upstream openWakeWord has a merged-but-unreleased PR
(dscripka/openWakeWord#289)
that migrates to ai-edge-litert; once that ships on PyPI with verified
model/runtime evidence, the Linux boundary can be revisited. Until then, the
safest and most honest fix is an explicit marker plus a fail-closed error —
not a broken resolver, and not a silent unverified backend swap.

Related Issue

No existing GitHub issue for this exact resolver failure. The related
upstream ai-edge-litert armv7l gap is tracked at
google-ai-edge/LiteRT#6043 (external repo).

Fixes #

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • pyproject.toml: [project.optional-dependencies].wake — add the
    python_version < '3.12' or sys_platform != 'linux' marker to
    openwakeword==0.6.0.
  • uv.lock: regenerate the corresponding marker on the locked
    openwakeword requirement (uv lock, no other package changes).
  • tools/lazy_deps.py: add openwakeword_supported() and
    openwakeword_unsupported_reason() as the single shared
    platform/Python-aware compatibility predicate.
  • tools/wake_word.py: fail closed with an actionable message in
    check_wake_word_requirements() and _OpenWakeWordEngine.__init__()
    before any lazy install is attempted on an unsupported Linux interpreter;
    preserve the existing macOS ARM64 ai-edge-litert bridge path unchanged.
  • tests/test_project_metadata.py: marker-evaluation tests for the
    Linux/Darwin boundary (project marker and locked marker), a lockfile
    assertion that the three published CPython 3.11 Linux wheel architectures
    (x86_64/aarch64/armv7l) remain present and no cp312+ wheel is claimed, and
    a CPython-3.11-only pytest.importorskip-gated import smoke for
    tflite_runtime.interpreter / openwakeword.model.
  • tests/tools/test_lazy_deps.py: predicate tests for the Linux boundary
    and the Darwin bridge, plus a fail-closed test asserting ensure() never
    calls pip/uv or probes installer policy when the predicate reports
    unsupported.
  • tests/tools/test_wake_word.py: requirements-probe and engine-construction
    fail-closed tests (simulated Python 3.12/3.13/3.14), a parameterized
    Darwin ARM64 bridge-preservation regression on simulated CPython 3.12 and
    3.13, a Windows-only positive ONNX-path regression on simulated CPython
    3.12 and 3.13 (executed by the real Windows CI lane), and a
    subprocess-isolated test that tools.lazy_deps /
    tools.wake_word remain importable when every optional wake dependency
    (ai_edge_litert, numpy, onnxruntime, openwakeword, pvporcupine,
    sherpa_onnx, sounddevice, tflite_runtime) is blocked at import time.
  • website/docs/user-guide/features/wake-word.md: document the Linux
    CPython 3.11 boundary, the preserved macOS bridge, and that [all] is
    unaffected.

How to Test

  1. Reproduce the pre-fix failure against tflite-runtime==2.14.0 directly
    (unaffected by this PR, since the PyPI package itself is unchanged):
    uv sync --extra wake --locked --python 3.14 --dry-run --no-install-project
    on an unpatched pyproject.toml/uv.lock fails with the resolver error
    quoted above.
  2. On this branch, the same command on Python 3.12/3.13/3.14 succeeds and
    selects no openwakeword/tflite-runtime packages; Python 3.11 still
    selects both, with all three Linux wheel architectures available.
  3. Run the focused suite:
    uv run --extra dev --extra wake --locked pytest -q tests/tools/test_lazy_deps.py tests/tools/test_wake_word.py tests/test_project_metadata.py
  4. Run the wake-touching gateway subset:
    uv run --extra dev --extra messaging --locked pytest -q tests/gateway/test_wake_delivery.py tests/gateway/test_kanban_notifier_apiserver_wake.py tests/gateway/test_kanban_notifier_wake_only_ordering.py

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — not run as a full suite in this PR; see focused/overlap evidence below
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Linux (Debian-based, aarch64), CPython 3.11.15/3.12.13/3.13.15 via uv; Darwin ARM64 coverage is exercised through simulated sys.platform/sys.version_info in the test suite (no physical macOS hardware used)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A (no config keys changed)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A (no architecture/workflow change)
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A (no tool schema changed)

Test evidence (exact head)

Base: f14059fad20e17acf2512785114791566e70bd06 (live origin/main at the
final refresh before publication). Head: f4a57481c090b13893fab72811f0d7945dd3ce50.

  • uv run --extra dev --extra wake --locked pytest -q tests/tools/test_lazy_deps.py tests/tools/test_wake_word.py tests/test_project_metadata.py → 106 passed, 6 skipped
  • uv run --extra dev --extra messaging --locked pytest -q tests/gateway/test_wake_delivery.py tests/gateway/test_kanban_notifier_apiserver_wake.py tests/gateway/test_kanban_notifier_wake_only_ordering.py → 10 passed
  • uv run --extra dev --locked ruff check tools/lazy_deps.py tools/wake_word.py tests/tools/test_lazy_deps.py tests/tools/test_wake_word.py tests/test_project_metadata.py → All checks passed!
  • uv lock --check → passed
  • git diff --check <base>..HEAD → passed (worktree clean)
  • uv sync --all-extras --locked --python 3.12 --dry-run --no-install-project → passed
  • uv sync --all-extras --locked --python 3.13 --dry-run --no-install-project → passed
  • uv sync --extra wake --locked --python 3.12 --dry-run --no-install-project → passed, resolves without openwakeword/tflite-runtime
  • The marker unit matrix also verifies that Windows CPython 3.12 and 3.13
    retain openwakeword; the Windows-only engine test verifies the ONNX path
    without probing the Linux TFLite bridge.

Known limitation, stated explicitly: this repository's
project.requires-python is currently >=3.11,<3.14, so a full repository
uv sync --all-extras --locked --python 3.14 is rejected before dependency
resolution even runs, independent of this PR. This PR does not raise that
ceiling (that's the separate Python 3.14 support PR, #92548) and does not
claim a repository-level Python 3.14 [all] pass. The Python 3.14 wake
marker behavior itself is verified with a minimal, uncapped reproducer
project outside the repository's own requires-python constraint, and with
durable marker-evaluation unit tests inside the repository
(tests/test_project_metadata.py) that assert the marker's boolean outcome
directly for python_version == 3.14 without depending on the repository's
own interpreter ceiling.

Rollback: revert this PR's commits; this restores the pre-change
Python-only-unconditional openwakeword marker and lazy-install behavior
(the resolver failure on Linux Python 3.12+ returns, matching current
main).

@vadelma-agent
vadelma-agent requested a review from a team August 24, 2026 20:23
@vadelma-agent
vadelma-agent force-pushed the fix/wake-openwakeword-python-compat branch from 8347b9f to 213ae58 Compare August 24, 2026 20:31
@alt-glitch alt-glitch added type/bug Something isn't working tool/tts Text-to-speech and transcription area/install-update Installer, updater, packaging, wheels, doctor sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades P3 Low — cosmetic, nice to have labels Aug 24, 2026
vadelma-agent and others added 5 commits August 24, 2026 13:42
Keep the released openWakeWord 0.6.0 path on the Python versions where its tflite-runtime wheels exist, and fail closed before lazy installation elsewhere. Preserve the existing macOS bridge and document the separate project-level Python ceiling.

Co-authored-by: Taneli Mielikäinen <taneli.mielikainen@iki.fi>
Co-authored-by: Taneli Mielikäinen <taneli.mielikainen@iki.fi>
Keep openWakeWord selectable on Darwin while failing closed for the Linux tflite-runtime path on Python 3.12+. Add resolver, bridge, lazy-install, and supported CPython 3.11 import coverage without changing [all].

Co-authored-by: Taneli Mielikäinen <taneli.mielikainen@iki.fi>
Parameterize the macOS ARM64 bridge regression across CPython 3.12 and 3.13, exercising the production unsupported-runtime gate in both cases.

Co-authored-by: Taneli Mielikäinen <taneli.mielikainen@iki.fi>
The Linux-only tflite-runtime compatibility boundary must not exclude openWakeWord from Windows CPython 3.12+ installs. Preserve the ONNX path and add positive Windows marker and engine coverage alongside the Linux negative and Darwin bridge tests.

Co-authored-by: Taneli Mielikäinen <taneli.mielikainen@iki.fi>
@vadelma-agent
vadelma-agent force-pushed the fix/wake-openwakeword-python-compat branch from 213ae58 to f4a5748 Compare August 24, 2026 20:43
@Enough1122

Copy link
Copy Markdown

AI code review — automated review for reference, author can ignore or act on any point.

Exemplary fail-closed design: the shared predicate (tools/lazy_deps.py:341) drives the packaging marker (pyproject.toml:209), the lazy installer hook (tools/lazy_deps.py:584-585), the engine constructor gate (tools/wake_word.py:545-550, before any pip attempt), and the requirements probe (tools/wake_word.py:934-935 skips installer-policy probes entirely). The lock-vs-project marker consistency tests and the "pip must not run" failure-injection tests are exactly the right shape. Two small things:

  1. Duplicated capture-mode mapping risks drift — tools/wake_word.py:980-991 reimplements resolve_capture_mode's string→mode mapping inline (client/remote/external → client, else local) for the unsupported case. If resolve_capture_mode later gains another mode or changes its cfg keys, this copy silently diverges and reports the wrong capture mode precisely in the degraded state users will be debugging. Extract a pure helper (e.g. capture_mode_from_cfg(cfg) -> str) used by both paths so only the probing differs.

  2. Honest reporting when unsupported — at tools/wake_word.py (~line 967, the tflite_ok = True default plus the not unsupported guard), the requirements result keeps tflite_ok=True even though the whole openWakeWord stack is unavailable and nothing was probed. If any consumer reads tflite_ok independently of the top-level available flag, it now reports a capability that was never checked; consider explicitly setting it to False (or omitting it) when unsupported so every field in the payload reflects reality.

Minor: the autouse fixture monkeypatching sys.version_info down to 3.11 (tests/tools/test_wake_word.py:243-254) is a pragmatic portability shim; the per-test overrides keep the boundary covered, just noting future tests in this module inherit simulated 3.11 unless they opt out.

@vadelma-agent

Copy link
Copy Markdown
Author

Closing as superseded: main replaced openwakeword + tflite-runtime with pyopen-wakeword==1.1.0 (py3-none wheels for Linux x86_64/aarch64, macOS and Windows), so the cp311-only tflite-runtime failure this PR guarded against no longer exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/install-update Installer, updater, packaging, wheels, doctor P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades tool/tts Text-to-speech and transcription type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants