paperclipai/paperclip · error

reuseLease must be a boolean.

Error message

reuseLease must be a boolean.

What it means

parseConfig validates the optional reuseLease option: if present it must be a boolean. The plugin converts it to a strict true/false (raw.reuseLease === true), so truthy non-boolean values like "true" or 1 are rejected rather than coerced.

Solutions

  1. Change reuseLease to a real boolean: true or false (unquoted).
  2. Remove the key entirely when the default (false) is acceptable — only non-null non-booleans are rejected.
  3. If the config comes from env vars, parse it first, e.g. reuseLease: process.env.REUSE_LEASE === 'true'.

Example fix

// before
{ "reuseLease": "true" }
// after
{ "reuseLease": true }
Defensive patterns

Strategy: validation

Validate before calling

if (cfg.reuseLease != null && typeof cfg.reuseLease !== 'boolean') throw new Error('reuseLease must be a boolean');

Prevention

When it happens

Trigger: Setting reuseLease to a string ("true"/"false"), a number (0/1), null-as-string, or an object in the CreateOS environment config and calling parseConfig via client/config construction or the validate/probe hooks.

Common situations: Editing a JSON/YAML config by hand and leaving the value quoted; environment-variable-driven configs where everything arrives as a string; copying a JS config where the value was a 0/1 flag.

Related errors


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

Appendix: source

Thrown at packages/plugins/sandbox-providers/createos/src/config.ts:39

  if (!apiUrl) throw new Error("CreateOS requires an API URL.");
  let url: URL;
  try { url = new URL(apiUrl); } catch { throw new Error("CreateOS API URL is invalid."); }
  // Configuration is board-owned, but never follow redirects with the API key.
  // Plain HTTP is useful for a loopback development server only.
  const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
  if ((url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) ||
      url.username || url.password || url.search || url.hash ||
      !["", "/", "/v1", "/v1/"].includes(url.pathname)) {
    throw new Error("CreateOS API URL must be an HTTPS origin (optionally ending in /v1); HTTP is allowed on loopback only.");
  }
  const shape = text("shape");
  if (!shape) throw new Error("CreateOS requires a shape from its shape catalog.");
  const timeoutMs = raw.timeoutMs ?? 300_000;
  if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86_400_000) {
    throw new Error("timeoutMs must be an integer between 1 and 86400000.");
  }
  if (raw.reuseLease != null && typeof raw.reuseLease !== "boolean") {
    throw new Error("reuseLease must be a boolean.");
  }
  return {
    apiUrl: url.origin,
    apiKey: text("apiKey"),
    shape,
    rootfs: text("rootfs"),
    region: text("region"),
    timeoutMs,
    reuseLease: raw.reuseLease === true,
  };
}

export function resolveApiKey(config: CreateosConfig): string {
  if (!config.apiKey && config.apiUrl !== "https://api.sb.createos.sh") {
    throw new Error("Custom CreateOS API endpoints require an explicit environment API key; the host fallback is only available for https://api.sb.createos.sh.");
  }
  const key = config.apiKey ?? process.env.CREATEOS_API_KEY?.trim();
  if (!key || /[\r\n\0]/.test(key)) {

View on GitHub (pinned to 3f1d897a7c)