pytest-dev/pytest · error · SyntaxError

unexpected character

Error message

unexpected character "{input[pos]}"

What it means

A SyntaxError raised by the marker-expression tokenizer when the current character matches none of the recognized token starts (whitespace, parens, =, comma, quote, or the identifier/number regex). The unexpected character and its 1-based column are reported. It is the catch-all lexical error for anything the grammar cannot start to tokenize.

Solutions

  1. Read the reported character and column; remove or replace it.
  2. Restrict marker expressions to identifiers, `and`, `or`, `not`, parentheses, and (for kwargs) `name=value` literals.
  3. If you need richer filtering, write a conftest hook (pytest_collection_modifyitems) instead of a CLI expression.

Example fix

# before
pytest -m '@smoke'
# after
pytest -m 'smoke'
Defensive patterns

Strategy: validation

Validate before calling

import re
VALID_EXPR_CHARS = re.compile(r"^[\w\s()=,.'\"]+$")
def looks_like_marker_expr(s: str) -> bool:
    return bool(VALID_EXPR_CHARS.match(s))

Try / catch

try:
    Expression.compile(expr)
except SyntaxError as e:
    # surface the unexpected character to the user
    ...

Prevention

When it happens

Trigger: Expressions containing characters like `@`, `#`, `$`, `%`, `;`, or unicode symbols that are not part of identifier/number/keyword grammar. For example `-m '@smoke'`, `-k 'test_##'`, or `-m 'a;b'`.

Common situations: Pasting a Python expression into -m expecting full Python syntax; using decorator-style or comment characters; locale/keyboard producing unexpected symbols.

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/de9a487940d93909. Report an issue: GitHub.

Appendix: source

Thrown at src/_pytest/mark/expression.py:126

                        (FILE_NAME, 1, pos + backslash_pos + 1, input),
                    )
                yield Token(TokenType.STRING, value, pos)
                pos += len(value)
            else:
                match = re.match(r"(:?\w|:|\+|-|\.|\[|\]|\\|/)+", input[pos:])
                if match:
                    value = match.group(0)
                    if value == "or":
                        yield Token(TokenType.OR, value, pos)
                    elif value == "and":
                        yield Token(TokenType.AND, value, pos)
                    elif value == "not":
                        yield Token(TokenType.NOT, value, pos)
                    else:
                        yield Token(TokenType.IDENT, value, pos)
                    pos += len(value)
                else:
                    raise SyntaxError(
                        f'unexpected character "{input[pos]}"',
                        (FILE_NAME, 1, pos + 1, input),
                    )
        yield Token(TokenType.EOF, "", pos)

    @overload
    def accept(self, type: TokenType, *, reject: Literal[True]) -> Token: ...

    @overload
    def accept(
        self, type: TokenType, *, reject: Literal[False] = False
    ) -> Token | None: ...

    def accept(self, type: TokenType, *, reject: bool = False) -> Token | None:
        if self.current.type is type:
            token = self.current
            if token.type is not TokenType.EOF:
                self.current = next(self.tokens)

View on GitHub (pinned to 0d6fbdeffa)