{"record":{"id":"482ebddf427167c6","repo":"paperclipai/paperclip","slug":"invalid-public-base-url","errorCode":null,"errorMessage":"Invalid public base URL","messagePattern":"Invalid public base URL","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/publish-announcements.ts","lineNumber":82,"sourceCode":"  }\n  files.push({ file: manifestPath, key: `${prefix}/current.json`, contentType: \"application/json\", cacheControl: \"public,max-age=300\" });\n  return { manifest, files };\n}\n\nexport function announcementUploadArgs(bucket: string, file: Awaited<ReturnType<typeof prepareAnnouncementPublish>>[\"files\"][number]) {\n  return [\"s3api\", \"put-object\", \"--bucket\", bucket, \"--key\", file.key, \"--body\", file.file,\n    \"--content-type\", file.contentType, \"--cache-control\", file.cacheControl];\n}\n\nasync function main() {\n  const { sourceDirectory, staging, publish } = parseAnnouncementPublishArgs(process.argv.slice(2));\n  const hostPrefix = process.env.PAPERCLIP_PAGE_DEFAULT_PREFIX;\n  const prepared = await prepareAnnouncementPublish(sourceDirectory, staging, hostPrefix);\n  const bucket = process.env.PAPERCLIP_PAGE_BUCKET;\n  const baseUrl = process.env.PAPERCLIP_PAGE_BASE_URL?.replace(/\\/+$/, \"\") ?? \"https://pages.paperclip.ing\";\n  const url = `${baseUrl}/${announcementPublishPrefix(staging, hostPrefix)}/current.json`;\n  const parsed = new URL(url);\n  if (parsed.protocol !== \"https:\" || parsed.username || parsed.password || parsed.search || parsed.hash) throw new Error(\"Invalid public base URL\");\n  console.log(JSON.stringify({ mode: publish ? \"publish\" : \"dry-run\", target: staging ? `staging/${staging}` : \"production\", bucket: bucket ?? \"(unset)\", url, announcementId: prepared.manifest.announcement?.id ?? null, files: prepared.files }, null, 2));\n  if (!publish) return;\n  if (!bucket) throw new Error(\"Set PAPERCLIP_PAGE_BUCKET before publishing\");\n  const env = { ...process.env };\n  const key = env.PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID;\n  const secret = env.PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY;\n  if (Boolean(key) !== Boolean(secret)) throw new Error(\"Set both namespaced page uploader credential variables\");\n  if (key && secret) {\n    env.AWS_ACCESS_KEY_ID = key;\n    env.AWS_SECRET_ACCESS_KEY = secret;\n    delete env.AWS_SESSION_TOKEN;\n    if (env.PAPERCLIP_PAGE_AWS_SESSION_TOKEN) env.AWS_SESSION_TOKEN = env.PAPERCLIP_PAGE_AWS_SESSION_TOKEN;\n  } else if (env.PAPERCLIP_PAGE_AWS_PROFILE) {\n    delete env.AWS_ACCESS_KEY_ID;\n    delete env.AWS_SECRET_ACCESS_KEY;\n    delete env.AWS_SESSION_TOKEN;\n    env.AWS_PROFILE = env.PAPERCLIP_PAGE_AWS_PROFILE;\n  }","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/publish-announcements.ts#L64-L100","documentation":"Before publishing, the script builds the public current.json URL from PAPERCLIP_PAGE_BASE_URL (default https://pages.paperclip.ing) and validates it must be HTTPS with no embedded credentials, query string, or fragment. This error means the resulting URL failed that validation.","triggerScenarios":"PAPERCLIP_PAGE_BASE_URL set to an http:// URL, a URL containing user:pass@, or one with ?query or #fragment after trailing slashes are stripped; the joined prefix also produces an invalid URL that URL parsing rejects or flags.","commonSituations":"Pointing the script at a local http dev mirror of the page host; pasting a base URL with a tracking query string or credentials; typos like 'https:/pages...' that break parsing.","solutions":["Set PAPERCLIP_PAGE_BASE_URL to a bare https:// hostname URL with no credentials, query, or hash","Remove any ?... or #... from the base URL value","Use the default by unsetting PAPERCLIP_PAGE_BASE_URL if publishing to pages.paperclip.ing","Confirm the URL parses and protocol === 'https:' before running the publish script"],"exampleFix":"// before\nPAPERCLIP_PAGE_BASE_URL=http://pages.paperclip.ing?env=dev\n// after\nPAPERCLIP_PAGE_BASE_URL=https://pages.paperclip.ing","handlingStrategy":"validation","validationCode":"const u = new URL(`${baseUrl}/${prefix}/current.json`);\nif (u.protocol !== \"https:\" || u.username || u.password || u.search || u.hash) throw new Error(\"invalid base URL\");","typeGuard":"const isValidPublicBaseUrl = (raw: string) => { try { const u = new URL(raw.replace(/\\/+$/, \"\")); return u.protocol === \"https:\" && !u.username && !u.password && !u.search && !u.hash; } catch { return false; } };","tryCatchPattern":"try { await main(); } catch (e) { if (e.message === \"Invalid public base URL\") { /* fix PAPERCLIP_PAGE_BASE_URL */ } }","preventionTips":["Set PAPERCLIP_PAGE_BASE_URL to a bare https:// host with no path credentials, query, or fragment","Unset the variable to use the default https://pages.paperclip.ing","Validate the URL in CI before the publish step","Avoid pasting URLs copied from browsers (they often carry query/hash)"],"tags":["config","url","env"],"backgroundTag":"invalid-url-format","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T10:30:35.592Z"}