Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions internal/config/config_load.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,18 @@ func (c *Config) applyEnvOverrides() {
envStr("GOCLAW_CLAUDE_CLI_MODEL", &c.Providers.ClaudeCLI.Model)
envStr("GOCLAW_CLAUDE_CLI_WORK_DIR", &c.Providers.ClaudeCLI.BaseWorkDir)

// Default provider/model: env overrides config.
envStr("GOCLAW_PROVIDER", &c.Agents.Defaults.Provider)
envStr("GOCLAW_MODEL", &c.Agents.Defaults.Model)
// Default provider/model: env is fallback only (applied when config has no value).
// The onboard wizard sets these in .env for initial bootstrap; once the user
// saves a provider/model via the Dashboard, the config-file value wins.
envFallback := func(key string, dst *string) {
if *dst == "" {
if v := os.Getenv(key); v != "" {
*dst = v
}
}
}
envFallback("GOCLAW_PROVIDER", &c.Agents.Defaults.Provider)
envFallback("GOCLAW_MODEL", &c.Agents.Defaults.Model)

// Workspace & sessions
envStr("GOCLAW_WORKSPACE", &c.Agents.Defaults.Workspace)
Expand Down
19 changes: 14 additions & 5 deletions ui/web/src/components/shared/provider-model-select.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo, useEffect } from "react";
import { useMemo, useEffect, useRef } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
Expand Down Expand Up @@ -57,14 +57,23 @@ export function ProviderModelSelect({
}: ProviderModelSelectProps) {
const { t } = useTranslation("common");
const { providers } = useProviders();
const enabledProviders = providers.filter((p) => p.enabled);
const enabledProviders = useMemo(
() => providers.filter((p) => p.enabled),
[providers],
);

// Stable ref for callback — prevents the auto-select effect from re-running
// on every parent render (inline onProviderChange creates a new ref each time).
const onProviderChangeRef = useRef(onProviderChange);
onProviderChangeRef.current = onProviderChange;

// Auto-select first enabled provider when none is set (unless allowEmpty)
// Auto-select first enabled provider when none is set (unless allowEmpty).
// Uses ref for callback so this only re-runs when provider or providers actually change.
useEffect(() => {
if (!allowEmpty && !provider && enabledProviders.length > 0) {
onProviderChange(enabledProviders[0]!.name);
onProviderChangeRef.current(enabledProviders[0]!.name);
}
}, [allowEmpty, provider, enabledProviders, onProviderChange]);
}, [allowEmpty, provider, enabledProviders]);

const selectedProvider = useMemo(
() => enabledProviders.find((p) => p.name === provider),
Expand Down
11 changes: 9 additions & 2 deletions ui/web/src/pages/config/hooks/use-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useWs } from "@/hooks/use-ws";
import { useAuthStore } from "@/stores/use-auth-store";
import { Methods } from "@/api/protocol";
import { queryKeys } from "@/lib/query-keys";
import { toast } from "@/stores/use-toast-store";

interface ConfigData {
config: Record<string, unknown>;
Expand Down Expand Up @@ -50,8 +51,11 @@ export function useConfig() {
});
hashRef.current = res.hash;
await invalidate();
toast.success("Config saved");
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to apply config");
const msg = err instanceof Error ? err.message : "Failed to apply config";
setError(msg);
toast.error("Failed to save config", msg);
throw err;
} finally {
setSaving(false);
Expand All @@ -71,8 +75,11 @@ export function useConfig() {
});
hashRef.current = res.hash;
await invalidate();
toast.success("Config saved");
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to patch config");
const msg = err instanceof Error ? err.message : "Failed to patch config";
setError(msg);
toast.error("Failed to save config", msg);
throw err;
} finally {
setSaving(false);
Expand Down