{"record":{"id":"3f7bdc2a4eb9a9a6","repo":"sqlalchemy/alembic","slug":"character-s-s-not-allowed-in-revision-identifi","errorCode":null,"errorMessage":"Character(s) '%s' not allowed in revision identifier '%s'","messagePattern":"Character\\(s\\) '(.+?)' not allowed in revision identifier '(.+?)'","errorType":"validation","errorClass":"RevisionError","httpStatus":null,"severity":"error","filePath":"alembic/script/revision.py","lineNumber":1579,"sourceCode":"    From a migration standpoint, these dependencies are added to the\n    down_revision to form the full iteration.  However, the separation\n    of down_revision from \"dependencies\" is to assist in navigating\n    a history that contains many branches, typically a multi-root scenario.\n\n    \"\"\"\n\n    branch_labels: set[str] = None  # type: ignore[assignment]\n    \"\"\"Optional string/tuple of symbolic names to apply to this\n    revision's branch\"\"\"\n\n    _resolved_dependencies: tuple[str, ...]\n    _normalized_resolved_dependencies: tuple[str, ...]\n\n    @classmethod\n    def verify_rev_id(cls, revision: str) -> None:\n        illegal_chars = set(revision).intersection(_revision_illegal_chars)\n        if illegal_chars:\n            raise RevisionError(\n                \"Character(s) '%s' not allowed in revision identifier '%s'\"\n                % (\", \".join(sorted(illegal_chars)), revision)\n            )\n\n    def __init__(\n        self,\n        revision: str,\n        down_revision: str | tuple[str, ...] | None,\n        dependencies: str | tuple[str, ...] | None = None,\n        branch_labels: str | tuple[str, ...] | None = None,\n    ) -> None:\n        if down_revision and revision in util.to_tuple(down_revision):\n            raise LoopDetected(revision)\n        elif dependencies is not None and revision in util.to_tuple(\n            dependencies\n        ):\n            raise DependencyLoopDetected(revision)\n","sourceCodeStart":1561,"sourceCodeEnd":1597,"githubUrl":"https://github.com/sqlalchemy/alembic/blob/5551b5d35f985c99cb8f1af2b3c526b050e4c059/alembic/script/revision.py#L1561-L1597","documentation":"Raised by Revision.verify_rev_id when a revision identifier contains characters from the illegal set ['@', '-', '+', ':']. These characters are reserved by Alembic's revision syntax: '@' separates branch labels, '+'/'-' express relative offsets, and ':' separates revision ranges. Allowing them in identifiers would make revision references ambiguous.","triggerScenarios":"Creating or registering a Revision (or Script) whose 'revision' string contains '@', '-', '+', or ':'. Happens at revision.py:1577-1582 during Revision.__init__ via verify_rev_id. Common when a custom revision id generator or hand-edited migration script uses an illegal character.","commonSituations":"Using a UUID with dashes as revision id (e.g. '550e8400-e29b-41d4-a716-446655440000'). Setting revision = 'feat@v2' thinking '@' is a valid separator. Using '+1' or ':3' style strings. Custom revision id callbacks that don't sanitize characters.","solutions":["Replace dashes in revision ids with underscores: use '550e8400_e29b_41d4_a716_446655440000' instead of dashes.","Remove any '@', '+', ':' characters from the revision identifier in the migration script's revision = '...' line.","If using a custom revision id generator (revision_environment=True or script.py.x), strip or replace illegal characters before assignment.","Use Alembic's default rev_id generator which produces safe hex strings."],"exampleFix":"# before\nrevision = '550e8400-e29b-41d4-a716-446655440000'\ndown_revision = '3f4d2a1b'\n\n# after\nrevision = '550e8400e29b41d4a716446655440000'\ndown_revision = '3f4d2a1b'","handlingStrategy":"validation","validationCode":"_ILLEGAL = {'@', '-', '+', ':'}\n\ndef is_valid_rev_id(rev_id):\n    return bool(rev_id) and not set(rev_id).intersection(_ILLEGAL)\n\ndef sanitize_rev_id(rev_id):\n    for ch in _ILLEGAL:\n        rev_id = rev_id.replace(ch, '_')\n    return rev_id","typeGuard":"def is_valid_revision_id(rev_id: str) -> bool:\n    \"\"\"Type guard: True if rev_id contains no illegal characters.\"\"\"\n    illegal = {'@', '-', '+', ':'}\n    return isinstance(rev_id, str) and len(rev_id) > 0 and not set(rev_id) & illegal","tryCatchPattern":null,"preventionTips":["Use Alembic's default rev_id generator (hex strings) to avoid illegal characters entirely.","If using custom rev_id callbacks, sanitize by replacing '@', '-', '+', ':' with underscores.","Add a pre-commit hook that validates revision ids in migration files."],"tags":["alembic","migrations","revision-id","validation","syntax"],"backgroundTag":null,"analyzedSha":"5551b5d35f985c99cb8f1af2b3c526b050e4c059","analyzedAt":"2026-08-11T01:38:46.612Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}