{"record":{"id":"e5a29555fd6cf8f9","repo":"github/spec-kit","slug":"auth-json-must-be-a-json-object-got-type-raw","errorCode":null,"errorMessage":"auth.json must be a JSON object, got {type(raw).__name__}","messagePattern":"auth\\.json must be a JSON object, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/authentication/config.py","lineNumber":118,"sourceCode":"        try:\n            mode = config_path.stat().st_mode\n            if mode & (stat.S_IRGRP | stat.S_IROTH):\n                import warnings\n\n                warnings.warn(\n                    f\"{config_path} is readable by group/others. \"\n                    \"Consider restricting with: chmod 600 \"\n                    f\"{config_path}\",\n                    UserWarning,\n                    stacklevel=2,\n                )\n        except OSError:\n            pass  # stat failed — skip permission check\n\n    raw = json.loads(config_path.read_text(encoding=\"utf-8\"))\n\n    if not isinstance(raw, dict):\n        raise ValueError(f\"auth.json must be a JSON object, got {type(raw).__name__}\")\n\n    providers_raw = raw.get(\"providers\")\n    if not isinstance(providers_raw, list):\n        raise ValueError(\"auth.json must contain a 'providers' array\")\n\n    entries: list[AuthConfigEntry] = []\n    for i, entry_raw in enumerate(providers_raw):\n        if not isinstance(entry_raw, dict):\n            raise ValueError(f\"providers[{i}]: must be a JSON object\")\n\n        hosts = entry_raw.get(\"hosts\")\n        if not isinstance(hosts, list) or not hosts:\n            raise ValueError(f\"providers[{i}]: 'hosts' must be a non-empty array\")\n        if not all(isinstance(h, str) and h.strip() for h in hosts):\n            raise ValueError(f\"providers[{i}]: each host must be a non-empty string\")\n        # Normalize hosts: strip whitespace and lowercase\n        hosts = [h.strip().lower() for h in hosts]\n        # Reject dangerous wildcard forms (e.g. *github.com matches github.com.evil.com)","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/authentication/config.py#L100-L136","documentation":"When loading auth.json, the config parser json.loads()s the file and requires the top level to be a JSON object (dict). Any other JSON type — array, string, number, true/false, null — raises ValueError with the actual type name, because provider entries can only be looked up on a mapping.","triggerScenarios":"auth.json containing just [ {...} ] (a top-level array of providers), \"...\", 42, null, or a bare true; commonly a truncated edit or wrapping mistake where the outer {} braces were deleted.","commonSituations":"Hand-editing auth.json and accidentally removing the outer braces while moving a provider entry; generating the file with json.dump(providers_list) instead of json.dump({\"providers\": providers_list}); emptying the file to null while debugging.","solutions":["Wrap the content in an object with a providers array: {\"providers\": [ ... ]}.","Validate before running: jq 'type' auth.json should print \"object\".","If a script generates the file, dump {\"providers\": entries}, not the list itself."],"exampleFix":"// before (auth.json)\n[ {\"hosts\": [\"github.com\"], \"provider\": \"github\", \"auth\": \"bearer\"} ]\n// after\n{ \"providers\": [ {\"hosts\": [\"github.com\"], \"provider\": \"github\", \"auth\": \"bearer\"} ] }","handlingStrategy":"type-guard","validationCode":"import json\nfrom pathlib import Path\n\nraw = json.loads(Path(\"auth.json\").read_text(encoding=\"utf-8\"))\nif not isinstance(raw, dict):\n    raise SystemExit(\"auth.json must contain a top-level JSON object\")","typeGuard":"from typing import Any\n\ndef is_auth_config_object(raw: Any) -> bool:\n    \"\"\"True when the parsed auth.json top level is a JSON object.\"\"\"\n    return isinstance(raw, dict)","tryCatchPattern":"try:\n    entries = load_auth_config(path)\nexcept ValueError as exc:\n    if \"must be a JSON object\" in str(exc):\n        raise SystemExit(f\"restructure auth.json: {exc}\") from exc\n    raise","preventionTips":["Keep auth.json shaped as {\"providers\": [...]} — never a bare array.","Validate with jq 'type' == \"object\" after every edit.","Generate the file from Python with json.dump({\"providers\": entries}, ...)."],"tags":["authentication","json","configuration","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}