{"repo":"NousResearch/hermes-agent","public_id":"9726","display_label":"Object values rendering as [object Object] in settings UI","summary":"Multiple reports and corresponding fixes for a bug where object/list config values, particularly fallback_providers, are incorrectly rendered as '[object Object]' in the web UI settings pages.","subsystem":"gateway","counts":{"members":5,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":3,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":10949},"resolution":{"state":"partially_resolved","by":"pr-10949"},"freshness":{"state":"verified","last_verified_at":"2026-05-18T14:03:26+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.30799999999999994},"fix_edges":[{"issue_number":9726,"pr_number":10949,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":9726,"title":"[Bug]: Dashboard Config page renders fallback_providers as \"[object Object]\"","url":null,"state":"closed"},{"kind":"issue","number":11190,"title":"[webUI] fallback_providers list renders as `[object Object]` in General settings","url":null,"state":"closed"},{"kind":"issue","number":25352,"title":"Settings UI: fallback_providers renders as \"[object Object], [object Object]\" in General settings","url":null,"state":"closed"},{"kind":"pr","number":10949,"title":"fix(web): render object config values structurally","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["web/src/components/AutoField.tsx"],"fix_hunks":[{"file":"web/src/components/AutoField.tsx","hunks":["@@ -17,6 +17,71 @@ function FieldHint({ schema, schemaKey }: { schema: Record<string, unknown>; sch\n   );\n }\n \n+function isRecord(value: unknown): value is Record<string, unknown> {\n+  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n+}\n+\n+function formatScalar(value: unknown): string {\n+  if (value === undefined || value === null) return \"\";\n+  if (typeof value === \"string\") return value;\n+  if (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n+  return JSON.stringify(value);\n+}\n+\n+function NestedValueEditor({\n+  fieldKey,\n+  value,\n+  onChange,\n+}: {\n+  fieldKey: string;\n+  value: unknown;\n+  onChange: (v: unknown) => void;\n+}) {\n+  if (isRecord(value)) {\n+    return (\n+      <div className=\"grid gap-2 border border-border p-2\">\n+        {Object.entries(value).map(([subKey, subVal]) => (\n+          <div key={subKey} className=\"grid gap-1\">\n+            <Label className=\"text-xs text-muted-foreground\">{subKey}</Label>\n+            <NestedValueEditor\n+              fieldKey={`${fieldKey}.${subKey}`}\n+              value={subVal}\n+              onChange={(next) => onChange({ ...value, [subKey]: next })}\n+            />\n+          </div>\n+        ))}\n+      </div>\n+    );\n+  }\n+\n+  if (Array.isArray(value)) {\n+    return (\n+      <div className=\"grid gap-2\">\n+        {value.map((item, index) => (\n+          <div key={`${fieldKey}.${index}`} className=\"grid gap-1\">\n+            <Label className=\"text-xs text-muted-foreground\">Item {index + 1}</Label>\n+            <NestedValueEditor\n+              fieldKey={`${fieldKey}.${index}`}\n+              value={item}\n+              onChange={(next) =>\n+                onChange(value.map((existing, i) => (i === index ? next : existing)))\n+              }\n+            />\n+          </div>\n+        ))}\n+      </div>\n+    );\n+  }\n+\n+  return (\n+    <Input\n+      value={formatScalar(value)}\n+      onChange={(e) => onChange(e.target.value)}\n+      className=\"text-xs\"\n+    />\n+  );\n+}\n+\n export function AutoField({\n   schemaKey,\n   schema,","@@ -26,6 +91,16 @@ export function AutoField({\n   const rawLabel = schemaKey.split(\".\").pop() ?? schemaKey;\n   const label = rawLabel.replace(/_/g, \" \").replace(/\\b\\w/g, (c) => c.toUpperCase());\n \n+  if (isRecord(value) || (Array.isArray(value) && value.some((item) => isRecord(item)))) {\n+    return (\n+      <div className=\"grid gap-3 border border-border p-3\">\n+        <Label className=\"text-xs font-medium\">{label}</Label>\n+        <FieldHint schema={schema} schemaKey={schemaKey} />\n+        <NestedValueEditor fieldKey={schemaKey} value={value} onChange={onChange} />\n+      </div>\n+    );\n+  }\n+\n   if (schema.type === \"boolean\") {\n     return (\n       <div className=\"flex items-center justify-between gap-4\">","@@ -114,26 +189,6 @@ export function AutoField({\n     );\n   }\n \n-  if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n-    const obj = value as Record<string, unknown>;\n-    return (\n-      <div className=\"grid gap-3 border border-border p-3\">\n-        <Label className=\"text-xs font-medium\">{label}</Label>\n-        <FieldHint schema={schema} schemaKey={schemaKey} />\n-        {Object.entries(obj).map(([subKey, subVal]) => (\n-          <div key={subKey} className=\"grid gap-1\">\n-            <Label className=\"text-xs text-muted-foreground\">{subKey}</Label>\n-            <Input\n-              value={String(subVal ?? \"\")}\n-              onChange={(e) => onChange({ ...obj, [subKey]: e.target.value })}\n-              className=\"text-xs\"\n-            />\n-          </div>\n-        ))}\n-      </div>\n-    );\n-  }\n-\n   return (\n     <div className=\"grid gap-1.5\">\n       <Label className=\"text-sm\">{label}</Label>\n"]}]},{"kind":"pr","number":25376,"title":"fix(web): AutoField list type renders objects as [object Object]","url":null,"state":"closed","close_reason":{"category":"stale_abandoned","implication":"active"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}