{"record":{"id":"8f210ca6c7b7c77f","repo":"can1357/oh-my-pi","slug":"invalid-branch-slug-slug-r-expected-kebab-case","errorCode":null,"errorMessage":"invalid branch slug {slug!r}: expected kebab-case [a-z0-9-], 1-50 chars, no leading/trailing/double hyphen","messagePattern":"invalid branch slug (.+?): expected kebab-case \\[a-z0-9-\\], 1-50 chars, no leading/trailing/double hyphen","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/robomp/src/sandbox.py","lineNumber":154,"sourceCode":"    env[\"GIT_TERMINAL_PROMPT\"] = \"0\"\n    return env\n\n\ndef make_branch(*, issue_number: int, title: str, seed: str | None = None) -> str:\n    return f\"farm/{_short_hex(seed or f'{issue_number}-{title}')}/{_slug(title or f'issue-{issue_number}')}\"\n\n\n_BRANCH_SLUG_RE = re.compile(r\"^[a-z0-9]+(?:-[a-z0-9]+)*$\")\n\n\ndef validate_branch_slug(slug: object) -> str:\n    \"\"\"Return ``slug`` if it is a valid kebab-case branch slug, else raise.\n\n    Rules: 1-50 chars, only ``[a-z0-9-]``, no leading/trailing hyphen, no\n    double hyphen. Raises ``ValueError`` otherwise.\n    \"\"\"\n    if not isinstance(slug, str) or not _BRANCH_SLUG_RE.fullmatch(slug) or len(slug) > 50:\n        raise ValueError(\n            f\"invalid branch slug {slug!r}: expected kebab-case [a-z0-9-], 1-50 chars, no leading/trailing/double hyphen\"\n        )\n    return slug\n\n\ndef rename_workspace_branch(\n    workspace: Workspace,\n    new_slug: str,\n    *,\n    pr_number: int | None = None,\n    slot_uid: int | None = None,\n) -> str:\n    \"\"\"Rename the workspace's local branch to ``farm/<hex>/<new_slug>``.\n\n    The 8-hex disambiguator stays untouched; only the trailing slug after\n    the second `/` changes. Runs ``git branch -m`` inside the worktree\n    (which updates the shared refs in the pool) and mutates\n    ``workspace.branch`` in place.","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/sandbox.py#L136-L172","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Normalize the input before calling: lowercase, replace non-[a-z0-9] runs with '-', collapse '--' to '-', strip leading/trailing '-', and truncate to 50 chars.","Check the exact invalid value in the error message (it is repr-quoted) to spot the offending characters.","If the slug comes from config/env, fix the configured value to kebab-case.","If the slug is derived from user/ticket input, call validate_branch_slug yourself first and sanitize on failure."],"exampleFix":"# before\nslug = f\"feature/{ticket.title}\"  # e.g. 'feature/Fix Login Flow!'\nsandbox.execute(action, branch=slug)  # ValueError\n\n# after\nimport re\ndef to_slug(s: str) -> str:\n    s = re.sub(r\"[^a-z0-9-]+\", \"-\", s.lower()).strip(\"-\")\n    s = re.sub(r\"-{2,}\", \"-\", s)\n    return s[:50].strip(\"-\") or \"branch\"\nsandbox.execute(action, branch=to_slug(ticket.title))","handlingStrategy":"validation","validationCode":"import re\n_BRANCH_SLUG_RE = re.compile(r\"[a-z0-9]+(-[a-z0-9]+)*\")\ndef safe_slug(raw: str) -> str:\n    s = re.sub(r\"[^a-z0-9-]+\", \"-\", raw.lower()).strip(\"-\")\n    s = re.sub(r\"-{2,}\", \"-\", s)[:50].strip(\"-\")\n    if not s or not _BRANCH_SLUG_RE.fullmatch(s) or len(s) > 50:\n        raise ValueError(f\"cannot sanitize slug from {raw!r}\")\n    return s","typeGuard":"import re\n_BRANCH_SLUG_RE = re.compile(r\"[a-z0-9]+(-[a-z0-9]+)*\")\ndef is_valid_branch_slug(slug: object) -> bool:\n    return isinstance(slug, str) and bool(_BRANCH_SLUG_RE.fullmatch(slug)) and len(slug) <= 50","tryCatchPattern":"try:\n    slug = validate_branch_slug(candidate)\nexcept ValueError as e:\n    slug = sanitize_to_slug(candidate)  # lowercase, non-alnum -> '-', collapse/truncate\n    validate_branch_slug(slug)","preventionTips":["Always sanitize derived names (titles, tickets, repo names) through a kebab-case normalizer before validate_branch_slug.","Strip '#' and '/' prefixes from issue/repo references — they are not legal in slugs.","Truncate to 50 chars after joining parts, then re-strip trailing hyphens.","Add a unit test that every slug your pipeline generates passes validate_branch_slug."],"tags":["validation","naming","git","branch"],"backgroundTag":"invalid-branch-name","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}