nexu-io/open-design · error

must be a positive integer

Error message

${field} must be a positive integer

What it means

optionalPositiveInteger (refresh.ts:518-522) validates numeric options like git.summary's input.maxCommits. It throws if the value is present but is not a safe integer, not a number type, or is less than 1. Valid values are clamped to maxValue (e.g. 50 for maxCommits).

Solutions

  1. Omit maxCommits to use the default (10), or set it to an integer between 1 and 50.
  2. Coerce/parseInt user input and reject values < 1 before refresh.
  3. If you need 'no commits', leave the field unset rather than passing 0.

Example fix

// before
const input = { maxCommits: 0 };
// throws `input.maxCommits must be a positive integer`

// after: omit or use a valid positive integer
const input = { maxCommits: 10 };
Defensive patterns

Strategy: validation

Validate before calling

function optionalPositiveInteger(v: unknown, field: string, def: number, max: number): number {
  if (v === undefined) return def;
  if (typeof v !== 'number' || !Number.isSafeInteger(v) || v < 1) {
    throw new Error(`${field} must be a positive integer`);
  }
  return Math.min(v, max);
}

Type guard

function isPositiveInteger(v: unknown): v is number {
  return typeof v === 'number' && Number.isSafeInteger(v) && v >= 1;
}

Prevention

When it happens

Trigger: input.maxCommits is 0, negative, a float (e.g. 2.5), NaN, or a non-number (string/object).

Common situations: User-supplied maxCommits of 0 meaning 'none'; a float passed by mistake; a string like '10' that was not coerced; a config default of -1 used as a sentinel.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/337dacc2e9570df4. Report an issue: GitHub.

Appendix: source

Thrown at apps/daemon/src/live-artifacts/refresh.ts:520

        });
    deepMergeBoundedJsonObject(dataJson, mapped);
    if (source.toolName === 'public_github_repository_metric') {
      applyLegacyGithubRepositoryMetricCompat(dataJson, options.documentOutput.output);
    }
  }

  return { dataJson: asBoundedRefreshOutput(dataJson) };
}

function optionalString(value: BoundedJsonValue | undefined, field: string): string | undefined {
  if (value === undefined) return undefined;
  if (typeof value !== 'string') throw new Error(`${field} must be a string`);
  return value;
}

function optionalPositiveInteger(value: BoundedJsonValue | undefined, field: string, defaultValue: number, maxValue: number): number {
  if (value === undefined) return defaultValue;
  if (!Number.isSafeInteger(value) || typeof value !== 'number' || value < 1) throw new Error(`${field} must be a positive integer`);
  return Math.min(value, maxValue);
}

function selectJsonPath(input: ProjectFilesReadJsonInput): string {
  const rawPath = optionalString(input.path, 'input.path') ?? optionalString(input.file, 'input.file') ?? optionalString(input.name, 'input.name');
  if (rawPath === undefined) throw new Error('project_files.read_json requires input.path');
  return validateProjectPath(rawPath);
}

function compactTextPreview(text: string, query: string | undefined): string {
  const normalized = text.replace(/\s+/g, ' ').trim();
  if (normalized.length <= 240) return normalized;
  if (query === undefined || query.trim().length === 0) return `${normalized.slice(0, 240)}…`;
  const index = normalized.toLowerCase().indexOf(query.toLowerCase());
  if (index < 0) return `${normalized.slice(0, 240)}…`;
  const start = Math.max(0, index - 80);
  return `${start > 0 ? '…' : ''}${normalized.slice(start, start + 240)}…`;
}

View on GitHub (pinned to 5be4028344)