paperclipai/paperclip · error

Invalid CreateOS command timeout.

Error message

Invalid CreateOS command timeout.

What it means

track() validates the effective command timeout: it must be a positive integer within 1 ms..86,400,000 ms (24h). The value comes from the explicit timeoutOverride (e.g. params.timeoutMs from onEnvironmentExecute) or falls back to the config's timeoutMs. This error is thrown when the resolved value is not an integer, is less than 1, or exceeds 24 hours.

Solutions

  1. Pass an integer millisecond value between 1 and 86,400,000 as the operation's timeoutMs (or timeoutOverride).
  2. Check the CreateOS environment config: timeoutMs must be a valid integer ms value — run onEnvironmentValidateConfig to catch it early.
  3. Convert second-based durations to milliseconds (seconds * 1000) before passing them.
  4. Guard computed timeouts: reject NaN/undefined with a default instead of forwarding them to the driver.

Example fix

// before: seconds value forwarded as if milliseconds
await driver.execute({ ...params, timeoutMs: 300 });
// after: normalize and validate before calling
const timeoutMs = Math.floor(Number(process.env.CMD_TIMEOUT_S ?? 600) * 1000);
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86_400_000) {
  throw new RangeError(`timeoutMs must be an integer in [1, 86400000], got ${timeoutMs}`);
}
await driver.execute({ ...params, timeoutMs });
Defensive patterns

Strategy: validation

Validate before calling

function assertValidTimeoutMs(ms) {
  const n = Number(ms);
  if (!Number.isInteger(n) || n < 1 || n > 86_400_000) {
    throw new RangeError(`timeoutMs must be an integer in [1, 86400000], got ${ms}`);
  }
  return n;
}
const timeoutMs = assertValidTimeoutMs(params.timeoutMs ?? config.timeoutMs);

Type guard

function isValidTimeoutMs(v: unknown): v is number {
  return typeof v === "number" && Number.isInteger(v) && v >= 1 && v <= 86_400_000;
}

Try / catch

try {
  return await driver.execute({ ...params, timeoutMs });
} catch (e) {
  if (e.message === "Invalid CreateOS command timeout.") {
    logger.warn(`bad timeoutMs=${params.timeoutMs}; falling back to 600000ms`);
    return await driver.execute({ ...params, timeoutMs: 600_000 });
  }
  throw e;
}

Prevention

When it happens

Trigger: onEnvironmentExecute is called with a non-integer timeoutMs (NaN, undefined when no config default exists, a float), a zero or negative timeout, or a timeout above 86,400,000; or the CreateOS config's timeoutMs itself is out of range and no override is supplied.

Common situations: Passing a duration in seconds (e.g. 300) instead of milliseconds; computing timeout from a Date diff that yields NaN; serializing timeouts through JSON where they become strings; configuring timeoutMs: 0 intending 'no timeout' when 0 is invalid; typo'd config like timeout: 60000 that leaves timeoutMs undefined.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at packages/plugins/sandbox-providers/createos/src/plugin.ts:85

  }

  async function stopActive(scope: string) {
    const calls = [...(active.get(scope) ?? [])];
    for (const call of calls) call.controller.abort();
    await Promise.all(calls.map((call) => call.done));
  }

  async function track<T>(
    params: PluginEnvironmentDriverBaseParams & { lease: PluginEnvironmentLease },
    work: (client: CreateosClient, signal: AbortSignal) => Promise<T>,
    timeoutOverride?: number,
  ): Promise<T> {
    if (!params.lease.providerLeaseId || !metadataMatches(params, params.lease.metadata)) throw new Error("CreateOS execution requires a lease from this environment.");
    const scope = key(params, params.lease.providerLeaseId);
    if (shuttingDown || closing.has(scope) || unconfirmedCleanup.has(scope)) throw new Error("CreateOS lease is closing or requires cleanup.");
    const config = parseConfig(params.config);
    const timeoutMs = timeoutOverride ?? config.timeoutMs;
    if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86_400_000) throw new Error("Invalid CreateOS command timeout.");
    const controller = new AbortController();
    let finish!: () => void;
    const entry: Active = { controller, done: new Promise<void>((resolve) => { finish = resolve; }) };
    const calls = active.get(scope) ?? new Set<Active>();
    calls.add(entry);
    active.set(scope, calls);
    try {
      return await work(new CreateosClient(config), AbortSignal.any([controller.signal, AbortSignal.timeout(timeoutMs)]));
    } catch (error) {
      if (error instanceof CreateosCleanupError) unconfirmedCleanup.add(scope);
      throw error;
    } finally {
      calls.delete(entry);
      if (calls.size === 0) active.delete(scope);
      finish();
    }
  }

View on GitHub (pinned to 3f1d897a7c)