Skip to content

fix(wake): refuse to arm when native deps die on import (#109982) - #110057

Open
Halldrix wants to merge 2 commits into
NousResearch:mainfrom
Halldrix:fix/wake-word-native-crash-guard-109982
Open

Halldrix wants to merge 2 commits into
NousResearch:mainfrom
Halldrix:fix/wake-word-native-crash-guard-109982

Conversation

@Halldrix

Copy link
Copy Markdown
Contributor

What does this PR do?

On some Windows hosts sentencepiece==0.2.2 dies with an access violation
on import — uncatchable in-process, so arming the sherpa wake-word engine
kills the whole backend and the desktop shell respawns it into a crash loop
(124 crashes in a day on the reporter's machine). try/except Exception
and the except ImportError inside sherpa-onnx cannot intercept a fatal
signal, and check_wake_word_requirements() reported the setup as healthy.

This PR probes native imports in a throwaway interpreter and refuses to arm
with a remediation hint instead of dying:

  • tools/wake_word.py — new _native_deps_loadable() subprocess smoke
    probe plus _refuse_unloadable_native_deps(); wired into
    check_wake_word_requirements() (new native_ok field + ladder rung, so
    wake.start on every surface refuses before arming).
  • tools/wake_word_engines.py_Engine.__init__ runs the same refusal
    post-ensure(), so direct start_listening callers are covered too and
    fresh installs still reach the lazy installer.
  • tools/lazy_deps.py, pyproject.toml, uv.lock — declare the missing
    pypinyin==0.55.0 the sherpa engine needs to arm at all (Sherpa wake word fails to arm: No module named 'pypinyin' #74719).

Related Issue

Fixes #109982

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

Changes Made

  • tools/wake_word.py — subprocess smoke probe, post-ensure refusal hook
    point, requirements wiring (native_ok + hint).
  • tools/wake_word_engines.py — refusal runs between ensure() and
    _build() in _Engine.__init__.
  • tools/lazy_deps.py / pyproject.toml (wake extra +
    [tool.uv.exclude-newer-package]) / uv.lock — declare
    pypinyin==0.55.0, mirrored in both pin surfaces.
  • tests/tools/test_wake_word.py — 6 regression tests: smoke
    refusal/success at the requirements gate, out-of-process proof, crash
    exit-code mapping, post-ensure refusal order, dispatcher fresh-install
    ordering.

How to Test

  1. scripts/run_tests.sh tests/tools/test_wake_word.py tests/tools/test_lazy_deps.py tests/test_project_metadata.py
  2. On an affected host: set sherpa wake provider with wake_word.enabled
    true — wake.start now returns started: false with a hint instead of
    crash-looping; wake_word.enabled false stays the manual workaround.
  3. Fresh install: /wake on with missing deps still reaches the lazy
    installer (pinned by test_dispatcher_does_not_preempt_lazy_install).

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've added tests for my changes
  • I've tested on my platform: Debian GNU/Linux 13 (trixie), x86_64 — Hermes Agent v0.21.2 (v2026.9.11), upstream 422bc9b

Documentation & Housekeeping

  • Relevant behavior is documented in code and test docstrings
  • Config example changes are not applicable
  • Contributor workflow documentation changes are not applicable
  • Cross-platform path and SQLite behavior were considered
  • Tool description/schema changes are not applicable

Exclusions

…#109982)

sentencepiece 0.2.2 segfaults on import on some Windows hosts, bypassing
every try/except and killing the backend in a respawn loop. Probe native
imports in a throwaway interpreter from check_wake_word_requirements and
post-ensure in _Engine.__init__ so every arm path refuses with a hint
instead of dying. Declare the missing pypinyin dep alongside.
@Halldrix
Halldrix requested a review from a team September 13, 2026 16:12
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists tool/tts Text-to-speech and transcription comp/tools Tool registry, model_tools, toolsets platform/windows Native Windows-specific behavior or breakage area/install-update Installer, updater, packaging, wheels, doctor sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Sep 13, 2026
@kvnloo

kvnloo commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Comment assumes head a39c98027fb0e810ab695ecda366e1713c59ba95; void if moved.

KEEP — refuse to arm when native deps die on import (closes #109982 crash-loop axis)

  • sentencepiece==0.2.2 access-violation on import on some Windows hosts is not catchable in-process — except ImportError inside sherpa-onnx and try/except Exception around arm both miss a fatal signal, so the desktop shell respawns the backend into a crash loop while check_wake_word_requirements() still greened the setup. That matches [Bug]: wake word kills the whole gateway on Windows — sentencepiece 0.2.2 crashes with an access violation on import #109982 (and the on-issue VC++/CPU comparison: host-dependent unloadable wheel, not venv corruption).
  • Subprocess _native_deps_loadable + _refuse_unloadable_native_deps, wired into requirements (native_ok + ladder rung) and post-ensure in _Engine.__init__, plus declaring the missing pypinyin==0.55.0 sherpa needs to arm, is the clean vehicle for the refuse-not-die half of Wake/Sherpa hygiene. Explicit non-goals in the PR body (no dated sentencepiece pin; openwakeword/tflite out of scope; crash-loop breaker belongs elsewhere) look right.

CHECK

One-click close / ordering

Landing-evidence

  • When merged, please reply with the merge commit SHA on main (not “completed”).

@kvnloo

kvnloo commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Comment assumes head a39c98027fb0e810ab695ecda366e1713c59ba95; void if moved.

KEEP — refuse to arm when native deps die on import (closes #109982 crash-loop axis)

  • sentencepiece==0.2.2 access-violation on import on some Windows hosts is not catchable in-process — except ImportError inside sherpa-onnx and try/except Exception around arm both miss a fatal signal, so the desktop shell respawns the backend into a crash loop while check_wake_word_requirements() still greened the setup. That matches [Bug]: wake word kills the whole gateway on Windows — sentencepiece 0.2.2 crashes with an access violation on import #109982 (and the on-issue VC++/CPU comparison: host-dependent unloadable wheel, not venv corruption).
  • Subprocess _native_deps_loadable + _refuse_unloadable_native_deps, wired into requirements (native_ok + ladder rung) and post-ensure in _Engine.__init__, plus declaring the missing pypinyin==0.55.0 sherpa needs to arm, is the clean vehicle for the refuse-not-die half of Wake/Sherpa hygiene. Explicit non-goals in the PR body (no dated sentencepiece pin; openwakeword/tflite out of scope; crash-loop breaker belongs elsewhere) look right.

CHECK

One-click close / ordering

Landing-evidence

  • When merged, please reply with the merge commit SHA on main (not “completed”).

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 comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists platform/windows Native Windows-specific behavior or breakage sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows 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.

[Bug]: wake word kills the whole gateway on Windows — sentencepiece 0.2.2 crashes with an access violation on import

3 participants