{"record":{"id":"19cce6bb01c64df2","repo":"paperclipai/paperclip","slug":"createos-requires-an-api-url","errorCode":null,"errorMessage":"CreateOS requires an API URL.","messagePattern":"CreateOS requires an API URL\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/sandbox-providers/createos/src/config.ts","lineNumber":21,"sourceCode":"  apiKey: string | null;\n  shape: string;\n  rootfs: string | null;\n  region: string | null;\n  timeoutMs: number;\n  reuseLease: boolean;\n}\n\nexport function parseConfig(raw: Record<string, unknown>): CreateosConfig {\n  const text = (key: string): string | null => {\n    const value = raw[key];\n    if (value == null) return null;\n    if (typeof value !== \"string\" || !value.trim() || value.includes(\"\\0\")) {\n      throw new Error(`${key} must be a non-empty string.`);\n    }\n    return value.trim();\n  };\n  const apiUrl = text(\"apiUrl\");\n  if (!apiUrl) throw new Error(\"CreateOS requires an API URL.\");\n  let url: URL;\n  try { url = new URL(apiUrl); } catch { throw new Error(\"CreateOS API URL is invalid.\"); }\n  // Configuration is board-owned, but never follow redirects with the API key.\n  // Plain HTTP is useful for a loopback development server only.\n  const loopback = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n  if ((url.protocol !== \"https:\" && !(url.protocol === \"http:\" && loopback)) ||\n      url.username || url.password || url.search || url.hash ||\n      ![\"\", \"/\", \"/v1\", \"/v1/\"].includes(url.pathname)) {\n    throw new Error(\"CreateOS API URL must be an HTTPS origin (optionally ending in /v1); HTTP is allowed on loopback only.\");\n  }\n  const shape = text(\"shape\");\n  if (!shape) throw new Error(\"CreateOS requires a shape from its shape catalog.\");\n  const timeoutMs = raw.timeoutMs ?? 300_000;\n  if (typeof timeoutMs !== \"number\" || !Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86_400_000) {\n    throw new Error(\"timeoutMs must be an integer between 1 and 86400000.\");\n  }\n  if (raw.reuseLease != null && typeof raw.reuseLease !== \"boolean\") {\n    throw new Error(\"reuseLease must be a boolean.\");","sourceCodeStart":3,"sourceCodeEnd":39,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/packages/plugins/sandbox-providers/createos/src/config.ts#L3-L39","documentation":"parseConfig requires a non-empty apiUrl to construct the CreateOS client; without it no API calls can be made. This error is thrown when the apiUrl key is absent, null, or undefined in the raw config (text() returned null for apiUrl), before any URL validation happens.","triggerScenarios":"Calling createClient/client/parseConfig/onEnvironmentProbe with a config object that omits apiUrl, or where raw.apiUrl is null/undefined (unset env var, missing JSON key, empty secret).","commonSituations":"Fresh install where CREATEOS_API_URL was never set, config stored per-company but never filled in on the board, renamed config key after an upgrade, or the secrets sync dropped the field.","solutions":["Set apiUrl in the plugin config (or the underlying env var the config is sourced from) to the CreateOS base URL, e.g. https://createos.example.com.","Verify the board/plugin configuration UI actually persisted the apiUrl field and re-save it.","Check for key renames between plugin versions; migrate old config keys to apiUrl.","Run onEnvironmentValidateConfig before deploy to catch missing fields early."],"exampleFix":"// before\nconst config = parseConfig({ apiKey: key }); // apiUrl missing\n// after\nconst config = parseConfig({ apiUrl: process.env.CREATEOS_API_URL, apiKey: key });","handlingStrategy":"validation","validationCode":"function requireApiUrl(raw) {\n  if (raw.apiUrl == null || raw.apiUrl === \"\")\n    throw new Error(\"CreateOS apiUrl is required; set it in plugin config or CREATEOS_API_URL.\");\n  return raw;\n}","typeGuard":null,"tryCatchPattern":"try {\n  config = parseConfig(raw);\n} catch (err) {\n  if (err.message === \"CreateOS requires an API URL.\")\n    throw new Error(\"CreateOS plugin not configured: set apiUrl (e.g. https://createos.example.com) in the board plugin settings.\");\n  throw err;\n}","preventionTips":["Set CREATEOS_API_URL in every environment (dev, staging, prod) and in secret sync.","Run onEnvironmentValidateConfig or a config preflight before probing/creating sandboxes.","Check for config key renames when upgrading the plugin.","Fail fast at boot with a clear message rather than at first API call."],"tags":["config","missing-value","initialization"],"backgroundTag":"missing-required-config-field","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}