[Bug]: JsonCache.get() crashes with unhandled AttributeError on non-dict or invalid envelope JSON payloads

Abierto Apto para principiantes
#2,614 2 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 5 días

Nadie ha tomado este issue todavía.

  • #1 de @wangkangmao — cerrado sin fusionar

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
82/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
python

Línea de trabajo

Lee packages/graphrag-cache/graphrag_cache/json_cache.py y compara JsonCache.get() con el comportamiento de SQLiteCache descrito en el issue. Revisa las pruebas de caché y añade cobertura para valores JSON válidos que no sean diccionarios y para diccionarios a los que les falte result. La tarea se considera terminada cuando se eliminan las entradas no válidas y get() devuelve None sin lanzar una excepción.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Do you need to file an issue?
  • I have searched the existing issues and this bug is not already filed.
  • I believe this is a legitimate bug, not just a question. If this is a question, please use the Discussions area.
Describe the bug

In packages/graphrag-cache/graphrag_cache/json_cache.py, JsonCache.get() decodes stored cache data using json.loads(data) and assumes the deserialized object is always a dictionary:

    async def get(self, key: str) -> Any:
        """Get method definition."""
        if await self.has(key):
            try:
                data = await self._storage.get(key)
                data = json.loads(data)
            except UnicodeDecodeError:
                await self._storage.delete(key)
                return None
            except json.decoder.JSONDecodeError:
                await self._storage.delete(key)
                return None
            else:
                return data.get("result")

If the storage contains a valid JSON payload that is not a dictionary (e.g. a JSON array [1, 2, 3], primitive string, number, or boolean from an external tool, corrupted state, or older schema format), json.loads() succeeds without raising JSONDecodeError. Then execution jumps to data.get("result"), immediately raising:

AttributeError: 'list' object has no attribute 'get'

Furthermore, unlike SQLiteCache (which explicitly handles invalid envelopes and purges corrupted entries via if not isinstance(data, dict) or "result" not in data:), JsonCache neither catches this exception nor deletes the offending key. As a result, every subsequent call to cache.get(key) repeatedly crashes the pipeline instead of recovering gracefully with None.

Steps to reproduce

Self-contained minimal reproduction (no external APIs or configs needed):

import asyncio
import json

class InMemoryStorage:
    def __init__(self):
        self._store = {}
    async def get(self, key):
        return self._store.get(key)
    async def set(self, key, value):
        self._store[key] = value
    async def has(self, key):
        return key in self._store
    async def delete(self, key):
        self._store.pop(key, None)

# Directly reproduces packages/graphrag-cache/graphrag_cache/json_cache.py
class JsonCache:
    def __init__(self, storage):
        self._storage = storage

    async def get(self, key: str):
        if await self.has(key):
            try:
                data = await self._storage.get(key)
                data = json.loads(data)
            except UnicodeDecodeError:
                await self._storage.delete(key)
                return None
            except json.decoder.JSONDecodeError:
                await self._storage.delete(key)
                return None
            else:
                return data.get("result")
        return None

    async def has(self, key: str) -> bool:
        return await self._storage.has(key)

async def main():
    storage = InMemoryStorage()
    cache = JsonCache(storage)
    
    # Store a valid JSON array or primitive payload under a cache key
    await storage.set("bad_envelope", "[1, 2, 3]")
    
    # Crashes with unhandled AttributeError: 'list' object has no attribute 'get'
    result = await cache.get("bad_envelope")
    print("Result:", result)

asyncio.run(main())
Expected Behavior

Consistent with SQLiteCache, JsonCache.get() should verify that isinstance(data, dict) and "result" in data. If invalid, it should purge the corrupted key from storage and safely return None.

Proposed fix:

    async def get(self, key: str) -> Any | None:
        """Get method definition."""
        if await self.has(key):
            try:
                data = await self._storage.get(key)
                data = json.loads(data)
            except (UnicodeDecodeError, json.decoder.JSONDecodeError):
                await self._storage.delete(key)
                return None
            else:
                if not isinstance(data, dict) or "result" not in data:
                    await self._storage.delete(key)
                    return None
                return data["result"]
        return None
GraphRAG Config Used

N/A (Reproducible directly in graphrag_cache.json_cache.JsonCache)

Logs and screenshots
Traceback (most recent call last):
  File "repro.py", line 47, in <module>
    asyncio.run(main())
  File ".../asyncio/runners.py", line 190, in run
    return runner.run(main)
  File ".../asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
  File ".../asyncio/base_events.py", line 654, in run_until_complete
    return future.result()
  File "repro.py", line 43, in main
    result = await cache.get("bad_envelope")
  File "repro.py", line 32, in get
    return data.get("result")
AttributeError: 'list' object has no attribute 'get'
Additional context
  • GraphRAG Version: main branch (post-monorepo split / PR #2591)
  • Python Version: Python 3.10 / 3.11 / 3.12
  • Operating System: Linux / macOS / Windows
  • Note: We have prepared a fix with regression tests and can submit a PR if this looks good to maintainers.
Lenguaje dominante
Python
Estrellas
36.6k
Forks
3.9k
Merge medio
4 d 11 h
PR fusionados (30 d)
1

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de microsoft/graphrag

Todos los issues de microsoft/graphrag

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.