windmill-labs/windmill · error
Error getting WebSocket URL from runnable after 5 tries: {:?
Error message
Error getting WebSocket URL from runnable after 5 tries: {:?} What it means
The websocket listener resolves the trigger's target URL lazily — when configured as $res:/$path or $flow:/$path, it evaluates the runnable (script/flow) up to 5 tries to obtain the WebSocket URL. If all attempts fail, the last error is wrapped into this message and consumer setup aborts.
Source
Thrown at backend/windmill-trigger-websocket/src/listener.rs:183
tokio::select! {
biased;
_ = killpill_rx.recv() => {
return Ok(None);
},
_ = self.loop_ping(&db, listening_trigger, err_message.clone(), Some(
"Waiting on runnable to return WebSocket URL...".to_string()
)) => {
return Ok(None);
},
url_result = {
let authed = listening_trigger.authed(db, "ws").await?;
let args = listening_trigger.trigger_config.url_runnable_args.as_ref().map(|r| &r.0);
let path = url.splitn(2, ':').nth(1).unwrap();
get_url_from_runnable_value(path, url.starts_with("$flow:"), db, authed, args, &listening_trigger.workspace_id)
} => match url_result {
Ok(url) => Cow::Owned(url),
Err(err) => {
return Err(anyhow::anyhow!("Error getting WebSocket URL from runnable after 5 tries: {:?}", err).into());
}
},
}
} else {
return Err(anyhow::anyhow!("Invalid WebSocket runnable path: {}", url).into());
}
} else {
Cow::Borrowed(&url)
};
let validated = validate_websocket_url_for_ssrf(&connect_url).await?;
// Gateway endpoints are often fronted by an edge proxy (e.g. Cloudflare)
// that sporadically answers the upgrade request with a transient 5xx
// instead of `101 Switching Protocols`, and a `get_consumer` error
// disables the trigger until a human re-enables it — so retry transient
// failures with backoff before giving up. The caller runs `loop_ping`
// concurrently so `last_server_ping` stays alive across the sleeps, andView on GitHub (pinned to e474e8803c)
Solutions
- Open the wrapped {:?} error to see why get_url_from_runnable_value failed (not-found resource, parse error, execution error)
- Verify the referenced resource exists in the workspace and contains a valid ws:// or wss:// URL string
- Run the referenced script/flow manually and confirm it returns exactly the URL string
- Check the runnable path syntax ($res:resource_path or $flow:flow_path) and workspace/permissions
- Fix network access from the worker to whatever the runnable depends on, then re-enable the trigger
Example fix
// before: resource "ws_endpoint" holds "my-host:8080/stream" // after resource "ws_endpoint" value: "wss://my-host:8080/stream" // full URL with ws/wss scheme
Defensive patterns
Strategy: validation
Validate before calling
// before enabling the trigger, resolve the URL reference yourself once
const resource = await api.getResource(workspace, 'u/ws/ws_endpoint');
const url = resource.value; // or eval the flow manually
if (typeof url !== 'string' || !/^wss?:\/\//.test(url)) {
throw new Error(`runnable must yield a ws:// or wss:// URL, got: ${JSON.stringify(url)}`);
} Type guard
function isWsUrl(v) {
return typeof v === 'string' && /^wss?:\/\/[^\s]+$/i.test(v);
} Try / catch
try {
startWebsocketListener(trigger).await?;
} catch (e) {
if (String(e).contains('Error getting WebSocket URL from runnable')) {
// reference is misconfigured: inspect resource/flow output before retrying
log::error!("trigger {}: fix url runnable; detail: {e}", trigger.path);
}
return Err(e.into());
} Prevention
- Validate that the referenced resource/flow returns exactly one ws:// or wss:// URL string
- Test the runnable manually in the workspace before wiring it into the trigger
- Use static ws:// URLs instead of runnables when the endpoint rarely changes
- Check worker network access to whatever the URL runnable depends on
When it happens
Trigger: A websocket trigger configured with url as a resource/flow reference ($res:... or $flow:...) whose runnable returns a value that cannot be parsed as a WebSocket URL on every one of 5 tries.
Common situations: Resource misconfigured: the referenced resource doesn't contain a valid ws:// or wss:// URL; the runnable errors (missing permission, missing env, wrong path); the script returns extra text or a non-string; network/firewall issues making the runnable's own lookups fail repeatedly.
Related errors
- Invalid WebSocket runnable path: {}
- The tool list for ${path} changed while it was loading. Try
- error (from getWorkspaceMutationTargetError)
- Failed to create ${triggerConfig.label} "${requestBody.path}
- Not connected to DAP server
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/9e8a0a896d8dd556.
Report an issue: GitHub.