{"record":{"id":"744014724ff6b5af","repo":"danielmiessler/Fabric","slug":"responsedata-error-failed-to-save-to-obsidian","errorCode":null,"errorMessage":"responseData.error || 'Failed to save to Obsidian'","messagePattern":"responseData\\.error \\|\\| 'Failed to save to Obsidian'","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/lib/components/chat/ChatInput.svelte","lineNumber":177,"sourceCode":"    }\n\n    try {\n      const response = await fetch('/obsidian', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({\n          pattern: $selectedPatternName,\n          noteName: $obsidianSettings.noteName,\n          content\n        })\n      });\n\n      const responseData = await response.json();\n      \n      if (!response.ok) {\n        throw new Error(responseData.error || 'Failed to save to Obsidian');\n      }\n      // Add this after successful save\n      updateObsidianSettings({ \n      saveToObsidian: false,  // Reset the save flag\n      noteName: ''           // Clear the note name\n      });\n      toastStore.success(responseData.message || `Saved to Obsidian: ${responseData.fileName}`);\n    } catch (error) {\n      console.error('Failed to save to Obsidian:', error);\n      toastStore.error(error instanceof Error ? error.message : 'Failed to save to Obsidian');\n    }\n  }\n\n  // Centralized language instruction logic in ChatService.ts; YouTube flow now passes plain transcript and system prompt\n  function extractYouTubeURLs(input: string): string[] {\n      const youtubePattern = /(?:https?:\\/\\/)?(?:www\\.)?(?:youtube\\.com\\/watch\\?v=[\\w-]+(?:&[^\\s]*)?|youtu\\.be\\/[\\w-]+(?:\\?[^\\s]*)?)/gi;\n      return input.match(youtubePattern) || [];\n  }","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/danielmiessler/Fabric/blob/338b89cfe97ab2d12ce30ce8b5449857a841366d/web/src/lib/components/chat/ChatInput.svelte#L159-L195","documentation":"Thrown in ChatInput.svelte when the POST to the Obsidian save endpoint returns non-ok. The real reason is in responseData.error from the server — typical causes are the Obsidian vault path not configured or not found, the notes directory missing, or a file write failure. If the server sends a non-JSON error page (e.g. a 404 HTML page from SvelteKit for a wrong route), response.json() itself throws before this line and you get a JSON parse error instead.","triggerScenarios":"Saving a chat with Obsidian settings whose vault/base path is unset or wrong; the configured notes folder was moved or deleted; the endpoint route moved so the fetch hits SvelteKit's 404 handler (HTML body, json() throws); disk permission errors on the vault directory.","commonSituations":"First-time use without completing Obsidian settings, vault relocated after initial config, running the dev server against a different working directory so relative vault paths resolve elsewhere, endpoint path typo after a refactor.","solutions":["Check the browser Network tab: read the actual status and responseData.error for the failed request","Open Obsidian settings in the UI and confirm the vault path / note name are set and the vault directory exists on the machine running the server","Verify the fetch URL matches the actual route (e.g. /api/obsidian/save) — a wrong path returns SvelteKit's HTML 404 and breaks response.json()","Guard the JSON parse so non-JSON error bodies still produce a readable message"],"exampleFix":"// before\nconst responseData = await response.json();\nif (!response.ok) {\n  throw new Error(responseData.error || 'Failed to save to Obsidian');\n}\n\n// after\nconst responseData = await response.json().catch(() => null);\nif (!response.ok) {\n  throw new Error(responseData?.error || `Failed to save to Obsidian (HTTP ${response.status})`);\n}","handlingStrategy":"validation","validationCode":"// Run before POSTing the save\nif (!$obsidianSettings.noteName?.trim()) {\n  toastStore.error('Set an Obsidian note name before saving');\n  return;\n}\nif (!('vaultPath' in $obsidianSettings && $obsidianSettings.vaultPath)) {\n  toastStore.error('Configure the Obsidian vault path first');\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  const response = await fetch('/api/obsidian/save', { method: 'POST', ... });\n  const responseData = await response.json().catch(() => null);\n  if (!response.ok) {\n    throw new Error(responseData?.error || `Failed to save to Obsidian (HTTP ${response.status})`);\n  }\n  toastStore.success(responseData.message || `Saved to Obsidian: ${responseData.fileName}`);\n} catch (error) {\n  toastStore.error(error instanceof Error ? error.message : 'Failed to save to Obsidian');\n}","preventionTips":["Complete Obsidian settings (vault path, note name) before enabling saveToObsidian","json().catch(() => null) so HTML 404/500 pages degrade to a readable message","Keep the fetch URL in sync with the actual route when refactoring endpoints"],"tags":["obsidian","http","file-write","svelte"],"backgroundTag":null,"analyzedSha":"338b89cfe97ab2d12ce30ce8b5449857a841366d","analyzedAt":"2026-08-15T11:38:51.759Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}