{"record":{"id":"9d277a5b3ee30585","repo":"nexu-io/open-design","slug":"collab-cloud-is-not-configured-od-collab-cloud-ur","errorCode":null,"errorMessage":"collab cloud is not configured (OD_COLLAB_CLOUD_URL is unset)","messagePattern":"collab cloud is not configured \\(OD_COLLAB_CLOUD_URL is unset\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/integrations/collab-cloud.ts","lineNumber":72,"sourceCode":"  displayName: string;\n  role: CollabMemberRole;\n}\n\nexport interface CollabCloudPullResult {\n  comments: CollabCloudComment[];\n  latestSeq: number;\n}\n\ninterface CollabCloudClientOptions {\n  config?: CollabCloudConfig;\n  fetch?: FetchLike;\n  timeoutMs?: number;\n}\n\nexport function createCollabCloudClient(options: CollabCloudClientOptions = {}) {\n  const config = options.config ?? readCollabCloudConfig();\n  if (!config) {\n    throw new Error('collab cloud is not configured (OD_COLLAB_CLOUD_URL is unset)');\n  }\n  const fetchImpl = options.fetch ?? fetch;\n  const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;\n\n  function authHeaders(extra?: Record<string, string>): Record<string, string> {\n    const headers: Record<string, string> = { 'content-type': 'application/json', ...extra };\n    if (config!.token) headers.authorization = `Bearer ${config!.token}`;\n    return headers;\n  }\n\n  async function request<T>(\n    method: string,\n    path: string,\n    body?: unknown,\n    extraHeaders?: Record<string, string>,\n  ): Promise<{ status: number; payload: T; etag: string | null }> {\n    const controller = new AbortController();\n    const timeout = setTimeout(() => controller.abort(), timeoutMs);","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/integrations/collab-cloud.ts#L54-L90","documentation":"Thrown by createCollabCloudClient() when readCollabCloudConfig() returns null, i.e. the env var OD_COLLAB_CLOUD_URL is unset or blank. The collab-cloud module deliberately uses an opt-in, env-scoped config (this file owns OD_COLLAB_CLOUD_*, not app-config.ts) and the client factory treats a missing URL as a hard stop rather than returning a degraded handle. The file header notes callers are expected to degrade with client?.method() no-ops when collab cloud is off.","triggerScenarios":"Calling createCollabCloudClient() (with no options.config override) while OD_COLLAB_CLOUD_URL is empty/unset in the daemon process environment. Also triggered by a caller that forgets to call hasExplicitCollabCloudConfig() first and unconditionally invokes the factory.","commonSituations":"Off-team / unconfigured daemon where collab cloud was never enabled; local dev shell that did not export OD_COLLAB_CLOUD_URL; tests that construct the client without injecting options.config; a packaged run that lost the env var because the namespace env was not propagated to the agent subprocess.","solutions":["Gate the call site: only build the client when hasExplicitCollabCloudConfig() is true, and otherwise treat collab cloud as a no-op (client?.method()).","Set OD_COLLAB_CLOUD_URL (and optionally OD_COLLAB_CLOUD_TOKEN) in the daemon environment and restart the daemon.","If you need the client in a context without env, pass options.config explicitly: createCollabCloudClient({ config: { baseUrl, token } })."],"exampleFix":"// before\nconst client = createCollabCloudClient();\nawait client.registerMember(teamId, memberId, input);\n\n// after\nif (!hasExplicitCollabCloudConfig()) return; // off-team no-op\nconst client = createCollabCloudClient();\nawait client.registerMember(teamId, memberId, input);","handlingStrategy":"validation","validationCode":"import { hasExplicitCollabCloudConfig } from '../integrations/collab-cloud.js';\n\nfunction shouldUseCollabCloud(env: NodeJS.ProcessEnv = process.env): boolean {\n  return hasExplicitCollabCloudConfig(env);\n}\n\n// usage\nif (!shouldUseCollabCloud()) return; // degrade to no-op\nconst client = createCollabCloudClient();","typeGuard":"import type { CollabCloudConfig } from '../integrations/collab-cloud.js';\n\nfunction isCompleteCollabCloudConfig(c: CollabCloudConfig | null): c is CollabCloudConfig {\n  return c !== null && typeof c.baseUrl === 'string' && c.baseUrl.trim().length > 0;\n}","tryCatchPattern":"try {\n  const client = createCollabCloudClient();\n} catch (err) {\n  if (err instanceof Error && err.message.includes('OD_COLLAB_CLOUD_URL is unset')) {\n    // expected when collab cloud is off — degrade silently\n    return;\n  }\n  throw err;\n}","preventionTips":["Always pair createCollabCloudClient() with a hasExplicitCollabCloudConfig() check.","Keep collab-cloud env wiring in this file only; do not re-read OD_COLLAB_CLOUD_URL elsewhere.","In tests, inject options.config so the env-dependent factory does not throw."],"tags":["config","integration","env","collab-cloud"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}