ruvnet/ruflo · error · PodTemplateValidationError

pod-template at /: reservationExpiryMs must be within [5000,

Error message

pod-template at /: reservationExpiryMs must be within [5000, 300000] ms (ADR-164.1 §3.2)

What it means

reservationExpiryMs is an optional field; when present it is bounded to [5000, 300000] ms (5 s to 5 min) per ADR-164.1 §3.2 — the reservation/lease window must be long enough to be usable but short enough that a crashed holder cannot stall a pod for long. Values below 5 s or above 300000 ms are rejected at path '/'. The 'pod-template at /:' prefix is the caller's path formatting of the thrown message.

Source

Thrown at v3/@claude-flow/cli/src/business-pods/pod-schema.ts:256

      '/',
    );
  }
  const preferLocalExecution = requireBoolean(json, 'preferLocalExecution', '/');
  const cronSchedule = requireString(json, 'cronSchedule', '/');
  if (!CRON_RE.test(cronSchedule)) {
    throw new PodTemplateValidationError(
      'cronSchedule must be a POSIX cron expression (5 or 6 fields)',
      '/',
    );
  }
  const auditReadView = validateAuditReadView(json.auditReadView, '/auditReadView');

  let reservationExpiryMs: number | undefined;
  if (json.reservationExpiryMs !== undefined) {
    const v = requireNumber(json, 'reservationExpiryMs', '/');
    // ADR-164.1 §3.2 — bounded to [5_000, 300_000] ms.
    if (v < 5_000 || v > 300_000) {
      throw new PodTemplateValidationError(
        'reservationExpiryMs must be within [5000, 300000] ms (ADR-164.1 §3.2)',
        '/',
      );
    }
    reservationExpiryMs = v;
  }

  return {
    name,
    displayName,
    roomId,
    agents,
    allowedMcpTools,
    bench,
    piiPolicy: piiPolicy as PiiPolicy,
    budgetUsdMonthly,
    budgetUsdPerRun,
    preferLocalExecution,

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Pick a value in [5000, 300000], e.g. 30000 (30 s) or 60000 (60 s)
  2. If you wanted 'no expiry', that is not supported — use the 300000 ms ceiling instead
  3. Double-check the unit is milliseconds (30 seconds = 30000, not 30)

Example fix

// before
"reservationExpiryMs": 10
// after
"reservationExpiryMs": 30000
Defensive patterns

Strategy: validation

Validate before calling

if (json.reservationExpiryMs !== undefined) {
  const v = json.reservationExpiryMs;
  if (typeof v !== 'number' || v < 5000 || v > 300000) {
    json.reservationExpiryMs = 30000; // safe default inside ADR-164.1 §3.2 bounds
  }
}

Type guard

function isValidReservationExpiry(v: unknown): boolean {
  return v === undefined || (typeof v === 'number' && v >= 5000 && v <= 300000);
}

Try / catch

try { validatePodTemplate(json); } catch (err) {
  if (err instanceof PodTemplateValidationError && /reservationExpiryMs/.test(err.message)) {
    // clamp into [5000, 300000] ms or delete the field to use the default
  }
}

Prevention

When it happens

Trigger: A template with reservationExpiryMs: 1000 (1 s — too short) or 3600000 (1 h — too long). Omitting the field entirely is fine and falls back to the default; only explicitly supplied out-of-range values throw.

Common situations: Porting lease timeouts tuned for other systems; unit confusion — expressing seconds instead of milliseconds (10 meaning '10 seconds' reads as 10 ms); attempting to 'disable' expiry with a huge value like Number.MAX_SAFE_INTEGER.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/b317872e9e338e6f. Report an issue: GitHub.