Describe the bug
The CLI and the media-use audio engine resolve Python differently:
packages/cli/src/tts/python.ts → findPython() checks HYPERFRAMES_PYTHON first. doctor uses it, and the CLI's install hints say to "point HYPERFRAMES_PYTHON at a venv python that has them".
skills/media-use/audio/scripts/lib/python.mjs → resolvePythonCommand() only probes python3 / python on PATH.
On macOS with Homebrew Python (PEP 668, externally managed), the documented setup is a venv plus HYPERFRAMES_PYTHON. With that setup doctor reports MusicGen as installed, but the audio engine probes the bare Homebrew python3 and disables BGM. So every workflow renders without music on a machine configured the way the docs describe. Its fallback python3 -m pip install … into the system interpreter also fails, as PEP 668 intends. Kokoro TTS is unaffected because it goes through the CLI, which does honour the variable.
Minimal reproduction
Self-contained. No project or assets needed:
uv venv ~/.venvs/hf --python 3.12
uv pip install --python ~/.venvs/hf/bin/python transformers torch soundfile numpy
export HYPERFRAMES_PYTHON=~/.venvs/hf/bin/python
mkdir repro && cd repro
echo '{"lines":[],"bgm":{"mode":"generate","prompt":"calm ambient pad"}}' > audio_request.json
node <skills>/media-use/audio/scripts/audio.mjs --request ./audio_request.json --hyperframes . --out ./audio_meta.json --only bgm
Steps to reproduce
- Set up the venv and export
HYPERFRAMES_PYTHON as above. python3 on PATH is Homebrew's, with no torch.
npx hyperframes doctor → ✓ BGM (MusicGen) MusicGen deps installed
- Run the audio engine command above.
Expected behavior
The engine uses HYPERFRAMES_PYTHON, as the CLI does, launches MusicGen, and writes assets/bgm/track.wav.
Actual behavior
anomalies (non-fatal):
- bgm: no Lyria key/recipe and local MusicGen deps unavailable (pip install transformers torch soundfile numpy)
No music track is produced. Workflows continue silently without BGM.
Environment
macOS 26.6 (Apple Silicon M5), Node 25.9.0, hyperframes 0.8.81, skills at main @ ff6e210, Python 3.12 venv (uv), Homebrew python3 3.14.
Proposed fix
resolvePythonCommand() checks HYPERFRAMES_PYTHON first, probing that it runs. It falls through to the existing PATH probe if it doesn't, the same way findPython() does. There are 3 new unit tests: the override wins, a non-running override falls through, and an empty override is never probed. python.test.mjs passes 10/10, and the bgm / tts suites pass 14/14. End to end, the repro above launches MusicGen and writes the track with no PATH changes.
diff --git a/skills/media-use/audio/scripts/lib/python.mjs b/skills/media-use/audio/scripts/lib/python.mjs
--- a/skills/media-use/audio/scripts/lib/python.mjs
+++ b/skills/media-use/audio/scripts/lib/python.mjs
@@ -26,12 +26,27 @@ function defaultProbe(cmd, args) {
* Pick the argv prefix that launches Python 3 on this platform.
* Returns e.g. `["python3"]`, `["python"]`, or `["py", "-3"]`.
*
- * Pure except for `probe` (which runs `<cmd> … --version`); both `platform`
- * and `probe` are injectable so every branch is unit-testable without spawning.
- * If nothing probes OK, falls back to the canonical name for the platform so
- * the eventual spawn fails loudly exactly as it did before — never worse.
+ * `HYPERFRAMES_PYTHON` wins when it runs, matching the CLI's `findPython()`
+ * (packages/cli/src/tts/python.ts). The CLI's install hints and `doctor` both
+ * tell users to point it at a venv holding the MusicGen/ElevenLabs deps, and
+ * `doctor` then reports them as installed — so ignoring it here left BGM
+ * generation probing a bare `python3` without those deps and silently skipping
+ * the track. An override that does not run falls through to the PATH probe,
+ * as the CLI does.
+ *
+ * Pure except for `probe` (which runs `<cmd> … --version`); `platform`,
+ * `probe` and `env` are injectable so every branch is unit-testable without
+ * spawning. If nothing probes OK, falls back to the canonical name for the
+ * platform so the eventual spawn fails loudly exactly as it did before — never
+ * worse.
*/
-export function resolvePythonCommand(platform = process.platform, probe = defaultProbe) {
+export function resolvePythonCommand(
+ platform = process.platform,
+ probe = defaultProbe,
+ env = process.env,
+) {
+ const override = env.HYPERFRAMES_PYTHON;
+ if (override && probe(override, ["--version"])) return [override];
const candidates =
platform === "win32" ? [["python3"], ["python"], ["py", "-3"]] : [["python3"], ["python"]];
for (const prefix of candidates) {
diff --git a/skills/media-use/audio/scripts/lib/python.test.mjs b/skills/media-use/audio/scripts/lib/python.test.mjs
--- a/skills/media-use/audio/scripts/lib/python.test.mjs
+++ b/skills/media-use/audio/scripts/lib/python.test.mjs
@@ -55,6 +55,29 @@ test("falls back to the canonical name (loud failure, unchanged) when nothing ru
);
});
+test("HYPERFRAMES_PYTHON wins over PATH when it runs (matches the CLI's findPython)", () => {
+ const venv = "/home/u/.venvs/hf/bin/python";
+ const env = { HYPERFRAMES_PYTHON: venv };
+ assert.deepEqual(resolvePythonCommand("darwin", probeFor(venv, "python3"), env), [venv]);
+ assert.deepEqual(resolvePythonCommand("win32", probeFor(venv, "python3", "py"), env), [venv]);
+});
+
+test("a HYPERFRAMES_PYTHON that does not run falls through to the PATH probe", () => {
+ const env = { HYPERFRAMES_PYTHON: "/nonexistent/python" };
+ assert.deepEqual(resolvePythonCommand("linux", probeFor("python3"), env), ["python3"]);
+ assert.deepEqual(resolvePythonCommand("win32", probeFor("py"), env), ["py", "-3"]);
+});
+
+test("an empty HYPERFRAMES_PYTHON is ignored and never probed", () => {
+ const seen = [];
+ const probe = (cmd) => {
+ seen.push(cmd);
+ return cmd === "python3";
+ };
+ assert.deepEqual(resolvePythonCommand("linux", probe, { HYPERFRAMES_PYTHON: "" }), ["python3"]);
+ assert.deepEqual(seen, ["python3"]);
+});
+
test("pythonInvocation prepends the resolved prefix ahead of caller args", () => {
assert.deepEqual(pythonInvocation(["-c", "import x"], ["python"]), {
cmd: "python",
Happy to open this as a PR if that's easier to review.
Describe the bug
The CLI and the media-use audio engine resolve Python differently:
packages/cli/src/tts/python.ts→findPython()checksHYPERFRAMES_PYTHONfirst.doctoruses it, and the CLI's install hints say to "point HYPERFRAMES_PYTHON at a venv python that has them".skills/media-use/audio/scripts/lib/python.mjs→resolvePythonCommand()only probespython3/pythononPATH.On macOS with Homebrew Python (PEP 668, externally managed), the documented setup is a venv plus
HYPERFRAMES_PYTHON. With that setupdoctorreports MusicGen as installed, but the audio engine probes the bare Homebrewpython3and disables BGM. So every workflow renders without music on a machine configured the way the docs describe. Its fallbackpython3 -m pip install …into the system interpreter also fails, as PEP 668 intends. Kokoro TTS is unaffected because it goes through the CLI, which does honour the variable.Minimal reproduction
Self-contained. No project or assets needed:
Steps to reproduce
HYPERFRAMES_PYTHONas above.python3onPATHis Homebrew's, with no torch.npx hyperframes doctor→✓ BGM (MusicGen) MusicGen deps installedExpected behavior
The engine uses
HYPERFRAMES_PYTHON, as the CLI does, launches MusicGen, and writesassets/bgm/track.wav.Actual behavior
No music track is produced. Workflows continue silently without BGM.
Environment
macOS 26.6 (Apple Silicon M5), Node 25.9.0,
hyperframes0.8.81, skills atmain@ff6e210, Python 3.12 venv (uv), Homebrewpython33.14.Proposed fix
resolvePythonCommand()checksHYPERFRAMES_PYTHONfirst, probing that it runs. It falls through to the existingPATHprobe if it doesn't, the same wayfindPython()does. There are 3 new unit tests: the override wins, a non-running override falls through, and an empty override is never probed.python.test.mjspasses 10/10, and thebgm/ttssuites pass 14/14. End to end, the repro above launches MusicGen and writes the track with noPATHchanges.Happy to open this as a PR if that's easier to review.