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
- Remove the '.lock' suffix from the branch name or the offending path component.
- Fix the generator/template that appends extensions to branch names.
- 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
- Never append file extensions to branch names in generators/templates.
- Treat '.lock' as reserved at the end of any ref path component.
- If lock semantics matter, use a different token like '-lock' or a 'lock/' prefix.
- Test generated branch names against git check-ref-format rules.
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
- err.message (InvalidBranchError rethrown as GitNexusRcError…
- : branch name contains characters not allowed in a git ref…
- : branch name is too long (max ).
- : branch name must not be empty.
- : branch name must not be "HEAD".
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)