ErrLookup › Background articles › SyntaxError: 'Unexpected token' and 'Unexpected end of input' — why parsers reject your input across libraries
SyntaxError: 'Unexpected token' and 'Unexpected end of input' — why parsers reject your input across libraries
SyntaxError is the class thrown when input cannot be parsed under the expected grammar — the force behind messages like "Unexpected token", "Unexpected end of input", "closing quote is missing", and "Named export not found". Far beyond the JavaScript and Python compilers, it surfaces from template engines (Twig), expression evaluators (pandas eval/query, pytest -m), CSS and XML parsers, WebSocket handshake header parsers (ws), PEP 508 requirement strings in pip, config validators (ultralytics), and module-import analyzers (Vite, Hoppscotch). You meet it whenever a string handed to a library — code, template, stylesheet, header, requirement, or config key — breaks that library's rules for shape, order, or names, and the library fails fast instead of guessing.
Distilled from 94 documented records across 24 repositories.
Background
SyntaxError originates in the language runtimes: JavaScript's interpreter and Python's compile() raise it when source text violates the grammar, and both expose it as a catchable class. Libraries across ecosystems borrow that class for their own front doors — the parsers that turn raw text into structure before any logic runs. The records span template parsing (Twig's token parser for {% trans %}), embedded expression languages (pandas eval/query walking the Python AST, pytest's marker-expression tokenizer for -m, CPython's ForwardRef compiling annotation strings with compile(..., 'eval')), wire-protocol headers (the Sec-WebSocket-Extensions and Sec-WebSocket-Protocol parsers in ws), configuration grammars (pip's PEP 508 parse_requirement, ultralytics' check_dict_alignment against default.yaml, the CSS worker's at-rule parser in dianping/cat), and code analysis (Acorn inside Hoppscotch's script sandbox, Vite's analyzeImportedModDifference for SSR imports). In every case the meaning is the same: the input stopped being interpretable at the grammar layer, before semantics or execution began.,From the caller's side the family has a consistent shape: a message naming what was expected or what was found, plus a position — line and column for the CSS worker and Twig, a byte offset for ws's 'Unexpected character at index', a span for swc's XML parser, a column for pytest's quote errors. That position marks where the parser noticed, not necessarily what caused the drift: an unmatched brace earlier in a stylesheet can surface as 'Unexpected token' much later, and pip's 'unexpected trailing data' names residue that survives after an otherwise complete requirement. Messages can also be borrowed misleadingly: swc's XML parser reports phase errors with JavaScript-flavored wording ('The requested module ... does not provide an export named', 'Cannot use import.meta outside a module') that describe parser phases, not modules. Whether the error is fatal is library-specific: dianping/cat's CSS parser downgrades it to an 'error' event in non-strict mode and keeps parsing, swc's XML parser collects errors in a vector and still returns a Document (one with no root element), while Vite's SSR guard and Twig's token parsers abort immediately.,Much of the family is not literal tokenizing failure but the library's way of saying the input cannot be interpreted as written. ultralytics raises SyntaxError for an unknown config key and appends difflib close-match suggestions; symfony raises it for a Twig filter whose providing component is not installed and answers with a 'composer require' suggestion; Pathway raises it for correlated subqueries its SQL engine cannot resolve; vitest raises it for expect.poll() combined with snapshot or throw matchers; pandas raises it for an '@' prefix at top-level eval where no local scope exists. Import analysis sits in between: Vite statically checks named imports against what a CommonJS module's module.exports exposes and emulates Node's own 'Named export not found' error, while Hoppscotch rejects import bindings that collide with sandbox-reserved names or that resolve to two different sources across combined scripts. None of these involve a broken tokenizer — the grammar being enforced is the library's contract about shape, order, and names, so the fix is usually to change the input or the call, not to catch harder.
Common causes
- Malformed or truncated input. Unterminated quotes, unbalanced braces, trailing separators, or truncated text. ws throws 'Unexpected end of input' for headers ending mid-token or mid-quote, pytest reports a missing closing quote in -m marker values, the CSS worker throws 'Expected ...'/'Unexpected token ...' for missing semicolons or surplus closing braces, pip rejects trailing data after a complete requirement, and embedded coordination JSON fails JSON.parse after hand edits.
- Grammar the parser does not cover. The input is valid in the wider language but this parser build does not implement it. Examples: modern CSS at-rules hitting 'Unknown @ rule.', TypeScript syntax in Hoppscotch's JavaScript-only Acorn sandbox, two top-level expressions in one pd.eval call, correlated subqueries in Pathway SQL, and forward-reference strings that are not a single Python expression.
- Valid tokens in illegal positions. Every token is legal but an ordering rule fails. @charset, @import, or @namespace appearing after style rules in CSS; a stray token where Twig's {% trans %} expects 'with', 'from', or 'into'; an '@' variable prefix at top-level pd.eval; a stray end tag or CDATA section before the XML root element in swc.
- Unknown names, typos, or missing providers. A name the library cannot resolve. ultralytics' 'not a valid YOLO argument' for keys like epocks, symfony's 'Unknown filter' answered by 'composer require <component>', and pandas failing to normalize an exotic column name into a valid Python identifier for query().
- Import shape a module cannot satisfy. Named imports from a CommonJS package whose module.exports does not statically expose the binding (Vite SSR), import bindings that collide with sandbox-reserved names like __hoppReporter or globalThis, or the same binding name imported from two different sources across combined scripts in one Hoppscotch request chain.
- Hand-edited or concatenated machine text. Text that drifted from its generator's guarantees: a hand-edited coordination JSON block inside an issue body, CSS fragments glued so directives land after rules, XML fragments concatenated so end tags precede the root element, or bundler output that appends @import statements below existing styles.
- API misuse surfaced as syntax. Call patterns the library rejects at its grammar layer: expect.poll() chained with snapshot or throw matchers in vitest, complex node bodies inside {% trans %} instead of simple text or a single expression, or assignments and imports inside a quoted annotation string evaluated via ForwardRef.
What usually fixes it
- Read the position, then look upstream. The reported line/column/index marks where the parser gave up, not the root cause — an unmatched brace or quote earlier shifts everything after it. Fix the first reported error, re-parse, and expect later errors to disappear as collateral.
- Generate instead of hand-building. Produce headers with ws's extension.format(), XML through a serializer, requirements from packaging.Requirement templates, and coordination blocks through their tooling. When combining CSS or XML fragments, wrap them in one container and re-serialize rather than gluing raw chunks.
- Rewrite to the grammar the library supports. Default-import CommonJS packages and destructure (or add them to optimizeDeps.include / ssr.noExternal), split 'a; b' into two eval calls, replace correlated subqueries with JOIN plus GROUP BY, replace runtime CSS @import with build-time inlining, and move dataset keys into their own YAML instead of overrides.
- Catch and classify at the boundary. Treat SyntaxError from header or handshake parsing as a protocol error: ws's handleUpgrade already answers HTTP 400, and direct parse() calls should be wrapped, closing the connection with code 1002. For recoverable parsers, inspect the collected error list before trusting output — swc still returns a Document, but with ErrorKind::UnexpectedEofInStartPhase it has no root element and should be treated as fatal.
- Validate with the same parser the consumer uses. Run lint:twig on templates, ast.parse(s, mode='eval') on generated annotation strings, stylelint or xmllint on stylesheets and XML, a JS linter on sandbox scripts before pasting them into Hoppscotch, and diff config keys against the library's defaults (ultralytics' default.yaml) at startup.
Go deeper
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Documented occurrences
- [vite] Named export '${lastBinding}' not found. The requested module '${rawId}' is a CommonJS module, which may not support all module.exports as named exports. CommonJS modules can always be imported via the default export, for example using: import pkg from '${rawId}'; const {${missingBindings.join(', ')}} = pkg; (vitejs/vite)
- Could not convert '{name}' to a valid Python identifier. (pandas-dev/pandas)
- Unexpected character at index ${i} (websockets/ws)
- Unknown @ rule. (dianping/cat)
- @charset not allowed here. (dianping/cat)
- only a single expression is allowed (pandas-dev/pandas)
- The requested module '{specifier}' does not provide an export named '{export_name}' (swc-project/swc)
- Did you forget to run "composer require %s"? Unknown %s "%s". (symfony/symfony)
- Expected {name} at line {line}, col {col}. (dianping/cat)
- [Hoppscotch] '${reservedConflict}' is reserved by Hoppscotch's script wrapper and cannot be used as an import binding. Please rename the import. (hoppscotch/hoppscotch)
- @import not allowed here. (dianping/cat)
- Forward reference must be an expression -- got {arg!r} (python/cpython)
- The '@' prefix is not allowed in top-level eval calls. please refer to your variables by name without the '@' prefix. (pandas-dev/pandas)
- expect.poll() is not supported in combination with .${key}(). Use vi.waitFor() if your assertion condition is unstable. (vitest-dev/vitest)
- Unexpected token. Twig was looking for the "with", "from", or "into" keyword. (symfony/symfony)
- invalid requirement: %s (pypa/pip)
- unexpected trailing data: %s (pypa/pip)
- [Hoppscotch] Script failed to parse: ${parseError} (hoppscotch/hoppscotch)
- @namespace not allowed here. (dianping/cat)
- Cannot use import.meta outside a module (swc-project/swc)
…and 74 more across the corpus — use search.
Honest provenance: generated on 2026-08-18 from AI-assisted analysis of the linked records. See how records are made.