tirth8205/code-review-graph · error · ValueError

expected JSON object

Error message

expected JSON object

What it means

Raised by _object_members when the token at the given index is not the '{' that opens a JSON object (or the index is past the end of the token stream). It means the JSONC path being edited assumes an object exists at that position, but the tokenizer found something else. This is part of the hand-rolled JSONC editing used to surgically remove keys from config files during uninstall.

Source

Thrown at code_review_graph/uninstall.py:271

    index += 1
    while index < len(tokens):
        kind = tokens[index].kind
        if kind == token.kind:
            depth += 1
        elif kind == closing:
            depth -= 1
            if depth == 0:
                return index + 1
        elif kind in ("{", "["):
            index = _skip_value(tokens, index)
            continue
        index += 1
    raise ValueError("unterminated JSON container")


def _object_members(tokens: Sequence[_Token], index: int) -> list[_Member]:
    if index >= len(tokens) or tokens[index].kind != "{":
        raise ValueError("expected JSON object")
    members: list[_Member] = []
    cursor = index + 1
    while cursor < len(tokens) and tokens[cursor].kind != "}":
        if tokens[cursor].kind == ",":  # trailing comma
            cursor += 1
            continue
        key_token = tokens[cursor]
        if key_token.kind != "string" or not isinstance(key_token.value, str):
            raise ValueError("expected JSON object key")
        if cursor + 1 >= len(tokens) or tokens[cursor + 1].kind != ":":
            raise ValueError("expected colon after JSON object key")
        value_index = cursor + 2
        value_end = _skip_value(tokens, value_index)
        comma_index = value_end if (
            value_end < len(tokens) and tokens[value_end].kind == ","
        ) else None
        members.append(
            _Member(key_token.value, cursor, value_index, value_end, comma_index)

View on GitHub (pinned to b58668751a)

Solutions

  1. Validate the JSONC file parses as JSON (after stripping comments) before running the uninstall removal
  2. Check the path components: every intermediate component must address an object (str key) or array (int index) matching the actual document structure
  3. If the file was hand-edited, fix the unbalanced/missing braces so the target parent is actually an object
  4. Catch the ValueError and fall back to leaving the file untouched (or rewriting it wholesale) instead of crashing the uninstall

Example fix

# before
remove_jsonc_paths(text, ["mcpServers", "code-review-graph"])
# with {"mcpServers": "disabled"}  -> ValueError: expected JSON object

# after
import json, re
def strip_comments(s):
    return re.sub(r"//[^\n]*|/\*.*?\*/", "", s, flags=re.S)
try:
    cfg = json.loads(strip_comments(text))
    assert isinstance(cfg.get("mcpServers"), dict)
except (ValueError, AssertionError):
    cfg = {}
remove_jsonc_paths(text, ["mcpServers", "code-review-graph"])
Defensive patterns

Strategy: validation

Validate before calling

import json, re
def strip_comments(s: str) -> str:
    return re.sub(r"//[^\n]*|/\*.*?\*/", "", s, flags=re.S)

def parent_is_object(text: str, path: list) -> bool:
    try:
        doc = json.loads(strip_comments(text))
    except ValueError:
        return False
    cur = doc
    for c in path:
        try:
            cur = cur[c]
        except (KeyError, IndexError, TypeError):
            return False
    return isinstance(cur, dict)

Type guard

def is_json_object_root(text: str) -> bool:
    t = text.lstrip()
    return t.startswith('{')

Try / catch

try:
    _remove_jsonc_paths(text, paths)
except ValueError as e:
    log.warning("skipping malformed config: %s", e)  # leave file untouched

Prevention

When it happens

Trigger: Calling the uninstall/path-removal API with a path whose parent component resolves to a non-object value, e.g. removing 'a.b' when 'a' is a string, number, or array. Also triggered by malformed JSONC where an object is truncated or the wrong token lands at the expected position.

Common situations: User hand-edited a JSON config (settings.json, tsconfig-like file) and left it structurally broken; config schema changed between install and uninstall; path was built with a wrong key type (int where an object key was expected); comments/strings confused a hand-edit and braces got unbalanced.

Related errors


AI-assisted analysis of tirth8205/code-review-graph@b58668751a (2026-08-28). Data as JSON: /api/errors/962d48ebab4bca46. Report an issue: GitHub.