paperclipai/paperclip · error · Error

Cloud readiness requires positive finite timeout and poll…

Error message

Cloud readiness requires positive finite timeout and poll interval.

What it means

waitForCloudArtifacts validates its timeoutMs and intervalMs options before starting the poll loop; both must be finite positive numbers. Non-finite (NaN/Infinity), zero, or negative values throw immediately.

Solutions

  1. Pass positive finite numbers: timeoutMs defaults to 1,800,000 (30 min) and intervalMs to 20,000 (20s).
  2. Guard env-derived values: validate with Number.isFinite(v) && v > 0 before passing.
  3. If reading from env, default when empty: const t = Number(process.env.TIMEOUT_MS) || 30*60_000.

Example fix

// before
const timeoutMs = Number(process.env.CLOUD_TIMEOUT_MS); // NaN when unset
// after
const timeoutMs = Number(process.env.CLOUD_TIMEOUT_MS) || 30 * 60_000;
if (!(Number.isFinite(timeoutMs) && timeoutMs > 0)) throw new Error("CLOUD_TIMEOUT_MS must be positive");
Defensive patterns

Strategy: validation

Validate before calling

const opts = {};
if (process.env.CLOUD_TIMEOUT_MS) opts.timeoutMs = Number(process.env.CLOUD_TIMEOUT_MS);
if (process.env.CLOUD_INTERVAL_MS) opts.intervalMs = Number(process.env.CLOUD_INTERVAL_MS);
for (const [k, v] of Object.entries(opts)) {
  if (!Number.isFinite(v) || v <= 0) throw new Error(`${k} must be a positive finite number, got ${v}`);
}
await waitForCloudArtifacts(sha, opts);

Type guard

const positiveFinite = (v) => typeof v === "number" && Number.isFinite(v) && v > 0;

Try / catch

try {
  await waitForCloudArtifacts(sha, opts);
} catch (error) {
  if (/positive finite timeout/.test(error.message)) {
    console.error("Bad poll options — use e.g. { timeoutMs: 1800000, intervalMs: 20000 }.");
    process.exitCode = 1;
  } else throw error;
}

Prevention

When it happens

Trigger: Calling waitForCloudArtifacts(sha, { timeoutMs: 0 }) or intervalMs: -1000, or passing NaN (e.g. Number(process.env.TIMEOUT) with an empty env var), or Infinity.

Common situations: Parsing env vars with Number()/parseInt on missing values yielding NaN; config wiring passing seconds where milliseconds are expected as 0; unit tests passing sentinel values.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at scripts/cloud-readiness.mjs:70

      "--cert-identity", `https://github.com/${repository}/${workflow}@refs/heads/master`,
      "--deny-self-hosted-runners"], { stdio: "inherit", timeout: 60_000 });
  } finally { rmSync(scratch, { recursive: true, force: true }); }
}

/** Read-only availability gate. Deployment still resolves and pins artifacts. */
export async function waitForCloudArtifacts(sha, {
  fetchImpl = fetch,
  token = process.env.GH_TOKEN,
  verifyProvenance = verifyManifestProvenance,
  now = () => performance.now(),
  sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
  timeoutMs = 30 * 60_000,
  intervalMs = 20_000,
  log = console.log,
} = {}) {
  const version = versionFor(sha);
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || !Number.isFinite(intervalMs) || intervalMs <= 0) {
    throw new Error("Cloud readiness requires positive finite timeout and poll interval.");
  }
  const deadline = now() + timeoutMs;
  let previous;
  let missing = ["image", "migrator"];
  while (now() < deadline) {
    // Recheck the image and exact-source publisher on the successful poll.
    // Only a missing/pending producer waits; failed publication fails closed.
    const results = await Promise.all([
      imageExists(sha, fetchImpl),
      migratorPublished(sha, fetchImpl, token),
    ]);
    missing = ["image", "migrator"].filter((_, index) => !results[index]);
    if (missing.length === 0) {
      // Verify the exact signed bytes and all pinned downloads after the
      // publisher succeeds. An inaccessible or corrupt artifact cannot pass.
      await verifyPublished(sha, fetchImpl, { verifyProvenance });
      log(`Cloud artifacts available for ${sha}: verified image and exact-source migrator ${version}.`);
      return { version: 1, sha, packageVersion: version };

View on GitHub (pinned to 3f1d897a7c)