abhigyanpatwari/GitNexus · error · InvalidBranchError

: branch name must not end with ".lock".

Error message

${source}: branch name must not end with ".lock".

What it means

validateBranchName rejects any branch whose name — or any '/'-separated path component — ends with '.lock', matching git's own ref-format rule (a '.lock' suffix collides with git's ref lock files, so git can never create such a ref). The library throws early so the name fails here instead of inside the git subprocess.

Solutions

  1. Remove the '.lock' suffix from the branch name or the offending path component.
  2. Fix the generator/template that appends extensions to branch names.
  3. If '.lock' is semantically meaningful, encode it differently, e.g. 'feature-lock' or 'lock/feature'.

Example fix

// before
const branch = `wip/${file}.lock`; // "wip/index.lock"
validateBranchName(branch);
// after
const branch = `wip/${file.replace(/\.lock$/, "")}-lock`; // "wip/index-lock"
validateBranchName(branch);
Defensive patterns

Strategy: validation

Validate before calling

function hasLockComponent(name) {
  return typeof name === "string" && name.split("/").some(p => p.endsWith(".lock"));
}
if (hasLockComponent(branch)) throw new Error("branch components must not end with '.lock'");

Type guard

function isLockFreeBranch(v: unknown): v is string {
  return typeof v === "string" && !v.split("/").some(p => p.endsWith(".lock"));
}

Try / catch

try {
  validateBranchName(branch, "generator");
} catch (e) {
  if (e instanceof InvalidBranchError && e.message.includes(".lock")) {
    branch = branch.replace(/(\.lock)(?=\/|$)/g, "-lock");
    validateBranchName(branch, "generator");
  } else throw e;
}

Prevention

When it happens

Trigger: Calling validateBranchName with 'feature.lock' or a component ending in .lock like 'wip/experiment.lock'; constructing names by appending extensions programmatically.

Common situations: Template or code-generator bugs that append '.lock' or a file extension to branch names; reusing lockfile-style identifiers as branch names; accidental concatenation of a filename with a branch prefix.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at gitnexus/src/core/git-ref.ts:97

  // through as a "branch" (#3199 review, defense in depth).
  if (trimmed.startsWith('+')) {
    throw new InvalidBranchError(`${source}: branch name must not start with "+".`);
  }
  // The symbolic ref HEAD (case-sensitive). A repo can have a branch named
  // `head`; git itself treats only `HEAD` as the current-commit alias.
  if (trimmed === 'HEAD') {
    throw new InvalidBranchError(`${source}: branch name must not be "HEAD".`);
  }
  if (trimmed.includes('..')) {
    throw new InvalidBranchError(`${source}: branch name must not contain "..".`);
  }
  // The remaining `git check-ref-format` rules. Without these the validator
  // accepted refs git itself refuses (`feature.lock`, `/feature`, `feature/`,
  // `feature//next`, `@`, `.hidden`), so the failure surfaced later from the
  // git subprocess instead of here. No real branch can violate them — git
  // could not have created one — so nothing that works today starts failing.
  if (trimmed.endsWith('.lock') || trimmed.split('/').some((part) => part.endsWith('.lock'))) {
    throw new InvalidBranchError(`${source}: branch name must not end with ".lock".`);
  }
  if (trimmed.startsWith('/') || trimmed.endsWith('/')) {
    throw new InvalidBranchError(`${source}: branch name must not start or end with "/".`);
  }
  if (trimmed.includes('//')) {
    throw new InvalidBranchError(`${source}: branch name must not contain consecutive slashes.`);
  }
  if (trimmed === '@') {
    throw new InvalidBranchError(`${source}: branch name must not be the single character "@".`);
  }
  if (trimmed.includes('@{')) {
    throw new InvalidBranchError(`${source}: branch name must not contain "@{".`);
  }
  if (trimmed.endsWith('.') || trimmed.split('/').some((part) => part.startsWith('.'))) {
    throw new InvalidBranchError(
      `${source}: branch name must not end with "." or have a path component starting with ".".`,
    );
  }

View on GitHub (pinned to ac9a4e9abd)