nexu-io/open-design · error
${field} 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).
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)
Solutions
- Omit maxCommits to use the default (10), or set it to an integer between 1 and 50.
- Coerce/parseInt user input and reject values < 1 before refresh.
- 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
- Omit numeric options to use defaults rather than passing 0 as a sentinel.
- Coerce with Math.trunc/parseInt and validate >= 1 at the input boundary.
- Document the maxValue clamp (e.g. maxCommits is clamped to 50) for tool inputs.
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
- ${field} must be a string
- connector refresh output must be a JSON object
- refresh source ${source.type} is not supported yet
- No refresh source is available yet.
- Refresh is disabled for this artifact source.
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/337dacc2e9570df4.
Report an issue: GitHub.