windmill-labs/windmill · error
Invalid WebSocket runnable path: {}
Error message
Invalid WebSocket runnable path: {} What it means
The WebSocket trigger listener resolves the runnable (script/flow) referenced by a trigger's path and rewrites it into a WebSocket URL for its consumer. When the configured runnable path does not match the expected workspace path format (u/, f/, g/ or hub/ prefixed), the listener refuses to connect and throws this error instead of building an invalid URL.
Source
Thrown at backend/windmill-trigger-websocket/src/listener.rs:188
_ = 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, and
// killpill cancels this future between awaits.
let mut attempt = 0;
loop {
attempt += 1;
match connect_async_with_proxy(&*connect_url, validated.pinned_addrs()).await {View on GitHub (pinned to e474e8803c)
Solutions
- Fix the trigger's runnable path in the trigger settings to a valid workspace path (u/, f/, g/ or hub/ prefixed)
- Check the trigger definition via the API (GET /api/w/{workspace}/triggers/websocket) and correct the path field
- Re-create the trigger pointing at an existing script or flow, then restart the worker/listener
Example fix
// before runnable_path: "/scripts/listen.py" // after runnable_path: "u/admin/listen.py"
Defensive patterns
Strategy: validation
Validate before calling
fn is_valid_ws_runnable_path(url: &str) -> bool {
url.starts_with("u/") || url.starts_with("f/") || url.starts_with("g/") || url.starts_with("hub/")
}
// call before creating/updating the websocket trigger Prevention
- Always build runnable paths with workspace prefixes (u/, f/, g/, hub/)
- Validate trigger paths in CI before deploying
- Use the UI/CLI to create triggers rather than hand-editing configs
When it happens
Trigger: Creating or editing a websocket trigger whose resource_path / runnable path points to something like '/local/script', 'myscript', or an absolute filesystem path instead of 'u/user/my_script' or 'f/flows/x'. The check happens in get_consumer during trigger startup.
Common situations: Hand-editing trigger configuration, importing triggers from another instance where path conventions differ, typos in the path, or programmatically deploying triggers with paths that were never validated against workspace conventions.
Related errors
- Error getting WebSocket URL from runnable after 5 tries: {:?
- ${what} failed:\n${output}
- Cannot push flow ${remotePath}: step(s) reference non-worksp
- Flow path must be a .flow/__flow directory or a flow.yaml fi
- No instance found, please add one first
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/a60011ebeb606898.
Report an issue: GitHub.