Summary
metal-whisper's (and presumably cpu-whisper/other whisper variants') run.sh forwards LocalAI's positional listen-address argument straight through to the whisper binary (exec "$CURDIR"/whisper "$@"), but backend/go/whisper/main.go parses its address exclusively via the Go flag package:
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
...
flag.Parse()
if err := grpc.StartServer(*addr, &Whisper{}); err != nil { ... }
flag.Parse() does not treat a bare positional token like 127.0.0.1:63508 as a value for -addr; it's silently ignored (not even an error, since there are no required positional args). The binary therefore always binds the default localhost:50051, regardless of what port LocalAI actually assigned it and is trying to connect to.
The result: LocalAI's gRPC client connects to the port it allocated (e.g. 127.0.0.1:63508), finds nothing listening there, and reports a generic, misleading failure:
Backend process exited unexpectedly id="whisper-large-turbo" address="127.0.0.1:63508" process="run.sh" exitCode="2"
Failed to load model ... error=... rpc error: code = Unavailable desc = error reading from server: EOF
(The exitCode=2 here is actually from a later, unrelated failure once the loader gives up and kills the process it can't reach — the whisper process itself, bound to :50051, is running fine at that point; LocalAI just can't see it.)
Environment
- LocalAI v4.8.2 (
5ff25d9d145e0a03a5b9a3559c620f1e1204ca6d)
- Backend
metal-whisper (quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-whisper, digest sha256:4f18b4b228d2a750e74cf9f006e3789bb3d99da5b9d651e4fc1d8e55121346fa)
- Also reproduced identically on
metal-whisper-development (:master-metal-darwin-arm64-whisper), so this is not tied to one release
- macOS 26.5.2, Apple M4 Pro (though the bug is platform-independent — it's a pure CLI-arg-parsing mismatch)
Reproduction
cd ~/backends/metal-whisper
./run.sh 127.0.0.1:59999
# ...
# 2026/08/20 15:15:54 gRPC Server listening at 127.0.0.1:50051 <-- wrong, ignored the arg entirely
# Whereas passing it as the flag the binary actually expects works correctly:
DYLD_LIBRARY_PATH="$(pwd)/lib" WHISPER_LIBRARY="$(pwd)/libgowhisper-fallback.so" \
./whisper -addr=127.0.0.1:59999
# 2026/08/20 15:17:25 gRPC Server listening at 127.0.0.1:59999 <-- correct
Root cause
backend/go/whisper/run.sh (all variants: metal-whisper, cpu-whisper, metal-whisper-development, etc., since they all share this generated launcher) does:
exec "$CURDIR"/whisper "$@"
but should be translating the positional address into the flag the Go binary's main.go expects, e.g.:
if [ -n "$1" ] && [[ "$1" != -* ]]; then
set -- "-addr=$1" "${@:2}"
fi
exec "$CURDIR"/whisper "$@"
(This is the local patch I applied to work around it, confirmed working — the backend now listens on the port LocalAI actually assigned.)
Alternatively/better: fix it on the Go side so main.go accepts the address as os.Args[1] (a positional arg) the same way most of LocalAI's other backend main.gos presumably do, for consistency with however core actually invokes backends — whichever convention is the intended contract between LocalAI core and backend launchers, the two currently disagree, and it's the whisper backend that's out of step. Worth checking whether other backend main.gos in backend/go/* use positional os.Args vs flag.String("addr", ...), since a project-wide inconsistency here would explain why this slipped through.
Impact
Every model load through metal-whisper/cpu-whisper fails on any host where the assigned port isn't already 50051 for another reason — i.e. essentially always, since LocalAI dynamically allocates a fresh port per backend instance. This appears to make the whisper backend entirely non-functional out of the box.
Summary
metal-whisper's (and presumablycpu-whisper/other whisper variants')run.shforwards LocalAI's positional listen-address argument straight through to thewhisperbinary (exec "$CURDIR"/whisper "$@"), butbackend/go/whisper/main.goparses its address exclusively via the Goflagpackage:flag.Parse()does not treat a bare positional token like127.0.0.1:63508as a value for-addr; it's silently ignored (not even an error, since there are no required positional args). The binary therefore always binds the defaultlocalhost:50051, regardless of what port LocalAI actually assigned it and is trying to connect to.The result: LocalAI's gRPC client connects to the port it allocated (e.g.
127.0.0.1:63508), finds nothing listening there, and reports a generic, misleading failure:(The
exitCode=2here is actually from a later, unrelated failure once the loader gives up and kills the process it can't reach — the whisper process itself, bound to :50051, is running fine at that point; LocalAI just can't see it.)Environment
5ff25d9d145e0a03a5b9a3559c620f1e1204ca6d)metal-whisper(quay.io/go-skynet/local-ai-backends:latest-metal-darwin-arm64-whisper, digestsha256:4f18b4b228d2a750e74cf9f006e3789bb3d99da5b9d651e4fc1d8e55121346fa)metal-whisper-development(:master-metal-darwin-arm64-whisper), so this is not tied to one releaseReproduction
Root cause
backend/go/whisper/run.sh(all variants:metal-whisper,cpu-whisper,metal-whisper-development, etc., since they all share this generated launcher) does:but should be translating the positional address into the flag the Go binary's
main.goexpects, e.g.:(This is the local patch I applied to work around it, confirmed working — the backend now listens on the port LocalAI actually assigned.)
Alternatively/better: fix it on the Go side so
main.goaccepts the address asos.Args[1](a positional arg) the same way most of LocalAI's other backendmain.gos presumably do, for consistency with however core actually invokes backends — whichever convention is the intended contract between LocalAI core and backend launchers, the two currently disagree, and it's the whisper backend that's out of step. Worth checking whether other backendmain.gos inbackend/go/*use positionalos.Argsvsflag.String("addr", ...), since a project-wide inconsistency here would explain why this slipped through.Impact
Every model load through
metal-whisper/cpu-whisperfails on any host where the assigned port isn't already50051for another reason — i.e. essentially always, since LocalAI dynamically allocates a fresh port per backend instance. This appears to make the whisper backend entirely non-functional out of the box.