{"record":{"id":"9e8a0a896d8dd556","repo":"windmill-labs/windmill","slug":"error-getting-websocket-url-from-runnable-after-5","errorCode":null,"errorMessage":"Error getting WebSocket URL from runnable after 5 tries: {:?}","messagePattern":"Error getting WebSocket URL from runnable after 5 tries: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-trigger-websocket/src/listener.rs","lineNumber":183,"sourceCode":"                tokio::select! {\n                    biased;\n                    _ = killpill_rx.recv() => {\n                        return Ok(None);\n                    },\n                    _ = 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","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-trigger-websocket/src/listener.rs#L165-L201","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: resource \"ws_endpoint\" holds \"my-host:8080/stream\"\n// after\nresource \"ws_endpoint\" value: \"wss://my-host:8080/stream\"  // full URL with ws/wss scheme","handlingStrategy":"validation","validationCode":"// before enabling the trigger, resolve the URL reference yourself once\nconst resource = await api.getResource(workspace, 'u/ws/ws_endpoint');\nconst url = resource.value; // or eval the flow manually\nif (typeof url !== 'string' || !/^wss?:\\/\\//.test(url)) {\n  throw new Error(`runnable must yield a ws:// or wss:// URL, got: ${JSON.stringify(url)}`);\n}","typeGuard":"function isWsUrl(v) {\n  return typeof v === 'string' && /^wss?:\\/\\/[^\\s]+$/i.test(v);\n}","tryCatchPattern":"try {\n  startWebsocketListener(trigger).await?;\n} catch (e) {\n  if (String(e).contains('Error getting WebSocket URL from runnable')) {\n    // reference is misconfigured: inspect resource/flow output before retrying\n    log::error!(\"trigger {}: fix url runnable; detail: {e}\", trigger.path);\n  }\n  return Err(e.into());\n}","preventionTips":["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"],"tags":["websocket","triggers","url-resolution","retry"],"backgroundTag":"retry-exhausted","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"}