tinyhumansai/openhuman · error

agentTeamApi: ${label} must be a positive integer

Error message

agentTeamApi: ${label} must be a positive integer

What it means

Client-side pre-flight validation in agentTeamApi. The helper assertPositiveInt rejects any explicitly-supplied pagination value that is not an integer greater than zero, before the JSON-RPC call to the core is made. It exists so bad limit/offset values fail fast in the renderer instead of surfacing as confusing core-side errors.

Source

Thrown at app/src/services/api/agentTeamApi.ts:160

interface RawRunEvent {
  runId: string;
  sequence: number;
  eventType: string;
  payload: unknown;
  timestamp: string;
}

/** Optional filters for {@link agentTeamApi.list}. Mirrors `AgentTeamListRequest`. */
interface AgentTeamListParams {
  parentThreadId?: string;
  status?: AgentTeamStatus;
  limit?: number;
  offset?: number;
}

function assertPositiveInt(value: number | undefined, label: string): void {
  if (value !== undefined && (!Number.isInteger(value) || value <= 0)) {
    throw new Error(`agentTeamApi: ${label} must be a positive integer`);
  }
}

/** Coerce a raw run-event payload into a typed message payload, defensively. */
function readMessagePayload(payload: unknown): TeamMessagePayload {
  const p = (payload ?? {}) as Record<string, unknown>;
  return {
    from: typeof p.from === 'string' ? p.from : '',
    to: typeof p.to === 'string' ? p.to : null,
    content: typeof p.content === 'string' ? p.content : '',
    visibility: typeof p.visibility === 'string' ? p.visibility : 'team',
  };
}

export const agentTeamApi = {
  /**
   * List team headers, newest first. Filters are optional; `parentThreadId`
   * scopes to one conversation, `status` to active/closed.

View on GitHub (pinned to a221052e0d)

Solutions

  1. Fix the caller to pass undefined instead of 0 when no cap is intended — undefined skips the check entirely
  2. Clamp computed pagination values before calling: limit = Math.max(1, Math.floor(limit))
  3. If 0 legitimately means 'no limit' in your UI, translate it: params.limit || undefined
  4. Trace where the non-integer originates (query param parsing, division) and round it

Example fix

// before
const teams = await agentTeamApi.list({ limit: pageEnd - pageStart }); // 0 on empty page

// after
const teams = await agentTeamApi.list({ limit: Math.max(1, pageEnd - pageStart) });
// or omit entirely when there is nothing to page:
const teams = await agentTeamApi.list(pageEnd - pageStart > 0 ? { limit: pageEnd - pageStart } : {});
Defensive patterns

Strategy: validation

Validate before calling

function safePage(v: number | undefined): number | undefined {
  if (v === undefined) return undefined;
  const n = Math.floor(v);
  return Number.isFinite(n) && n > 0 ? n : undefined;
}
// before the call:
const params = { status, limit: safePage(limit), offset: safePage(offset) };
if (limit !== undefined && params.limit === undefined) throw new Error('bad limit');

Type guard

const isPositiveInt = (v: unknown): v is number =>
  typeof v === 'number' && Number.isInteger(v) && v > 0;

Try / catch

try { await agentTeamApi.list({ limit }); }
catch (e) { if (String(e.message).includes('positive integer')) { limit = undefined; retryWithoutCap(); } else throw e; }

Prevention

When it happens

Trigger: Calling agentTeamApi.list({ limit: 0 }), list({ limit: -5 }), list({ offset: 2.5 }), or list({ limit: NaN }) — i.e. any call where limit/offset is defined but fails Number.isInteger(value) || value <= 0. Note assertPositiveInt is also reused by listMessages (line 216) with the label 'limit'.

Common situations: UI pagination math that computes limit as pageEnd - pageStart and yields 0 on an empty page; passing a float from a slider or a parsed query-string param (e.g. Number('2.5')); defaults accidentally set to 0; NaN propagation from Number(undefined).

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/2a368c2dc07510ea. Report an issue: GitHub.