{"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":"exception","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/44fb3450330204b222ff05135e1fbbbdb28c44db/alembic/script/revision.py#L1561-L1597","documentation":"Raised by Revision.verify_rev_id (revision.py:1579) when the revision identifier string contains any character from _revision_illegal_chars, which is ['@', '-', '+', ':']. These characters are reserved: '@' selects branch labels, '+'/'-' express relative offsets, and ':' is used in resource/branch syntax. A revision id containing them would be ambiguous to the parser, so it is rejected at construction time.","triggerScenarios":"Setting revision = 'rev-001' or '2024-01-15' or 'feat@2' in a migration file's revision = '...' line; programmatically building a Revision with an id that includes a hyphen (very common with date-formatted ids), plus sign, at-sign, or colon.","commonSituations":"Teams adopting ISO-date revision ids like '20240115_1200' is fine, but '2024-01-15' trips the hyphen check; copy-pasting a git branch name containing '/' is fine but one with '@' is not; autogenerated ids from external tooling that embeds version numbers with dashes.","solutions":["Replace illegal characters in the revision id: use underscores or nothing (e.g. '20240115' instead of '2024-01-15').","If you already created migration files with bad ids, rename the revision AND every down_revision that references it across all files.","Keep revision ids as pure hex (the default from alembic.revision) or alnum+underscore tokens.","Run `alembic history` after fixing to confirm the graph still resolves."],"exampleFix":"# before\nrevision = '2024-01-15-init'\ndown_revision = '2024-01-10'\n# after\nrevision = '20240115_init'\ndown_revision = '20240110'","handlingStrategy":"validation","validationCode":"from alembic.script.revision import _revision_illegal_chars\n\ndef validate_rev_id(rev_id: str) -> None:\n    bad = set(rev_id).intersection(_revision_illegal_chars)\n    if bad:\n        raise ValueError(f'Illegal chars {sorted(bad)} in revision id {rev_id!r}; use [A-Za-z0-9_]')\n\nvalidate_rev_id(my_new_revision_id)","typeGuard":"import re\ndef is_valid_rev_id(rev_id: str) -> bool:\n    return isinstance(rev_id, str) and bool(re.fullmatch(r'[A-Za-z0-9_]+', rev_id))","tryCatchPattern":"from alembic.script.revision import RevisionError\ntry:\n    Revision.verify_rev_id(proposed_id)\nexcept RevisionError as e:\n    proposed_id = re.sub(r'[@\\-+:]', '_', proposed_id)\n    Revision.verify_rev_id(proposed_id)","preventionTips":["Use the default alembic-generated hex ids or alnum+underscore tokens.","Avoid date formats with hyphens; use YYYYMMDD or underscores.","Add a pre-commit hook that validates revision ids in new migration files."],"tags":["alembic","migration","revision-id","validation","naming"],"analyzedSha":"44fb3450330204b222ff05135e1fbbbdb28c44db","analyzedAt":"2026-08-04T19:57:10.248Z","schemaVersion":2}