tinyhumansai/openhuman · error · ValidationError

Invalid ${paramName}: ${String(value)}. Must be a positive i

Error message

Invalid ${paramName}: ${String(value)}. Must be a positive integer.

What it means

The fallback branch of validatePositiveInt(): the value is neither number nor string (boolean, null, object, array, undefined passed where not allowed), so it throws ValidationError 'Invalid <param>: <String(value)>. Must be a positive integer.' — a wrong-type failure rather than a bad-value one.

Source

Thrown at app/src/lib/mcp/validation.ts:91

 * Validate a positive integer parameter (e.g. message IDs)
 */
export function validatePositiveInt(value: unknown, paramName: string): number {
  if (typeof value === 'number') {
    if (!Number.isInteger(value) || value <= 0) {
      throw new ValidationError(`Invalid ${paramName}: ${value}. Must be a positive integer.`);
    }
    return value;
  }

  if (typeof value === 'string') {
    const intValue = Number.parseInt(value, 10);
    if (Number.isNaN(intValue) || intValue <= 0) {
      throw new ValidationError(`Invalid ${paramName}: '${value}'. Must be a positive integer.`);
    }
    return intValue;
  }

  throw new ValidationError(`Invalid ${paramName}: ${String(value)}. Must be a positive integer.`);
}

/**
 * Validate optional ID (can be undefined)
 */
export function validateOptionalId(value: unknown, paramName: string): number | string | undefined {
  if (value === undefined || value === null) {
    return undefined;
  }
  return validateId(value, paramName);
}

View on GitHub (pinned to 7491200858)

Solutions

  1. Check the value's type before the call: require typeof number (integer > 0) or numeric string.
  2. Fix argument construction — the message shows the actual value, making key-mixups visible.
  3. Add shared param types (TS interfaces) for tool payloads so the compiler catches swapped/missing fields.
  4. Convert booleans/objects upstream instead of relying on the validator's error at runtime.

Example fix

// before
await tool({ message_id: payload.id ?? null });

// after
const id = payload.id;
if (typeof id !== 'number' || !Number.isInteger(id) || id < 1) {
  throw new TypeError('payload.id must be a positive integer');
}
await tool({ message_id: id });
Defensive patterns

Strategy: type-guard

Validate before calling

function asPositiveInt(v: unknown): number | undefined {
  if (typeof v === 'number' && Number.isInteger(v) && v > 0) return v;
  const m = typeof v === 'string' ? /^\d+$/.exec(v) : null;
  return m && Number(m[0]) > 0 ? Number(m[0]) : undefined;
}
if (asPositiveInt(payload.id) === undefined) throw new TypeError('id must be a positive integer');

Type guard

function isPositiveIntLike(v: unknown): v is number | string {
  if (typeof v === 'number') return Number.isInteger(v) && v > 0;
  if (typeof v === 'string') return /^\d+$/.test(v) && Number(v) > 0;
  return false;
}

Try / catch

try {
  validatePositiveInt(value, paramName);
} catch (err) {
  if (err instanceof ValidationError) {
    // wrong type (null/bool/object) — fix the caller's payload construction
    logPayloadShape(value);
    return badRequest(err.message);
  }
  throw err;
}

Prevention

When it happens

Trigger: Passing null, true/false, an object ({id: 3} instead of 3), or an array ([3]) as the parameter. Typical when callers forward unvalidated JSON payloads or destructure with the wrong key.

Common situations: Tool params deserialized from untyped JSON where a field is sometimes an object; boolean flags accidentally passed in the ID slot due to argument-order mistakes; nulls from optional chaining defaults (value ?? null).

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/8ecbf149e596a71f. Report an issue: GitHub.