abhigyanpatwari/GitNexus · error

${source} must be a positive integer.

Error message

${source} must be a positive integer.

What it means

Thrown by parsePositiveInteger in the MCP output-budget module when a token budget value is not a usable positive integer. It guards the maxTokens argument accepted by the budgeted MCP tools (query, context, impact) and the GITNEXUS_MCP_DEFAULT_MAX_TOKENS environment variable. A value passes only if it is a number that is a safe integer > 0, or a string matching ^[1-9]\d*$ that parses to a safe integer (whitespace is trimmed first). Anything else — 0, negatives, floats, '1e3', 'abc', '007', values above Number.MAX_SAFE_INTEGER — throws immediately, before any tool backend runs.

Source

Thrown at gitnexus/src/mcp/output-budget.ts:12

const BUDGETED_TOOLS = new Set(['query', 'context', 'impact']);

export const MCP_TOKEN_ESTIMATE_BYTES = 4;
export const MCP_TRUNCATION_MARKER = '\n…';

function parsePositiveInteger(value: unknown, source: string): number {
  if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) return value;
  if (typeof value === 'string' && /^[1-9]\d*$/.test(value.trim())) {
    const parsed = Number(value.trim());
    if (Number.isSafeInteger(parsed)) return parsed;
  }
  throw new Error(`${source} must be a positive integer.`);
}

export function resolveMcpMaxTokens(
  toolName: string,
  args: Record<string, unknown> | undefined,
  env: NodeJS.ProcessEnv = process.env,
): number | undefined {
  if (!BUDGETED_TOOLS.has(toolName)) return undefined;
  if (args?.maxTokens !== undefined) return parsePositiveInteger(args.maxTokens, 'maxTokens');

  const configured = env.GITNEXUS_MCP_DEFAULT_MAX_TOKENS;
  if (configured === undefined || configured.trim() === '') return undefined;
  return parsePositiveInteger(configured, 'GITNEXUS_MCP_DEFAULT_MAX_TOKENS');
}

function utf8Prefix(text: string, maxBytes: number): string {
  let bytes = 0;
  const codePoints: string[] = [];

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Pass maxTokens as a plain positive integer (number or digit-only string), e.g. 4096 — no decimals, no scientific notation, no leading zeros, and at most Number.MAX_SAFE_INTEGER.
  2. If you do not want a budget, omit the maxTokens argument entirely instead of passing 0.
  3. Check and fix GITNEXUS_MCP_DEFAULT_MAX_TOKENS in the server environment (unset it or set it to a plain positive integer like 4096).
  4. If the value comes from another system, coerce and validate it client-side before the MCP call (Number.isSafeInteger check).

Example fix

// before
const out = await callTool('query', { search_query: 'auth flow', maxTokens: tokens }); // tokens = 0 or 4096.5

// after
const tokens = Number.isSafeInteger(raw) && raw > 0 ? raw : 4096;
const out = await callTool('query', { search_query: 'auth flow', maxTokens: tokens });
Defensive patterns

Strategy: validation

Validate before calling

function toMaxTokens(v: unknown): number | undefined {
  if (v === undefined) return undefined;
  if (typeof v === 'number' && Number.isSafeInteger(v) && v > 0) return v;
  if (typeof v === 'string' && /^[1-9]\d*$/.test(v.trim())) {
    const n = Number(v.trim());
    if (Number.isSafeInteger(n)) return n;
  }
  return undefined; // omit the arg rather than send an invalid one
}
const args = { search_query: 'auth' } as Record<string, unknown>;
const mt = toMaxTokens(userBudget);
if (mt !== undefined) args.maxTokens = mt;

Type guard

const isPositiveIntArg = (v: unknown): v is number | string =>
  (typeof v === 'number' && Number.isSafeInteger(v) && v > 0) ||
  (typeof v === 'string' && /^[1-9]\d*$/.test(v.trim()) && Number.isSafeInteger(Number(v.trim())));

Try / catch

try {
  await client.callTool({ name: 'query', arguments });
} catch (e) {
  if (e instanceof Error && /must be a positive integer/.test(e.message)) {
    // deterministic input error: fix the value, do not retry
    throw new Error(`Bad maxTokens: ${JSON.stringify(arguments['maxTokens'])}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the MCP 'query', 'context', or 'impact' tool with maxTokens: 0, maxTokens: -100, maxTokens: 5000.5, maxTokens: '1e3', or maxTokens: 'abc'. Or starting/serving MCP with env GITNEXUS_MCP_DEFAULT_MAX_TOKENS='-1', '10.5', 'true', or '1e4' while any budgeted tool is invoked without an explicit maxTokens argument.

Common situations: An AI client computes maxTokens from a model context size and passes 0 or a float (e.g. ctx.window / 1000). A .env file sets GITNEXUS_MCP_DEFAULT_MAX_TOKENS with quotes or units ('4096 tokens', '"4096"'). Copying a permissive validator that accepts '1e3' style scientific notation, which the strict ^[1-9]\d*$ regex deliberately rejects.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20). Data as JSON: /api/errors/5bb3a536d19b3e39. Report an issue: GitHub.