paperclipai/paperclip · error · Error

Invalid public base URL

Error message

Invalid public base URL

What it means

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.

Solutions

  1. Set PAPERCLIP_PAGE_BASE_URL to a bare https:// hostname URL with no credentials, query, or hash
  2. Remove any ?... or #... from the base URL value
  3. Use the default by unsetting PAPERCLIP_PAGE_BASE_URL if publishing to pages.paperclip.ing
  4. Confirm the URL parses and protocol === 'https:' before running the publish script

Example fix

// before
PAPERCLIP_PAGE_BASE_URL=http://pages.paperclip.ing?env=dev
// after
PAPERCLIP_PAGE_BASE_URL=https://pages.paperclip.ing
Defensive patterns

Strategy: validation

Validate before calling

const u = new URL(`${baseUrl}/${prefix}/current.json`);
if (u.protocol !== "https:" || u.username || u.password || u.search || u.hash) throw new Error("invalid base URL");

Type guard

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; } };

Try / catch

try { await main(); } catch (e) { if (e.message === "Invalid public base URL") { /* fix PAPERCLIP_PAGE_BASE_URL */ } }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/482ebddf427167c6. Report an issue: GitHub.

Appendix: source

Thrown at scripts/publish-announcements.ts:82

  }
  files.push({ file: manifestPath, key: `${prefix}/current.json`, contentType: "application/json", cacheControl: "public,max-age=300" });
  return { manifest, files };
}

export function announcementUploadArgs(bucket: string, file: Awaited<ReturnType<typeof prepareAnnouncementPublish>>["files"][number]) {
  return ["s3api", "put-object", "--bucket", bucket, "--key", file.key, "--body", file.file,
    "--content-type", file.contentType, "--cache-control", file.cacheControl];
}

async function main() {
  const { sourceDirectory, staging, publish } = parseAnnouncementPublishArgs(process.argv.slice(2));
  const hostPrefix = process.env.PAPERCLIP_PAGE_DEFAULT_PREFIX;
  const prepared = await prepareAnnouncementPublish(sourceDirectory, staging, hostPrefix);
  const bucket = process.env.PAPERCLIP_PAGE_BUCKET;
  const baseUrl = process.env.PAPERCLIP_PAGE_BASE_URL?.replace(/\/+$/, "") ?? "https://pages.paperclip.ing";
  const url = `${baseUrl}/${announcementPublishPrefix(staging, hostPrefix)}/current.json`;
  const parsed = new URL(url);
  if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash) throw new Error("Invalid public base URL");
  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));
  if (!publish) return;
  if (!bucket) throw new Error("Set PAPERCLIP_PAGE_BUCKET before publishing");
  const env = { ...process.env };
  const key = env.PAPERCLIP_PAGE_AWS_ACCESS_KEY_ID;
  const secret = env.PAPERCLIP_PAGE_AWS_SECRET_ACCESS_KEY;
  if (Boolean(key) !== Boolean(secret)) throw new Error("Set both namespaced page uploader credential variables");
  if (key && secret) {
    env.AWS_ACCESS_KEY_ID = key;
    env.AWS_SECRET_ACCESS_KEY = secret;
    delete env.AWS_SESSION_TOKEN;
    if (env.PAPERCLIP_PAGE_AWS_SESSION_TOKEN) env.AWS_SESSION_TOKEN = env.PAPERCLIP_PAGE_AWS_SESSION_TOKEN;
  } else if (env.PAPERCLIP_PAGE_AWS_PROFILE) {
    delete env.AWS_ACCESS_KEY_ID;
    delete env.AWS_SECRET_ACCESS_KEY;
    delete env.AWS_SESSION_TOKEN;
    env.AWS_PROFILE = env.PAPERCLIP_PAGE_AWS_PROFILE;
  }

View on GitHub (pinned to 3f1d897a7c)