ruvnet/ruflo · error

Failed to fetch ${baseURL}/models: ${response.status} ${resp

Error message

Failed to fetch ${baseURL}/models: ${response.status} ${response.statusText} (no auth token available)

What it means

Thrown by buildModels() when the GET ${OPENAI_BASE_URL}/models request returns 401 AND no auth token is configured. The token is OPENAI_API_KEY (canonical) or HF_TOKEN (legacy alias); with neither set and an unauthorized response, retrying cannot help, so the code fails fast with the '(no auth token available)' suffix instead of looping on a doomed request.

Source

Thrown at ruflo/src/ruvocal/src/lib/server/models.ts:320

		throw new Error("OPENAI_BASE_URL not set");
	}

	try {
		const baseURL = openaiBaseUrl;
		logger.info({ baseURL }, "[models] Using OpenAI-compatible base URL");

		// Canonical auth token is OPENAI_API_KEY; keep HF_TOKEN as legacy alias
		const authToken = config.OPENAI_API_KEY || config.HF_TOKEN;

		// Use auth token from the start if available to avoid rate limiting issues
		// Some APIs rate-limit unauthenticated requests more aggressively
		const response = await fetch(`${baseURL}/models`, {
			headers: authToken ? { Authorization: `Bearer ${authToken}` } : undefined,
		});
		logger.info({ status: response.status }, "[models] First fetch status");
		if (!response.ok && response.status === 401 && !authToken) {
			// If we get 401 and didn't have a token, there's nothing we can do
			throw new Error(
				`Failed to fetch ${baseURL}/models: ${response.status} ${response.statusText} (no auth token available)`
			);
		}
		if (!response.ok) {
			throw new Error(
				`Failed to fetch ${baseURL}/models: ${response.status} ${response.statusText}`
			);
		}
		const json = await response.json();
		logger.info({ keys: Object.keys(json || {}) }, "[models] Response keys");

		const parsed = listSchema.parse(json);
		logger.info({ count: parsed.data.length }, "[models] Parsed models count");

		let modelsRaw = parsed.data.map((m) => {
			let logoUrl: string | undefined = undefined;
			if (isHFRouter && m.id.includes("/")) {
				const org = m.id.split("/")[0];

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Set OPENAI_API_KEY (or legacy HF_TOKEN) in .env.local and restart the server.
  2. Verify the token is valid for the gateway: curl -H "Authorization: Bearer $OPENAI_API_KEY" $OPENAI_BASE_URL/models returns 200.
  3. If the gateway truly serves anonymous model lists, check for a proxy in front that injects its own 401 (corporate proxy, auth middleware).
  4. Make sure the token has no trailing newline/quotes in the env file.

Example fix

# .env.local (before)
OPENAI_BASE_URL=https://router.huggingface.co/v1

# .env.local (after)
OPENAI_BASE_URL=https://router.huggingface.co/v1
OPENAI_API_KEY=hf_xxxxxxxxxxxx
Defensive patterns

Strategy: validation

Validate before calling

const token = process.env.OPENAI_API_KEY || process.env.HF_TOKEN;
if (!token) {
	throw new Error("OPENAI_API_KEY (or HF_TOKEN) is required by this gateway");
}

Try / catch

try {
	await buildModels();
} catch (err) {
	if (String(err).includes("no auth token available")) {
		// configuration problem — do not retry, surface to operator
		process.exitCode = 1;
	}
	throw err;
}

Prevention

When it happens

Trigger: fetch(baseURL + '/models') with no Authorization header responds 401 because neither config.OPENAI_API_KEY nor config.HF_TOKEN is set. Typical for gateways (e.g. Hugging Face router) that require a Bearer token even for the model list.

Common situations: Deployments that copied .env but left OPENAI_API_KEY blank assuming the gateway was public; migration from HF_TOKEN-only setups where the alias was removed; token present under a different variable name.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/cae8577824a0a769. Report an issue: GitHub.