windmill-labs/windmill · error · Error
An error occurred
Error message
An error occurred
What it means
The generated 'fetch' starter script for Windmill replaces any failure from fetch() or res.json() with a generic Error('An error occurred'). The real cause (network failure, DNS error, HTTP 4xx/5xx status, or a non-JSON response body that breaks JSON parsing) is swallowed by the catch with no argument, so the developer only sees the opaque message.
Source
Thrown at frontend/src/lib/script_helpers.ts:556
}
const requestOptions: RequestInit = {
method: method || 'GET',
headers: headers || {}
}
if (requestOptions.method !== 'GET' && requestOptions.method !== 'HEAD' && body !== undefined) {
requestOptions.body = JSON.stringify(body)
requestOptions.headers = {
'Content-Type': 'application/json',
...requestOptions.headers
}
}
return await fetch(url, requestOptions)
.then((res) => res.json())
.catch(() => {
throw new Error('An error occurred')
})
}`
const BASH_INIT_CODE = `# shellcheck shell=bash
# arguments of the form X="$I" are parsed as parameters X of type string
msg="$1"
dflt="\${2:-default value}"
# the last line of the stdout is the return value
# unless you write json to './result.json' or a string to './result.out'
echo "Hello $msg"
`
const DENO_INIT_CODE_TRIGGER = `import * as wmill from "npm:windmill-client@${__pkg__.version}"
export async function main() {
// A common trigger script would follow this pattern:View on GitHub (pinned to e474e8803c)
Solutions
- Log the actual error and response status instead of discarding it: catch((err) => { console.error(err); throw err })
- Check res.ok / res.status before calling res.json() and surface the body text on failure
- Verify the target URL is reachable from the worker (curl it from the same environment)
- If the response may not be JSON, use res.text() and parse conditionally
Example fix
// before
return await fetch(url, requestOptions)
.then((res) => res.json())
.catch(() => {
throw new Error('An error occurred')
})
// after
const res = await fetch(url, requestOptions)
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${await res.text()}`)
}
return await res.json() Defensive patterns
Strategy: try-catch
Validate before calling
// before calling the script/fetch
if (!url || !/^https?:\/\//.test(url)) {
throw new Error(`Invalid URL: ${url}`)
} Type guard
function isErrorResponse(res: Response): boolean {
return !res.ok
} Try / catch
try {
const res = await fetch(url, requestOptions)
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`)
return await res.json()
} catch (err) {
// preserve the original cause instead of masking it
throw new Error(`Fetch to ${url} failed: ${err instanceof Error ? err.message : err}`)
} Prevention
- Never use a bare catch that discards the original error
- Always check res.ok before res.json()
- Confirm the URL is reachable from the worker environment, not just locally
- Log response status and body text on failure
When it happens
Trigger: Running the FETCH_INIT_CODE starter script when: the url is unreachable or blocked (network/CORS), the server returns a non-2xx status (fetch does not throw on HTTP errors), or the response body is not valid JSON so res.json() rejects.
Common situations: Typing a wrong or internal-only URL; calling an API that returns HTML error pages or empty bodies; missing auth headers so the server returns 401 with a non-JSON body; TypeScript/Deno environments where fetch fails on TLS or DNS.
Related errors
- HTTP ${res.status} ${res.statusText}
- ${response.status} ${text}
- GET /assets/graph → ${res.status}
- ApiError with mapped HTTP status message (e.g. "Not Found",
- Generic Error: status: ${errorStatus}; status text: ${errorS
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/0a1e636a06ecbecc.
Report an issue: GitHub.