linshenkx/prompt-optimizer · error · Error
Garden response is not valid JSON
Error message
Garden response is not valid JSON
What it means
The raw response text from the Garden fetch is passed to JSON.parse; if parsing throws (SyntaxError), it is rethrown as 'Garden response is not valid JSON'. This guards parseV1 from receiving non-JSON input such as HTML error pages, plain-text 502 bodies, or truncated responses.
Source
Thrown at packages/ui/src/composables/app/useAppPromptGardenImport.ts:709
}
return {
importCode: snapshot.importCode,
optimizerTargetKey,
promptFormat: format,
promptText,
promptMessages,
variables: variablesForImport,
examples,
gardenSnapshot: snapshot,
}
}
let data: unknown
try {
data = JSON.parse(text) as unknown
} catch {
throw new Error('Garden response is not valid JSON')
}
return parseV1(data)
}
const resolveGardenUrl = (opts: { gardenBaseUrl: string | null; url: string }): string | null => {
const raw = String(opts.url || '').trim()
if (!raw) return null
if (/^https?:\/\//u.test(raw)) return raw
const base = opts.gardenBaseUrl ? normalizeBaseUrl(opts.gardenBaseUrl) : null
if (!base) return null
try {
return new URL(raw, `${base}/`).toString()
} catch {
return null
}View on GitHub (pinned to 3e677b1d9f)
Solutions
- Log the first ~200 chars of the response text to see what actually came back (usually HTML)
- Fix gardenBaseUrl/resolveGardenUrl so the URL points at the JSON API endpoint, not a page route
- If a proxy or auth wall is intercepting, fix headers/credentials on the fetch
- Check resp.ok and content-type: application/json before parsing, and surface the status alongside this error
Example fix
// before
data = JSON.parse(text)
// after
const resp = await fetch(url)
const text = await resp.text()
if (!resp.ok || !(resp.headers.get('content-type') ?? '').includes('json')) {
throw new Error(`Garden fetch failed: ${resp.status}`)
}
let data: unknown
try { data = JSON.parse(text) } catch {
throw new Error(`Garden response is not valid JSON: ${text.slice(0, 120)}`)
} Defensive patterns
Strategy: try-catch
Validate before calling
const text = await resp.text()
if (!resp.ok) throw new Error(`Garden HTTP ${resp.status}`)
const looksJson = /^[\s\[\{]/.test(text)
if (!looksJson) throw new Error('Non-JSON Garden response') Type guard
const isJsonObjectText = (t: string): boolean => t.trimStart().startsWith('{') Try / catch
try { JSON.parse(text) } catch { throw new Error(`Garden response is not valid JSON (first 120 chars): ${text.slice(0, 120)}`) } Prevention
- Check content-type includes application/json before parsing
- Verify gardenBaseUrl resolves to the API endpoint, not an HTML page
- Log response snippets on parse failure to catch proxy/auth interception
When it happens
Trigger: The resolved Garden URL returns HTML (proxy error page, login redirect), a plain-text error, an empty body with 200, or a truncated stream — anything where JSON.parse throws.
Common situations: Corporate proxy or CDN intercepting the request with an HTML block page; the URL resolving to the app's own SPA index.html (bad gardenBaseUrl path); auth wall returning an HTML login page with 200; response truncated by network drop or size limits.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
Related errors
- Unsupported Garden response schema
- Example image request failed: ${resp.status}
- Invalid import data format
- VALIDATION_ERROR
- INVALID_RESPONSE_FORMAT
AI-assisted analysis of linshenkx/prompt-optimizer@3e677b1d9f (2026-08-27).
Data as JSON: /api/errors/71ebee18cb6bb64f.
Report an issue: GitHub.