Repository navigation
fix: take helper programs only from install locations, never the project folder - #7524
Conversation
…ever the project folder FFmpeg, FFprobe, whisper-cli, parakeet-mlx, Python and system Chrome are now found through one shared rule: an absolute PATH entry outside the working directory, or a fixed install dir. A relative PATH entry and any folder inside the working directory are skipped, on every platform. Windows walks PATH itself instead of asking `where`, which lists the working directory first. FFmpeg discovery no longer falls back to `.hyperframes/bin` or to an installer package in the project's node_modules; an installer package installed next to HyperFrames still works. The env overrides are unchanged. The CLI skill reference stops suggesting the project-local folder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Windows now walks PATH instead of running `where`, so tests that faked `where` output found the runner's real python and cmake. Both tests now set PATH to a temp folder holding only the programs they expect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45e60b2 to
708ab6f
Compare
There was a problem hiding this comment.
Verdict: REQUEST CHANGES · 1 blocker · 1 important · 0 nits · head 708ab6f
The lookup rule is correct. On a miss, though, several callers still run the bare name, so PATH (or Windows' cwd-first search) can still pick the project's copy.
- blocker:
engine/src/utils/ffmpegBinaries.ts:10,14(findFfBinary(...) ?? "ffmpeg") and the studio-server copies (peakMap.ts:69,waveform.ts:67,mediaMetadata.ts:298) spawn bareffmpeg/ffprobewhen the trusted lookup returnsundefined. Repro on Linux, with system FFmpeg hidden, cwd = project,PATH=<proj>/node_modules/.bin:…(thenpm run/npxshape), plantednode_modules/.bin/ffmpeg:findFfBinary("ffmpeg")→undefined, thenspawnSync(found ?? "ffmpeg")→ the planted script runs. On Windows a project-rootffmpeg.exeruns the same way, because libuv'ssearch_pathtries the cwd first. Fix: fail with the install hint and never spawn the bare name. Lambda: the handler's cwd is/var/taskandprimeRuntimeEnvprepends/var/task/binto PATH. That folder is inside the cwd, so this PR already rejects it, and Lambda renders today only through this fallback. Removing the fallback therefore needsprimeRuntimeEnvto setHYPERFRAMES_FFMPEG_PATH/HYPERFRAMES_FFPROBE_PATHtoo. - important: some sibling call sites are outside the diff. On Windows,
utils/clipboard.tsspawns bareclip.exefromhyperframes add, so aclip.exein the project runs.media-usecodex-provider.mjs:52andlocal-run.mjs(shell: true) resolve by name the same way. Route them throughfindTrustedExecutableand spawn the result, or note them as out of scope in the PR body. ↓ inline for the one in this diff
Scope
Read all 14 files. Checked every findFfBinary consumer and every which/where and bare-name spawn under packages/. CI is green on the head; BLOCKED is the review gate only. No prior reviews.
— Genesis
When no trusted FFmpeg or FFprobe is found, the engine, the producer's probes and Studio's waveform, peak and audio-track helpers now report "not found" with the HYPERFRAMES_FFMPEG_PATH / HYPERFRAMES_FFPROBE_PATH hint instead of handing the bare name to the OS lookup. Callers that degraded on a missing binary still do: runFfmpeg returns a spawn_error result, the GPU and version probes report none, and Studio's audio helpers fail soft. The Lambda handler names its bundled ffmpeg and ffprobe by path, since they live under the task root, which is the working directory. brew, git and cmake for the whisper.cpp install, and the clipboard tool for `hyperframes add`, run by the absolute path the trusted lookup approved. The media-use engine gets a PATH holding only trusted folders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Throwing from getFfmpegBinary turned every caller's missing-binary path into a synchronous throw: the streaming encoder's "failure result, not a throw" contract broke, and unit lanes that run without FFmpeg crashed at module load. With no trusted copy the getters now return an absolute path inside HyperFrames' own install that is never created, named for the override to set. Spawning it fails with ENOENT exactly as before, and the OS still never looks the bare name up. The call-site guards and fixture changes that the throw needed are reverted. The whisper install tests match git and cmake by name and skip the brew case on Windows, where the lookup walks the real PATH. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… macOS on Windows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 0 nits — 3 resolved, 0 open, 0 new · 708ab6f → 73ecb9c
No engine or studio fallback runs bare-name FFmpeg/FFprobe any more. Lambda names its bundled binaries by path, and brew, git, cmake, the clipboard tools and the media-use child are all resolved through trusted paths. The new engine tests fail with the old fallback restored.
— Genesis
The CLI, engine and Studio now find FFmpeg, FFprobe, whisper-cli, parakeet-mlx, Python, system Chrome, brew, git, cmake and the clipboard tool through one shared rule: a program comes from an absolute PATH entry outside the working directory, or from a fixed install dir. It never comes from the project folder or a relative PATH entry, and when no such copy exists HyperFrames says so instead of handing the bare name to the OS lookup. An explicit
HYPERFRAMES_*override still points anywhere the person chooses.What
packages/parsers/src/trustedExecutable.ts, exported from@hyperframes/parsers/ff-binaries:isTrustedDirectory,isTrustedExecutablePath,trustedPathDirs,findTrustedExecutable.findFfBinaryuses it for thewhichresult, the PATH scan, the common install dirs and the installer package. The.hyperframes/binfallback is gone. An@ffmpeg-installer/@ffprobe-installerpackage is now looked up next to HyperFrames' own install (global or npx), not from the working directory'spackage.json.requireFfBinary(name):findFfBinary, or anENOENT-coded "not found" error that namesHYPERFRAMES_FFMPEG_PATH/HYPERFRAMES_FFPROBE_PATH. It never returns the bare name.getFfmpegBinary/getFfprobeBinary(also a public@hyperframes/engineexport) and Studio's waveform, peak-map and audio-track helpers used to spawn plainffmpeg/ffprobewhen the lookup came back empty. Studio now reports the binary as missing. The engine getters return an absolute path inside HyperFrames' own install that is never created, in a folder named for the override (…/ffmpeg-not-found-set-HYPERFRAMES_FFMPEG_PATH/ffmpeg), so a spawn fails withENOENTexactly as a missing FFmpeg always has.<task root>/bin, and the task root is Lambda's working directory, so the rule above would skip them.primeRuntimeEnvnow setsHYPERFRAMES_FFMPEG_PATHandHYPERFRAMES_FFPROBE_PATHto the bundled files when they exist and no override is set. Cloud Run and therender --dockerimage install FFmpeg in/usr/local/binand run from/app/...and/project, so they need no change.findWhisper(and its brew/git/cmake checks),findSystemBrowser,findParakeetandfindPythoncallfindTrustedExecutableinstead of parsingwhere/whichoutput. The whisper.cpp install now runs brew, git and cmake by the path that lookup approved, not by name.hyperframes addresolvespbcopy/clip/wl-copy/xclip/xselthroughfindTrustedExecutableand runs that path.hyperframes media-usestarts as a child process and which runs ffmpeg, codex and local models by name, gets a PATH that holds only the folderstrustedPathDirsaccepts.Why
A project folder is often something the person cloned or unpacked. Follows up #7511, which skipped relative PATH entries in the FFmpeg PATH scan and the working directory in
findPythonon Windows. Three gaps were left. The FFmpeg resolver still fell back to.hyperframes/binand to an installer package resolved from the working directory. The other helper lookups still took the first line ofwhere, which lists the working directory before PATH on Windows. And an absolute PATH entry inside the project, such as the working directory itself or itsnode_modules/.binundernpm run/npx, was still accepted. Review then showed that a lookup miss still ended in a bare-name spawn, so the OS search could reach those same folders; this PR closes that too.Related work
Refs #7511. Open PRs #7253 (browser download failure message) and #6038 (Parakeet decode window) touch
browser/manager.tsandwhisper/parakeet.tsin different functions, so they should rebase cleanly either way.How
Claude Opus 5.5
isTrustedDirectory): the directory must be absolute, and it must not be the working directory or inside it. Both sides go throughrealpathSync.native, and paths are compared case-insensitively on Windows. One carve-out: when the working directory is the home folder or one of its parents, only that folder itself is excluded. Without it, running the CLI from~would reject~/.local/bin,~/.volta/binand similar per-user install dirs..exefirst and thenPATHEXT, the same approachffBinariesalready used. That avoids the working-directory-first order ofwhereand its console code-page decoding. Elsewherewhichanswers. If its first hit is untrusted (for example.on PATH), a PATH walk finds a later trusted copy. Ifwhichfinds nothing, the result is still "not found", same as before.import.meta.url, and the result must also pass the rule, so a CLI run from inside a project's ownnode_modulesnever picks the project's copy.HAS_FFMPEGprobes, mocked-spawn tests). The never-created absolute path keeps the failure asynchronous:runFfmpegreturns its usualspawn_error,ffprobe.tsmaps theENOENTto "ffprobe not found", the GPU and-versionprobes report none, andisFfmpegInfrastructureFailureand the audio-mixer and HEIC patterns still readENOENT. No lookup by name happens on any of those paths. Studio'sprobeAudioTrackreturnsnull(no audio track) and the waveform and peak decoders reject with therequireFfBinarymessage, which their routes already handle.useBundledBinaries(taskRoot, env)is the extracted, testable part ofprimeRuntimeEnv. It keeps prepending<task root>/binto PATH as before and only adds the two overrides.mediaUseEngineEnv, keyed case-insensitively because Windows spells the variablePath. The engine's.mjsfiles ship as standalone skill scripts and cannot import@hyperframes/parsers, so scoping their PATH from the CLI side covers ffmpeg/ffprobe (ff-binaries.mjs),codexand thelocal-runshell commands at once.NoDefaultCurrentDirectoryInExePath=1in its own environment on startup (utils/windowsExeSearch.ts, imported bycli.tsand the CLI's worker entry points). libuv's Windowssearch_pathconsults it viaNeedCurrentDirectoryForExePathWsince libuv 1.48.0 (libuv#4238), and Node 22.0.0, the CLI's minimum, ships 1.48.0. That covers the cwd-first part of any remaining by-name spawn from the CLI process and its children; it does not cover an absolute PATH entry inside the project, which is why the spawns above take an absolute path or a filtered PATH.doctor'sunzipcheck only tests whether a command exists.core'sanalyzeMediaGradekeeps its"ffmpeg"default, but its only callers (media-treatment-analysisand the share host) pass resolved paths. Standalone skill scripts underskills/that an agent runs directly, and repo dev scripts (regression harness, catalog preview generators), still callffmpegby name; they run in the agent's or developer's own shell, with that shell's lookup.Test plan
parsers/src/trustedExecutable.test.ts: covers relative entries, the working directory and folders inside it, and the home-folder carve-out.parsers/src/ffBinaries.test.ts: a project with.hyperframes/bin/{ffmpeg,ffprobe}and a seeded installer package (flat and pnpm layouts) resolves to nothing. A projectffmpegthatwhichreports, with a second copy in an install dir, resolves to the install-dir copy. A project-only copy with the project on PATH (plus.andbin) resolves to nothing. A Windowsffmpeg.exein the working directory loses to the PATH copy. The Apple Silicon x86_64 skip is now exercised on PATH binaries. New:requireFfBinarythrows anENOENTnaming the override instead of returning a bare name.engine/src/utils/ffmpegBinaries.test.ts(new "with no trusted FFmpeg installed" block): with a projectnode_modules/.binleading PATH and holdingffmpeg/ffprobescripts that write a marker file,getFfmpegBinaryreturns the never-created path,runFfmpegreturns aspawn_errorwithENOENT,detectGpuEncoderresolvesnull,runFfproberejects with "ffprobe not found", and no marker is written.studio-server/src/helpers/noTrustedFfmpeg.test.ts(new): the same setup;decodeAudioPeaksanddecodePeakMapreject with "ffmpeg not found" andprobeAudioTrackreturnsnull, with no marker written.aws-lambda/src/handler.test.ts(useBundledBinaries): a task root withbin/ffmpegandbin/ffprobegets both overrides plus the PATH prefix; an override already set is kept; a bundle without one binary leaves that override unset; nobinfolder changes nothing.cli/src/whisper/manager.install.test.ts: brew runs as/opt/homebrew/bin/brew(the approved path) and never asbrew(skipped on Windows, where the lookup walks the real PATH); the clone and build tests match git and cmake by name, with or without.exe.engine/src/services/videoFrameExtractor.hdr.test.ts: the "macOS ffmpeg without zscale" case re-imports the engine withprocess.platformfaked todarwin; on the Windows runner that fresh lookup cannot walk the Windows PATH, so the test now pins the FFmpeg it already found through the two overrides for that block.studio-server/src/helpers/loudness.windowsHide.test.ts: now stubs the ff-binaries lookup like its sibling window tests, so a lane without FFmpeg still reaches the stubbed ffprobe.cli/src/utils/clipboard.test.ts/clipboard.windowsHide.test.ts: the copy runs the trusted path (/usr/bin/pbcopy,C:\Windows\System32\clip.exe), and nothing runs when no trusted tool exists.cli/src/commands/media-use.test.ts: with a projectnode_modules/.bin/codexthat writes a marker,.and/usr/bin:/binon PATH, the engine env's PATH is/usr/bin:/binand runningcodexthrough/bin/shwith it writes no marker.cli/src/whisper/trustedLookup.test.ts,cli/src/browser/manager.test.ts,cli/src/tts/python.test.ts: the Windows working-directory cases for whisper-cli, parakeet-mlx, Chrome and Python, as before.?? "ffmpeg"/?? "ffprobe"fallback fails all 3 engine tests (the stand-in runs, or the probe resolves with its empty output); restoring the Studio fallbacks fails both Studio tests (the stand-in ffmpeg runs); dropping the Lambda override loop fails the bundled-binaries test; running"brew"by name, the clipboard tool by name, or the media-use engine with the unfiltered PATH fails each matching test (the stand-incodexwrites its marker);requireFfBinaryreturning the name fails its test. Earlier: makingisTrustedDirectoryaccept every absolute folder fails 8 tests; restoring the.hyperframes/binand working-directory installer fallbacks fails 3.ffBinaries+trustedExecutable; enginesrc/utils/,aacEncode,streamingEncoder,audioMixer(1048 passed, after the engine change too); studio-serversrc/helpers/(694 passed); CLIsrc/whisper/,src/utils/clipboard*,src/commands/media-use,src/tts/python,src/browser/manager(340 passed); aws-lambdahandler.test.ts(32 passed); typecheck of parsers, engine, studio-server, aws-lambda and producer; the comment ratchet and citation checks;check-cli-process-ownership;check-test-reachability.doctor --jsonfrom a project folder holding.hyperframes/bin/ffmpegand a top-levelffmpeg. Each fake writes a marker file when run. With PATH set to only the Node folder (nowhich, no ffmpeg),doctorreports/usr/bin/ffmpeg. With the project folder first on PATH, it also reports/usr/bin/ffmpeg. Neither marker file was created. Not run locally: an engine/producer/Studio unit run on a machine with no FFmpeg at all (this box has/usr/bin/ffmpeg; CI's unit lanes cover it), a real Windows host (the Windows cases are simulated on Linux withprocess.platform), a real Lambda invocation (the handler change is covered by the unit test only), a real macOS whisper install through brew, andcheck-cli-lazy-depsagainst a fresh CLI build (the local build stopped on unlinked workspace packages unrelated to this change; the new imports arenode:built-ins and@hyperframes/parsersonly).— Rames Jusso
🤖 Generated with Claude Code