{"record":{"id":"1a9e7aebd84b86d0","repo":"chenhg5/cc-connect","slug":"cloud-web-ws-url-or-base-url-is-required-for-webs","errorCode":null,"errorMessage":"cloud_web: ws_url or base_url is required for websocket transport","messagePattern":"cloud_web: ws_url or base_url is required for websocket transport","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/cloud-web/cloudweb.go","lineNumber":103,"sourceCode":"\tlisten, _ := opts[\"listen\"].(string)\n\twebhookPath, _ := opts[\"webhook_path\"].(string)\n\tregisterURL, _ := opts[\"register_url\"].(string)\n\tpublicURL, _ := opts[\"public_url\"].(string)\n\teventsPath, _ := opts[\"events_path\"].(string)\n\tsendPath, _ := opts[\"send_path\"].(string)\n\tlongPollMS := pickInt(opts[\"long_poll_timeout_ms\"])\n\n\tallowFrom, _ := opts[\"allow_from\"].(string)\n\tcore.CheckAllowFrom(name, allowFrom)\n\tshareInChannel, _ := opts[\"share_session_in_channel\"].(bool)\n\tgroupReplyAll, _ := opts[\"group_reply_all\"].(bool)\n\n\tvar tp transport\n\tswitch transportKind {\n\tcase \"websocket\":\n\t\tresolved := resolveWSURL(baseURL, wsURL)\n\t\tif strings.TrimSpace(resolved) == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"cloud_web: ws_url or base_url is required for websocket transport\")\n\t\t}\n\t\ttp = newWSTransport(resolved, token, name, project)\n\tcase \"long_poll\":\n\t\tif strings.TrimSpace(baseURL) == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"cloud_web: base_url is required for long_poll transport\")\n\t\t}\n\t\ttp = newPollTransport(baseURL, token, name, project, eventsPath, sendPath, longPollMS)\n\tcase \"gateway\":\n\t\tif strings.TrimSpace(baseURL) == \"\" && strings.TrimSpace(registerURL) != \"\" {\n\t\t\tbaseURL = deriveBaseURL(registerURL)\n\t\t}\n\t\tif strings.TrimSpace(baseURL) == \"\" && strings.TrimSpace(registerURL) == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"cloud_web: base_url or register_url is required for gateway transport\")\n\t\t}\n\t\tif strings.TrimSpace(registerURL) != \"\" && strings.TrimSpace(publicURL) == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"cloud_web: public_url is required when register_url is set for gateway transport\")\n\t\t}\n\t\ttp = newGatewayTransport(baseURL, token, name, project, listen, webhookPath, registerURL, publicURL, sendPath)","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/cloud-web/cloudweb.go#L85-L121","documentation":"The cloud_web constructor requires a usable endpoint when transport is \"websocket\". resolveWSURL(baseURL, wsURL) derives the WebSocket URL from ws_url or base_url; if both are empty/blank, no connection target exists and New returns this error.","triggerScenarios":"Calling cloudweb.New with transport unset or \"websocket\" while both opts[\"ws_url\"] and opts[\"base_url\"] are missing, non-string, or whitespace-only.","commonSituations":"Config block containing token but no server URL; ws_url/base_url provided as non-string types (number, bool); empty strings from unexpanded env placeholders; switching transports to websocket without carrying over base_url from a long_poll config.","solutions":["Add `base_url = \"https://<your-cloud-web-host>\"` to the cloud_web platform options.","Or set `ws_url = \"wss://<host>/ws\"` directly for an explicit WebSocket endpoint.","Verify both values are quoted non-empty strings (check unexpanded env placeholders).","If you only have an HTTP endpoint for polling, set `transport = \"long_poll\"` with base_url instead."],"exampleFix":"// before\nopts := map[string]any{\"token\": tok} // websocket default, no endpoint\np, err := cloudweb.New(opts)\n// after\nopts := map[string]any{\"token\": tok, \"base_url\": \"https://cloud.example.com\"}\np, err := cloudweb.New(opts)","handlingStrategy":"validation","validationCode":"func hasWSEndpoint(opts map[string]any) bool {\n\tws, _ := opts[\"ws_url\"].(string)\n\tbase, _ := opts[\"base_url\"].(string)\n\treturn strings.TrimSpace(ws) != \"\" || strings.TrimSpace(base) != \"\"\n}","typeGuard":"func hasNonEmptyString(opts map[string]any, key string) bool {\n\ts, ok := opts[key].(string)\n\treturn ok && strings.TrimSpace(s) != \"\"\n}\n// use: hasNonEmptyString(opts, \"ws_url\") || hasNonEmptyString(opts, \"base_url\")","tryCatchPattern":"p, err := cloudweb.New(opts)\nif err != nil {\n\tif strings.Contains(err.Error(), \"ws_url or base_url is required\") {\n\t\tslog.Error(\"cloud_web: add base_url (https://host) or ws_url (wss://host/ws) for websocket transport\")\n\t\treturn\n\t}\n\treturn err\n}","preventionTips":["Always set base_url (or ws_url) alongside token for cloud_web.","Quote URL values as strings; avoid unexpanded env placeholders.","When switching from long_poll to websocket, carry base_url over.","Add a startup config check that both token and an endpoint exist for websocket transport."],"tags":["config","websocket","cloud-web","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}