can1357/oh-my-pi · error · ValueError

invalid branch slug {slug!r}: expected kebab-case [a-z0-9-],

Error message

invalid branch slug {slug!r}: expected kebab-case [a-z0-9-], 1-50 chars, no leading/trailing/double hyphen

What it means

ValueError raised by validate_branch_slug when a branch slug is not valid kebab-case: it must be a string of 1-50 chars matching [a-z0-9-], with no leading/trailing hyphen and no double hyphen. It guards sandbox/workspace branch creation (execute) and branch renames (rename_workspace_branch) from producing invalid git branch names.

Source

Thrown at python/robomp/src/sandbox.py:154

    env["GIT_TERMINAL_PROMPT"] = "0"
    return env


def make_branch(*, issue_number: int, title: str, seed: str | None = None) -> str:
    return f"farm/{_short_hex(seed or f'{issue_number}-{title}')}/{_slug(title or f'issue-{issue_number}')}"


_BRANCH_SLUG_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")


def validate_branch_slug(slug: object) -> str:
    """Return ``slug`` if it is a valid kebab-case branch slug, else raise.

    Rules: 1-50 chars, only ``[a-z0-9-]``, no leading/trailing hyphen, no
    double hyphen. Raises ``ValueError`` otherwise.
    """
    if not isinstance(slug, str) or not _BRANCH_SLUG_RE.fullmatch(slug) or len(slug) > 50:
        raise ValueError(
            f"invalid branch slug {slug!r}: expected kebab-case [a-z0-9-], 1-50 chars, no leading/trailing/double hyphen"
        )
    return slug


def rename_workspace_branch(
    workspace: Workspace,
    new_slug: str,
    *,
    pr_number: int | None = None,
    slot_uid: int | None = None,
) -> str:
    """Rename the workspace's local branch to ``farm/<hex>/<new_slug>``.

    The 8-hex disambiguator stays untouched; only the trailing slug after
    the second `/` changes. Runs ``git branch -m`` inside the worktree
    (which updates the shared refs in the pool) and mutates
    ``workspace.branch`` in place.

View on GitHub (pinned to 9690622007)

Solutions

  1. Normalize the input before calling: lowercase, replace non-[a-z0-9] runs with '-', collapse '--' to '-', strip leading/trailing '-', and truncate to 50 chars.
  2. Check the exact invalid value in the error message (it is repr-quoted) to spot the offending characters.
  3. If the slug comes from config/env, fix the configured value to kebab-case.
  4. If the slug is derived from user/ticket input, call validate_branch_slug yourself first and sanitize on failure.

Example fix

# before
slug = f"feature/{ticket.title}"  # e.g. 'feature/Fix Login Flow!'
sandbox.execute(action, branch=slug)  # ValueError

# after
import re
def to_slug(s: str) -> str:
    s = re.sub(r"[^a-z0-9-]+", "-", s.lower()).strip("-")
    s = re.sub(r"-{2,}", "-", s)
    return s[:50].strip("-") or "branch"
sandbox.execute(action, branch=to_slug(ticket.title))
Defensive patterns

Strategy: validation

Validate before calling

import re
_BRANCH_SLUG_RE = re.compile(r"[a-z0-9]+(-[a-z0-9]+)*")
def safe_slug(raw: str) -> str:
    s = re.sub(r"[^a-z0-9-]+", "-", raw.lower()).strip("-")
    s = re.sub(r"-{2,}", "-", s)[:50].strip("-")
    if not s or not _BRANCH_SLUG_RE.fullmatch(s) or len(s) > 50:
        raise ValueError(f"cannot sanitize slug from {raw!r}")
    return s

Type guard

import re
_BRANCH_SLUG_RE = re.compile(r"[a-z0-9]+(-[a-z0-9]+)*")
def is_valid_branch_slug(slug: object) -> bool:
    return isinstance(slug, str) and bool(_BRANCH_SLUG_RE.fullmatch(slug)) and len(slug) <= 50

Try / catch

try:
    slug = validate_branch_slug(candidate)
except ValueError as e:
    slug = sanitize_to_slug(candidate)  # lowercase, non-alnum -> '-', collapse/truncate
    validate_branch_slug(slug)

Prevention

When it happens

Trigger: Passing a slug containing uppercase letters, underscores, spaces, dots, or slashes; an empty string; a string over 50 chars; a slug starting/ending with '-'; or one containing '--'. Also raised when a non-string (None, int) reaches the validator.

Common situations: Deriving a slug from a task title or ticket name without normalizing (spaces/underscores/uppercase left in); interpolating an issue number prefix like '#123' or a repo name with '/'; truncating a long title to over 50 chars including separators; passing None when the branch name is missing from config.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/8f210ca6c7b7c77f. Report an issue: GitHub.