From 06cf00129fa6f8c41ceea15de26c2de69bb1328e Mon Sep 17 00:00:00 2001 From: "Cody (Radius Red)" Date: Tue, 31 Mar 2026 15:09:54 +0000 Subject: [PATCH 1/3] fix(ui): preserve env var when switching type from Plain to Secret MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When changing an env var's type from Plain to Secret in the agent config form, the row was silently dropped because emit() skipped secret rows without a secretId. This caused data loss — the variable disappeared from both the UI and the saved config. Fix: keep the row as a plain binding during the transition state until the user selects an actual secret. This preserves the key and value so nothing is lost. Co-Authored-By: Paperclip --- ui/src/components/AgentConfigForm.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index 06c74e65..7d5c4f45 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -1170,8 +1170,12 @@ function EnvVarEditor({ const k = row.key.trim(); if (!k) continue; if (row.source === "secret") { - if (!row.secretId) continue; - rec[k] = { type: "secret_ref", secretId: row.secretId, version: "latest" }; + if (row.secretId) { + rec[k] = { type: "secret_ref", secretId: row.secretId, version: "latest" }; + } else { + // Preserve as plain during transition to avoid data loss + rec[k] = { type: "plain", value: row.plainValue }; + } } else { rec[k] = { type: "plain", value: row.plainValue }; } From ce8d9eb3234f25390703348e9ad17311eb7987d5 Mon Sep 17 00:00:00 2001 From: "Cody (Radius Red)" Date: Tue, 31 Mar 2026 15:42:03 +0000 Subject: [PATCH 2/3] fix(server): preserve adapter-agnostic keys when changing adapter type When the adapter type changes via PATCH, the server only preserved instruction bundle keys (instructionsBundleMode, etc.) from the existing config. Adapter-agnostic keys like env, cwd, timeoutSec, graceSec, promptTemplate, and bootstrapPromptTemplate were silently dropped if the PATCH payload didn't explicitly include them. This caused env var data loss when adapter type was changed via the UI or API without sending the full existing adapterConfig. The fix preserves these adapter-agnostic keys from the existing config before applying the instruction bundle preservation, matching the UI's behavior in AgentConfigForm.handleSave. Co-Authored-By: Paperclip --- server/src/routes/agents.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index 2ad85e63..c2cacfe4 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -1772,6 +1772,18 @@ export function agentRoutes(db: Db) { rawEffectiveAdapterConfig = { ...existingAdapterConfig, ...requestedAdapterConfig }; } if (changingAdapterType) { + // Preserve adapter-agnostic keys (env, cwd, etc.) from the existing config + // when the adapter type changes. Without this, a PATCH that includes + // adapterConfig but omits these keys would silently drop them. + const ADAPTER_AGNOSTIC_KEYS = [ + "env", "cwd", "timeoutSec", "graceSec", + "promptTemplate", "bootstrapPromptTemplate", + ] as const; + for (const key of ADAPTER_AGNOSTIC_KEYS) { + if (rawEffectiveAdapterConfig[key] === undefined && existingAdapterConfig[key] !== undefined) { + rawEffectiveAdapterConfig = { ...rawEffectiveAdapterConfig, [key]: existingAdapterConfig[key] }; + } + } rawEffectiveAdapterConfig = preserveInstructionsBundleConfig( existingAdapterConfig, rawEffectiveAdapterConfig, From 92e03ac4e3c7b19b265f4df881153ccbb43195bb Mon Sep 17 00:00:00 2001 From: "Cody (Radius Red)" Date: Tue, 31 Mar 2026 15:52:46 +0000 Subject: [PATCH 3/3] fix(ui): prevent dropdown snap-back when switching env var to Secret Address Greptile review feedback: the plain-value fallback in emit() caused the useEffect sync to re-run toRows(), which mapped the plain binding back to source: "plain", snapping the dropdown back. Fix: add an emittingRef that distinguishes local emit() calls from external value changes (like overlay reset after save). When the change originated from our own emit, skip the re-sync so the transitioning row stays in "secret" mode while the user picks a secret. Co-Authored-By: Paperclip --- ui/src/components/AgentConfigForm.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index 7d5c4f45..c3c9bdfa 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -1155,9 +1155,17 @@ function EnvVarEditor({ const [rows, setRows] = useState(() => toRows(value)); const [sealError, setSealError] = useState(null); const valueRef = useRef(value); + const emittingRef = useRef(false); - // Sync when value identity changes (overlay reset after save) + // Sync when value identity changes (overlay reset after save). + // Skip re-sync when the change was triggered by our own emit() to avoid + // reverting local row state (e.g. a secret-transition dropdown choice). useEffect(() => { + if (emittingRef.current) { + emittingRef.current = false; + valueRef.current = value; + return; + } if (value !== valueRef.current) { valueRef.current = value; setRows(toRows(value)); @@ -1173,13 +1181,15 @@ function EnvVarEditor({ if (row.secretId) { rec[k] = { type: "secret_ref", secretId: row.secretId, version: "latest" }; } else { - // Preserve as plain during transition to avoid data loss + // Row is transitioning to secret but user hasn't picked one yet. + // Preserve the plain value so it isn't silently dropped. rec[k] = { type: "plain", value: row.plainValue }; } } else { rec[k] = { type: "plain", value: row.plainValue }; } } + emittingRef.current = true; onChange(Object.keys(rec).length > 0 ? rec : undefined); }