{"record":{"id":"a60011ebeb606898","repo":"windmill-labs/windmill","slug":"invalid-websocket-runnable-path","errorCode":null,"errorMessage":"Invalid WebSocket runnable path: {}","messagePattern":"Invalid WebSocket runnable path: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-trigger-websocket/src/listener.rs","lineNumber":188,"sourceCode":"                    _ = self.loop_ping(&db, listening_trigger, err_message.clone(), Some(\n                    \"Waiting on runnable to return WebSocket URL...\".to_string()\n                )) => {\n                        return Ok(None);\n                    },\n                    url_result = {\n                        let authed = listening_trigger.authed(db, \"ws\").await?;\n                        let args = listening_trigger.trigger_config.url_runnable_args.as_ref().map(|r| &r.0);\n                        let path = url.splitn(2, ':').nth(1).unwrap();\n                        get_url_from_runnable_value(path, url.starts_with(\"$flow:\"), db, authed, args, &listening_trigger.workspace_id)\n                    } => match url_result {\n                        Ok(url) => Cow::Owned(url),\n                        Err(err) => {\n                            return Err(anyhow::anyhow!(\"Error getting WebSocket URL from runnable after 5 tries: {:?}\", err).into());\n                        }\n                    },\n                }\n            } else {\n                return Err(anyhow::anyhow!(\"Invalid WebSocket runnable path: {}\", url).into());\n            }\n        } else {\n            Cow::Borrowed(&url)\n        };\n\n        let validated = validate_websocket_url_for_ssrf(&connect_url).await?;\n\n        // Gateway endpoints are often fronted by an edge proxy (e.g. Cloudflare)\n        // that sporadically answers the upgrade request with a transient 5xx\n        // instead of `101 Switching Protocols`, and a `get_consumer` error\n        // disables the trigger until a human re-enables it — so retry transient\n        // failures with backoff before giving up. The caller runs `loop_ping`\n        // concurrently so `last_server_ping` stays alive across the sleeps, and\n        // killpill cancels this future between awaits.\n        let mut attempt = 0;\n        loop {\n            attempt += 1;\n            match connect_async_with_proxy(&*connect_url, validated.pinned_addrs()).await {","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-trigger-websocket/src/listener.rs#L170-L206","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nrunnable_path: \"/scripts/listen.py\"\n// after\nrunnable_path: \"u/admin/listen.py\"","handlingStrategy":"validation","validationCode":"fn is_valid_ws_runnable_path(url: &str) -> bool {\n    url.starts_with(\"u/\") || url.starts_with(\"f/\") || url.starts_with(\"g/\") || url.starts_with(\"hub/\")\n}\n// call before creating/updating the websocket trigger","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["websocket","triggers","path-validation","configuration"],"backgroundTag":"invalid-runnable-path","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}