Commit 1bc7a5af0 for llama.cpp

commit 1bc7a5af0d14b1fb72f266abbd1237b394187115
Author: Abhiram <78226909+geckguy@users.noreply.github.com>
Date:   Tue Sep 15 04:41:26 2026 +0530

    webui: stop re-probing disabled /tools endpoint on every message (#28646)

    When /tools returns 403 (server started without tools), the web UI
    refetched the tool list before every chat message, since the guard
    treated an empty tool list as "not yet fetched". Each retry returned
    403 and could trip fail2ban.

    Skip the refetch once the store flags the endpoint as disabled, and
    detect that state via the response status code instead of string-
    matching the error message. The tools panel keeps probing on open so
    the UI recovers once the server is restarted with tools enabled.

    Fixes #28299

diff --git a/tools/ui/src/lib/hooks/use-tools-panel.svelte.ts b/tools/ui/src/lib/hooks/use-tools-panel.svelte.ts
index 21deed32d..cb361aad8 100644
--- a/tools/ui/src/lib/hooks/use-tools-panel.svelte.ts
+++ b/tools/ui/src/lib/hooks/use-tools-panel.svelte.ts
@@ -47,7 +47,7 @@ export function useToolsPanel(): UseToolsPanelReturn {

 		if (toolsStore.toolGroups.length > 0) return null;

-		// Tools endpoint is unreachable (404) — server started without --tools
+		// Tools endpoint unreachable (403) — server started without tools
 		if (toolsStore.isToolsEndpointUnreachable) {
 			return `To enable Server Tools you need to run llama-server with ${CLI_FLAGS.TOOLS} all or ${CLI_FLAGS.TOOLS} <name> flag. To see MCP Tools you need to add / enable MCP Server(s).`;
 		}
diff --git a/tools/ui/src/lib/stores/agentic/index.svelte.ts b/tools/ui/src/lib/stores/agentic/index.svelte.ts
index 50db1be0c..121ee7739 100644
--- a/tools/ui/src/lib/stores/agentic/index.svelte.ts
+++ b/tools/ui/src/lib/stores/agentic/index.svelte.ts
@@ -315,8 +315,14 @@ class AgenticStore {
 		// Clear any pending permissions/continue requests for this conversation when starting a new flow
 		this.gates.clear(conversationId);

-		// Ensure server tools are fetched before checking if agentic is enabled
-		if (toolsStore.serverTools.length === 0 && !toolsStore.loading) {
+		// Ensure server tools are fetched before checking if agentic is enabled.
+		// A disabled /tools endpoint stays disabled for the life of the server,
+		// so the tools panel is the only place that probes it again.
+		if (
+			toolsStore.serverTools.length === 0 &&
+			!toolsStore.loading &&
+			!toolsStore.isToolsEndpointUnreachable
+		) {
 			await toolsStore.fetchServerTools();
 		}

diff --git a/tools/ui/src/lib/stores/tools.svelte.ts b/tools/ui/src/lib/stores/tools.svelte.ts
index db05e3cd5..1d4133408 100644
--- a/tools/ui/src/lib/stores/tools.svelte.ts
+++ b/tools/ui/src/lib/stores/tools.svelte.ts
@@ -32,7 +32,7 @@ import { mcpStore } from '$lib/stores/mcp/index.svelte';
 import { modelsStore } from '$lib/stores/models/index.svelte';
 import { settingsStore } from '$lib/stores/settings/index.svelte';
 import type { OpenAIToolDefinition, ToolEntry, ToolGroup } from '$lib/types';
-import { buildSandboxToolDefinition } from '$lib/utils';
+import { ApiError, buildSandboxToolDefinition } from '$lib/utils';
 import { SvelteMap, SvelteSet } from 'svelte/reactivity';

 /** Stable selection identity for a tool, shared by the disabled set and the permission store */
@@ -246,13 +246,10 @@ class ToolsStore {
 				toolInfos.filter((info) => info.uses_cwd).map((info) => info.tool)
 			);
 		} catch (err) {
-			const errorMessage = err instanceof Error ? err.message : String(err);
-
-			this._error = errorMessage;
+			this._error = err instanceof Error ? err.message : String(err);

 			// 403 from /tools means the server was started without --tools
-			// TODO: check status code instead of relying on message
-			if (errorMessage.includes('this feature is disabled')) {
+			if (err instanceof ApiError && err.status === 403) {
 				this._toolsEndpointUnreachable = true;
 				console.info('[ToolsStore] Server tools are disabled on the server');
 			} else {