abhigyanpatwari/GitNexus · error

"branch" must be a string

Error message

"branch" must be a string

What it means

A 400 validation response from the analyze endpoint: the optional `branch` field in the request body must be a string (an index-branch selector validated like the CLI's --branch flag). The server rejects non-string values before the ref can reach git.

Solutions

  1. Send branch as a JSON string: { "branch": "main" }
  2. Omit the branch field entirely instead of sending null
  3. Quote numeric-looking branch names in your request-building code or config
  4. Use the same branch value that works with the CLI's --branch flag

Example fix

// before
{ "repo": "my-repo", "branch": 123 }
// 400 { error: '"branch" must be a string' }

// after
{ "repo": "my-repo", "branch": "123-release" }
Defensive patterns

Strategy: validation

Validate before calling

const isBranchOk = typeof branch === 'string' && branch.length > 0;
if (branch !== undefined && !isBranchOk) throw new Error('branch must be a string');

Type guard

const isString = (v: unknown): v is string => typeof v === 'string';
if (branch !== undefined && !isString(branch)) throw new TypeError('"branch" must be a string');

Prevention

When it happens

Trigger: POST to the analyze route with body { branch: 123 }, { branch: true }, { branch: null } (treated as non-undefined), or any other non-string type.

Common situations: Happens when clients build the JSON body dynamically (e.g. number-like branch names '123' parsed as numbers by YAML/JSON tooling), or a template sends a null placeholder instead of omitting the field.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@ac9a4e9abd (2026-09-15). Data as JSON: /api/errors/8fac045b3123fb7a. Report an issue: GitHub.

Appendix: source

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

        if (
          asyncApiSpecPath !== undefined &&
          (typeof asyncApiSpecPath !== 'string' || asyncApiSpecPath.trim().length === 0)
        ) {
          res.status(400).json({ error: '"asyncApiSpecPath" must be a non-empty string' });
          return;
        }

        if (!repoUrl && !repoLocalPath) {
          res.status(400).json({ error: 'Provide "url" (git URL) or "path" (local path)' });
          return;
        }

        // Branch: optional index-branch selector, validated with the same rules
        // as the CLI's `--branch` so both entry points accept the same refs.
        // Rejecting here (rather than letting the clone fail) keeps a malformed
        // ref from ever reaching `git`.
        if (repoBranch !== undefined && typeof repoBranch !== 'string') {
          res.status(400).json({ error: '"branch" must be a string' });
          return;
        }
        let analyzeBranch: string | undefined;
        if (repoBranch !== undefined) {
          try {
            analyzeBranch = validateBranchName(repoBranch, '"branch"');
          } catch (err) {
            if (err instanceof InvalidBranchError) {
              res.status(400).json({ error: err.message });
              return;
            }
            throw err;
          }
        }

        // Token: optional, restricted charset to prevent header smuggling
        // (CRLF), bound length, and bound to github.com (see validateAnalyzeToken).
        const tokenError = validateAnalyzeToken(repoToken, repoUrl);

View on GitHub (pinned to ac9a4e9abd)