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, and

View on GitHub (pinned to e474e8803c)

Solutions

  1. Open the wrapped {:?} error to see why get_url_from_runnable_value failed (not-found resource, parse error, execution error)
  2. Verify the referenced resource exists in the workspace and contains a valid ws:// or wss:// URL string
  3. Run the referenced script/flow manually and confirm it returns exactly the URL string
  4. Check the runnable path syntax ($res:resource_path or $flow:flow_path) and workspace/permissions
  5. 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

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


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/9e8a0a896d8dd556. Report an issue: GitHub.