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

Aperta Adatta ai principianti
#2,614 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 5 giorni

Nessuno ha ancora preso questa issue.

  • #1 di @wangkangmao — chiusa senza merge

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
82/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
backend, databases

Direzione di ricerca

Leggi packages/graphrag-cache/graphrag_cache/json_cache.py e confronta JsonCache.get() con il comportamento di SQLiteCache descritto nell’issue. Controlla i test della cache e aggiungi la copertura per i valori JSON validi che non sono dizionari e per i dizionari a cui manca result. Il lavoro è completato quando le voci non valide vengono eliminate e get() restituisce None senza sollevare eccezioni.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

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.
Lingua principale
Python
Stelle
36.6k
Fork
3.9k
Merge medio
4g 11h
PR unite (30g)
1

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di microsoft/graphrag

Tutte le issue di microsoft/graphrag

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.