Skip to content

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

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

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.

This branch has not been deployed

No deployments
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