ErrLookup › Background articles › JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about
JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about
"Unexpected token in JSON at position", "not valid JSON", "failed to JSON decode", and similar JSON parse errors appear when a library calls a JSON parser on text that is not well-formed JSON — usually a hand-edited or truncated file. This article explains which layer produces these errors, why the message rarely names the real problem, and the common causes: trailing commas, single quotes, comments, BOMs, smart quotes, merge-conflict markers, and truncated documents.
Distilled from 84 documented records across 45 repositories.
Background
This family covers every point where a library hands text or bytes to a JSON parser — JSON.parse in JavaScript, json.Unmarshal in Go, phutil_json_decode in PHP, json.load in Python, serde_json in Rust, json_decode in PHP, or a bespoke hand-rolled scanner like Hibernate's StringJsonDocumentReader — and the parser rejects the input as syntactically invalid. The errors look very different on the surface: a Node SyntaxError naming a token and position, a Go wrapped error carrying the JSON offset, a Python JSONDecodeError with line/column, a PHP PhutilJSONParserException, or a generic app-level toast like 'json_format_failed'. Underneath they are all the same condition: the input is not a well-formed JSON document per RFC 8259.
The mechanisms differ in what happens next, and that shapes how the error reaches you. Some libraries fail hard: maigret raises ValueError('Problem parsing json contents from file ...'), Phabricator wraps PhutilJSONParserException in a proxy exception ('Failed to decode rule data.', 'Failed to JSON decode rule data!'), and Hadoop's DiskBalancer throws Result.MALFORMED_PLAN after the plan's sha1 already verified. Others degrade deliberately: CodeWhale's config loader downgrades a bad mcp.server_definitions value to a warning and continues with an empty server list, ECC's context-dir migration keeps going with default metadata, claude-mem resets a corrupt settings file to an empty object (silently dropping your stored keys on the next write) and treats an unparseable PID file as 'no live worker'. A few are defensive guards rather than thrown errors at all: AnotherRedisDesktopManager blocks the save with a localized toast before re-encoding msgpack, protobuf, or PHP-serialized values, and Grav distinguishes a file literally containing null (valid) from a real decode failure by checking json_last_error(). The severity is library-specific — the same underlying syntax problem can be fatal, a silent data loss, or a blocked save depending on which one you are holding.
A recurring theme is that well-behaved parsers point at the exact offending byte. V8's 'Unexpected token ... in JSON at position N', the Go wrapped 'parsing node file %q: %w' with its JSON offset, PHP's json_last_error_msg(), and Python's json.tool output all tell you where the document broke. But many wrappers hide this: Phabricator's generic messages carry the offset only in the wrapped exception trace, AnotherRedisDesktopManager's toasts append the message only as an improvement suggestion, and the CLI parsing the coordination JSON in ECC issues is deliberately terse about file contents ('contents were omitted') so sensitive data never reaches logs. When the message is generic, running the file through an independent validator — jq, python -m json.tool, node -e "JSON.parse(...)" — recovers the position. Also worth knowing: parsers disagree about what is 'almost JSON'. JSON5 object literals, single-quoted keys, trailing commas, and comments all parse fine in JavaScript tooling but are rejected by every strict parser in this family; UTF-8 BOMs (Grav, maigret, ECC), smart quotes from rich-text editors, and git conflict markers are other frequent invisible offenders.
A distinct sub-family deserves mention: embedded-asset parse failures. CodeWhale's 'bundled model catalog must parse' and llmfit's 'embedded use_case_benchmarks.json is invalid' are include_str!-compiled JSON parsed at runtime with an .expect — they panic not because anything is wrong on your machine, but because the shipped asset and the deserialization struct drifted apart at build time. Similarly, some records fire on structurally valid JSON with the wrong shape: grafana/k6 fails unmarshalling options that are an array or scalar instead of an object, Hibernate's reader rejects a bare top-level string (state NONE, quote illegal there), and tailscale's node-file reader enforces key prefixes like 'privkey:' and 'mkey:' as typed fields. So 'JSON parse error' can mean invalid syntax, or valid syntax that does not match the expected schema — check which before editing anything.
Common causes
- Hand edits that break strict JSON syntax. The single most common trigger across the records: trailing commas after the last element, single quotes instead of double, unquoted keys, comments, or unescaped quotes introduced while manually editing a config, cache, or data file. Several records (Phabricator Herald pairs, ECC coordination JSON, maigret data files, claude-mem settings) call this out explicitly.
- Truncated or partially written files. Interrupted writes, cut-off downloads, crash-killed processes, and length-limited columns leave JSON cut mid-literal or mid-value: unterminated strings (Hibernate 'Can't find ending quote of key name'), bare values running to end of input ('Unrecognized marker'), or empty/zero-byte files. Append-style loaders can also receive a partial trailing line from a .jsonl file.
- Pasting non-JSON or JSON-ish text. Pasting a JS object literal instead of strict JSON, pasting JSON5 with unquoted keys, pasting HTML/plain text over decoded content (AnotherRedisDesktopManager editors, maigret URLs serving HTML with status 200), or pasting a path instead of file contents (Hadoop DiskBalancer plan submission).
- Invisible characters: BOM, smart quotes, mojibake. UTF-8 BOMs at the start of a file are rejected by json_decode, JSON.parse, and Python's json module; smart quotes from rich-text editors break string parsing; encoding corruption and merge-conflict markers (<<<<<<< / >>>>>>>) committed into JSON files appear across the ECC, Grav, and tailscale records.
- Valid JSON of the wrong shape. Some parsers fail on structurally valid input that does not match the expected type: grafana/k6 requires req.Options to be a JSON object (not an array, scalar, or double-encoded string), Hibernate's reader requires object- or array-rooted documents, tailscale's NodeFile rejects keys without the right prefix, and serde structs panic when schema fields drift. These are schema errors wearing a parse-error message.
- Double encoding or serialization bugs in the producer. JSON wrapped in a quoted string (k6's 'already-double-encoded JSON'), hand-built serialization instead of a real serializer (concatenated strings in Hibernate columns), stale client/server version skew (flow's type-at-pos RawValue), or a proxy/browser extension mangling a POST body (Phabricator rule saves).
- Embedded or bundled assets out of sync with code. CodeWhale's bundled model catalog and llmfit's use_case_benchmarks.json are compiled into the binary and parsed with .expect; a partial merge, renamed field, or schema-version mismatch makes the first user-facing call panic. Not a runtime condition on the user's machine — fix the build, not the environment.
What usually fixes it
- Validate independently before editing: run the failing file or payload through jq, python -m json.tool, or node -e "JSON.parse(...)" — these report the exact line, column, or byte offset, which many library wrappers hide or omit from their user-facing message.
- Fix the reported construct, not the symptom: remove trailing commas, convert single quotes to double, remove comments and conflict markers, strip UTF-8 BOMs, terminate unterminated strings. Strict JSON admits no relaxed syntax even when your editor or JS tooling accepts it.
- Repair producers, not just files: wherever possible regenerate the JSON through the library that owns it (WriteNodeFile for tailscale node files, the CLI for CodeWhale MCP definitions, hdfs diskbalancer -plan for Hadoop plans) rather than hand-editing; hand-edited files are the dominant cause across this family.
- Check whether the input was truncated and whether it is complete-and-corrupt versus incomplete: full-corrupt files need repair or restore from backup; truncated ones need a re-download, a bigger column, or an atomic-write producer (temp file + rename).
- Distinguish hard failures from degradations before acting: some loaders silently skip bad data (CodeWhale warns and uses an empty list, ECC migrates with defaults, claude-mem resets settings and will drop stored keys on next write), so after fixing the JSON, diff your data against what was silently lost.
- Prevent recurrence in CI: lint every JSON data/config file with jq or an equivalent before deploy, keep a unit test that parses embedded assets, and write JSON only through real serializers so quotes and escapes are always balanced.
Go deeper
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Documented occurrences
- invalid JSON payload at key {MCP_SERVER_DEFINITIONS_KEY}; contents were omitted (Hmbown/CodeWhale)
- Could not parse existing candidates file at ${candidatesPath}: ${e.message} (santifer/career-ops)
- The regular expression pair "%s" is not valid JSON. Enter a valid JSON array with two elements. (phacility/phabricator)
- type-at-pos: server payload is valid JSON (facebook/flow)
- unmarshalling options for SDK: %w (grafana/k6)
- Malformed coordination JSON in body: ${error.message} — raw: ${match[1].slice(0, 120)} (affaan-m/ECC)
- character [{}] is not the next non-blank character (hibernate/hibernate-orm)
- bundled model catalog must parse (Hmbown/CodeWhale)
- Unrecognized marker: {} (hibernate/hibernate-orm)
- ! ${contextDir}: invalid meta.json, continuing with defaults (${e.message}) (affaan-m/ECC)
- Decoding JSON failed: {json_last_error_msg} (getgrav/grav)
- parsing node file %q: %w (tailscale/tailscale)
- Failed to parse transcript line (non-Error thrown) (thedotmack/claude-mem)
- Problem parsing json contents from file '{filename}': {str(error)}. (soxoj/maigret)
- Failed to read existing settings file; starting fresh (thedotmack/claude-mem)
- embedded use_case_benchmarks.json is invalid (AlexsJones/llmfit)
- Problem parsing json contents at '{url}': {str(error)}. (soxoj/maigret)
- unexpected quote read in current processing state {} (hibernate/hibernate-orm)
- Raw content is an object, but now parse object failed: ${e.message} (qishibo/AnotherRedisDesktopManager)
- Warning: Error counting components for plugin at ${pluginPath} (davila7/claude-code-templates)
…and 64 more across the corpus — use search.
Honest provenance: generated on 2026-08-28 from AI-assisted analysis of the linked records. See how records are made.