abhigyanpatwari/GitNexus · error

"params" must be a plain object with scalar values (string/n

Error message

"params" must be a plain object with scalar values (string/number/boolean/null)

What it means

HTTP 400 from POST /api/query when the optional params field is present but fails isValidQueryParams: it must be a plain object whose values are scalars only (string, number, boolean, or null). Because the cypher is executed via executePrepared against LadybugDB, allowing arrays, nested objects, or other types would either break prepared-statement binding or smuggle structure into the query layer, so the API validates before opening the read-only connection.

Source

Thrown at gitnexus/src/server/api.ts:656

      res.status(statusFromError(err)).json({ error: err.message || 'Failed to read file' });
    }
  }
};

export const handleQueryRequest = async (
  req: express.Request,
  res: express.Response,
  resolveRepo: (repoName?: string) => Promise<{ storagePath: string } | undefined>,
): Promise<void> => {
  try {
    const cypher = req.body.cypher as string;
    if (!cypher) {
      res.status(400).json({ error: 'Missing "cypher" in request body' });
      return;
    }
    const queryParams = req.body.params;
    if (queryParams !== undefined && !isValidQueryParams(queryParams)) {
      res.status(400).json({
        error: '"params" must be a plain object with scalar values (string/number/boolean/null)',
      });
      return;
    }

    const entry = await resolveRepo(requestedRepo(req));
    if (!entry) {
      res.status(404).json({ error: 'Repository not found' });
      return;
    }
    const lbugPath = path.join(entry.storagePath, 'lbug');
    const result = await withLbugDb(lbugPath, () => executePrepared(cypher, queryParams ?? {}), {
      readOnly: true,
    });
    res.json({ result });
  } catch (err: any) {
    if (isReadOnlyDbError(err)) {
      res.status(403).json({ error: 'Write queries are not allowed via the HTTP API' });

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Flatten params to scalars: {"params": {"name": "foo", "limit": 10, "flag": true}} — one level, scalar values only
  2. Omit params entirely when the query has none
  3. Stringify or JSON-encode nested structures before sending if the query needs them, and decode in Cypher
  4. Validate client-side with a type guard mirroring the server's rule before POSTing

Example fix

// before
body: JSON.stringify({ cypher, params: { filters: { name: 'x' } } }) // nested → 400

// after
body: JSON.stringify({ cypher, params: { name: 'x' } }) // scalar values only
Defensive patterns

Strategy: type-guard

Validate before calling

// Flatten params to scalar values before sending.
function flattenParams(obj: Record<string, unknown>): Record<string, string | number | boolean | null> {
  const out: Record<string, string | number | boolean | null> = {};
  for (const [k, v] of Object.entries(obj)) {
    out[k] = (v !== null && typeof v === 'object') ? JSON.stringify(v) : (v as string | number | boolean | null);
  }
  return out;
}

Type guard

type Scalar = string | number | boolean | null;
function isValidQueryParams(v: unknown): v is Record<string, Scalar> {
  if (typeof v !== 'object' || v === null || Array.isArray(v)) return false;
  return Object.values(v).every((x) => x === null || ['string', 'number', 'boolean'].includes(typeof x));
}

Prevention

When it happens

Trigger: Sending params as an array [1,2,3]; nesting objects {filter:{name:'x'}}; passing a serialized string 'name=x'; sending true/false boxed weirdly via JSON revival; passing a params value that is an array of one element expecting it to bind as a scalar.

Common situations: Porting a query client from a Neo4j driver that accepts array params; frontend state objects being passed wholesale as params; Date objects (they serialize to strings via JSON, but hand-built clients may pass them as objects); users trying to sneak full filter objects through the HTTP API.

Related errors


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