#1277·eino

components/model/ollama: requests to ollama.com fail on hosts without ~/.ollama/id_ed25519 — forced request signing has no opt-out

Author: vf1Created Sep 10, 2026Updated Sep 11, 2026

Summary

components/model/ollama (v0.1.9, dep eino-contrib/ollama v0.1.0) unconditionally signs every request with the local Ollama CLI's private key (~/.ollama/id_ed25519) when the base URL hostname is ollama.com. On hosts where the Ollama app was never installed (servers, CI, containers), the key is absent, so every API call fails before the request is sent — even when the app supplies its own Authorization header via the injected HTTPClient (the documented way to use an API key).

Environment

  • github.com/cloudwego/eino-ext/components/model/ollama v0.1.9
  • github.com/eino-contrib/ollama v0.1.0 (only published version)
  • Go 1.25, linux/amd64
  • No ~/.ollama/id_ed25519 on the host (headless server)

Repro

go
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/url"

	"github.com/eino-contrib/ollama/api"
)

func main() {
	base, _ := url.Parse("https://ollama.com")
	client := api.NewClient(base, &http.Client{})
	_, err := client.List(context.Background())
	fmt.Println("err:", err)
	// err: open /home/user/.ollama/id_ed25519: no such file or directory
}

Output on a keyless host:

{"level":"INFO","msg":"Failed to load private key: open /home/user/.ollama/id_ed25519: no such file or directory"}
err: open /home/user/.ollama/id_ed25519: no such file or directory

The same snippet against https://ollama.com. (trailing dot) succeeds — confirming the hardcoded hostname comparison is the trigger.

Root cause

api/client.go, in both do() (line 119) and stream() (line 184):

go
if envconfig.UseAuth() || c.base.Hostname() == "ollama.com" {
    token, err = getAuthorizationToken(ctx, chal) // → auth.Sign()
    if err != nil {
        return err // request aborted before it is sent
    }
    ...
}
  • The hostname branch is unconditional. OLLAMA_AUTH (envconfig/config.go:198) participates via ||, so it can only add the signing requirement, never remove it.
  • auth.Sign (auth/auth.go:59) reads ~/.ollama/id_ed25519 and returns an error when missing, aborting the request. It also emits slog.Info("Failed to load private key: ...") to the host application's default logger — INFO-level noise for a hard failure.
  • The signed token is set as Authorization at client.go:143 before c.http.Do(), so a caller-supplied http.RoundTripper that sets its own Authorization: Bearer <api-key> would overwrite it anyway — the signing is redundant in that setup, yet still mandatory.

This makes ChatModel with BaseURL: "https://ollama.com" + API-key auth via HTTPClient unusable on any host without the Ollama CLI installed — the primary use case for a model-component library running on servers.

Suggested fixes (any one)

  1. Skip signing when the caller's HTTPClient/transport will set its own Authorization (or expose an explicit SkipAuth/NoSign option on Client).
  2. Honor OLLAMA_AUTH=false as an opt-out by changing || to a plain check of UseAuth() and treating the ollama.com hostname as the default (on) rather than forced.
  3. Fall back to an unsigned request when the key is missing instead of aborting.

Workaround

Use the FQDN form with a trailing dot: https://ollama.com. — valid DNS, url.Parse does not normalize the hostname, so the comparison misses while DNS/TLS treat it identically. (Also https://OLLAMA.COM works, but a future case-insensitive comparison would silently break it.)