{"record":{"id":"29509e39b44be128","repo":"tinyhumansai/openhuman","slug":"key-is-required","errorCode":null,"errorMessage":"\"{key}\" is required","messagePattern":"\"(.+?)\" is required","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/src/components/channels/mcp/InstalledServerDetail.tsx","lineNumber":199,"sourceCode":"  // sets may be partial; the form requires every key so a reconnect never drops\n  // a required var (issue #3039 gap B6 — suggested values were never persisted).\n  const handleApplySuggestedEnv = useCallback(\n    (env: Record<string, string>) => {\n      log('suggested_env received, opening reconfigure form keys=%o', Object.keys(env));\n      setShowAssistant(false);\n      openReconfigure(env);\n    },\n    [openReconfigure]\n  );\n\n  const handleSaveReconfigure = useCallback(() => {\n    void runBusy(async () => {\n      // Replace-all semantics (update_env DELETEs then INSERTs): every key must\n      // have a value or the server loses required env on reconnect. Mirror the\n      // install dialog's validation.\n      for (const key of visibleEnvKeys) {\n        if (!reconfigValues[key]?.trim()) {\n          throw new Error(t('mcp.install.missingRequired').replace('{key}', key));\n        }\n      }\n      log('reconfigure save server_id=%s', server.server_id);\n      const result = await mcpClientsApi.updateEnv({\n        server_id: server.server_id,\n        env: reconfigValues,\n      });\n      setTools(result.tools ?? []);\n      if (result.status === 'unauthorized') {\n        // A 401 after reconfigure: show the actionable auth reason (use Sign in\n        // / token rejected / credential required) the same way the Connect\n        // dialog does — the raw 401 message is withheld server-side (#4289).\n        const key = authHintMessageKey(result.auth_hint);\n        throw new Error(key ? t(key) : t('mcp.detail.reconfigureReconnectFailed'));\n      }\n      if (result.status !== 'connected') {\n        throw new Error(result.error ?? t('mcp.detail.reconfigureReconnectFailed'));\n      }","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/components/channels/mcp/InstalledServerDetail.tsx#L181-L217","documentation":"Reconfigure validation in InstalledServerDetail: before calling mcpClientsApi.updateEnv, every key in visibleEnvKeys must have a non-empty trimmed value in reconfigValues. The check is strict because update_env has replace-all semantics (DELETEs all env rows then INSERTs the submitted set) - a blank value would silently strip required env on the next reconnect. Throws the localized 'mcp.install.missingRequired' with {key} substituted.","triggerScenarios":"Saving the Reconfigure dialog with any visible env field left empty or whitespace-only - e.g. the user clears a value intending to 'keep it as-is', or a prefill did not populate one key.","commonSituations":"Users assume blank means 'unchanged' (it means 'delete this var' server-side, hence the guard); migrating a server whose stored env keys have no values; copying a .env block where one line lost its value.","solutions":["Enter a value for the exact key named in the message, then Save again","If the var is genuinely optional, remove it from the server's env keys so it is not part of the replace-all set, rather than submitting it blank","Copy the full original value from the server's configuration before reconfiguring"],"exampleFix":"// before - throws only inside the busy handler after Save is clicked\nfor (const key of visibleEnvKeys) {\n  if (!reconfigValues[key]?.trim()) {\n    throw new Error(t('mcp.install.missingRequired').replace('{key}', key));\n  }\n}\n\n// after - also disable Save up front so the error cannot happen\nconst reconfigInvalid = visibleEnvKeys.some(k => !reconfigValues[k]?.trim());\n// <Button disabled={reconfigInvalid || busy}>Save</Button>","handlingStrategy":"validation","validationCode":"// Run the identical check before opening the busy path\nconst invalidKeys = visibleEnvKeys.filter(k => !reconfigValues[k]?.trim());\nconst canSave = invalidKeys.length === 0;\n// <Button disabled={!canSave || busy} onClick={handleSaveReconfigure}>Save</Button>","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Remember update_env is replace-all (DELETE then INSERT): blank never means 'keep the old value'","Prefill every existing key's value when opening the Reconfigure dialog so users edit rather than re-enter","Disable Save while any visible key is empty - the throw exists for races, not as the primary UX"],"tags":["mcp","env-vars","validation","reconfigure","form"],"backgroundTag":"required-form-field-empty","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}