abhigyanpatwari/GitNexus · error · Error

Invalid group name "${name}". Names must start with a letter

Error message

Invalid group name "${name}". Names must start with a letter or digit and contain only [a-zA-Z0-9_-].

What it means

Thrown by validateGroupName (storage.ts) when the proposed group name fails GROUP_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/. Names must start with a letter or digit and contain only letters, digits, underscores, or hyphens. This runs inside getGroupDir and createGroupDir, so it guards every path that constructs a group directory — primarily to prevent path traversal and invalid filesystem characters, not merely aesthetics.

Source

Thrown at gitnexus/src/core/group/storage.ts:22

import * as os from 'node:os';
import type { ContractRegistry } from './types.js';
import { writeFileAtomic } from '../../storage/fs-atomic.js';

const CONTRACTS_FILE = 'contracts.json';

export function getDefaultGitnexusDir(): string {
  return process.env.GITNEXUS_HOME || path.join(os.homedir(), '.gitnexus');
}

export function getGroupsBaseDir(gitnexusDir?: string): string {
  return path.join(gitnexusDir || getDefaultGitnexusDir(), 'groups');
}

const GROUP_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;

export function validateGroupName(name: string): void {
  if (!GROUP_NAME_RE.test(name)) {
    throw new Error(
      `Invalid group name "${name}". Names must start with a letter or digit and contain only [a-zA-Z0-9_-].`,
    );
  }
}

export function getGroupDir(gitnexusDir: string, groupName: string): string {
  validateGroupName(groupName);
  return path.join(gitnexusDir, 'groups', groupName);
}

export async function writeContractRegistry(
  groupDir: string,
  registry: ContractRegistry,
): Promise<void> {
  await writeFileAtomic(path.join(groupDir, CONTRACTS_FILE), JSON.stringify(registry, null, 2));
}

export async function readContractRegistry(groupDir: string): Promise<ContractRegistry | null> {

View on GitHub (pinned to d540b00184)

Solutions

  1. Use only [a-zA-Z0-9_-], starting with a letter or digit — e.g. 'team-project', not 'team.project'.
  2. If the name comes from user input, sanitize it (replace disallowed chars with '-' and strip leading non-alphanumerics) before calling validateGroupName.
  3. Avoid '.' especially: it would let a crafted name escape the groups/ base directory.

Example fix

// before — dot and slash trigger the guard
await createGroupDir(home, 'team/sub.project', false);

// after — sanitized, regex-valid name
await createGroupDir(home, 'team-sub-project', false);
Defensive patterns

Strategy: validation

Validate before calling

const GROUP_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
function isValidGroupName(name: string): boolean {
  return GROUP_NAME_RE.test(name);
}
// sanitize untrusted input before calling createGroupDir:
function sanitizeGroupName(raw: string): string {
  const cleaned = raw.replace(/[^a-zA-Z0-9_-]/g, '-').replace(/^[^a-zA-Z0-9]+/, '');
  return isValidGroupName(cleaned) ? cleaned : 'group';
}

Type guard

function isValidGroupName(name: unknown): name is string {
  return typeof name === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(name);
}

Prevention

When it happens

Trigger: Calling createGroupDir or getGroupDir with a name containing '.', '/', spaces, leading hyphen/underscore, or any non-[a-zA-Z0-9_-] character; an empty string; a name starting with '-' (looks like a CLI flag and is regex-invalid).

Common situations: User passes a dotted name like 'team.project' (dot not allowed); a name with a slash implying a nested path (traversal risk); a name starting with '_' or '-'; a name derived from untrusted input that wasn't sanitized.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/a65359d36c198a3f. Report an issue: GitHub.