Skip to content

[Bug] Non transient enrich error aborts entire batch #47

Description

@nelsonmfinda

Description

In enrich_chunks, a single chunk that produces a non-transient ProviderError (e.g. unparseable JSON from the model) aborts the entire concurrent batch. asyncio.gather is called without return_exceptions=True, so the first re-raised error cancels every other in flight request in that batch. Resume picks up from the last checkpoint on the next run, but the user sees a crash mid-pipeline and concurrent work is wasted.

Steps to reproduce

  1. king-scrape https://hono.dev/docs --name hono-test --display-name "Hono Test" --yes
  2. Let the pipeline reach the [enrich] running... stage on a corpus large enough that at least one chunk gets a malformed JSON response from the model (~500+ chunks with a preview quality model is enough in practice).
  3. Wait for the first non-transient ProviderError to surface.

Expected behavior

A bad response from one chunk should be treated as a per-chunk failure: log it, mark that chunk as unprocessed (the existing "no valid response after retries" path), let the schema fallback try, and continue the rest of the batch. Resume should not be triggered just because one chunk in 500 was malformed.

Actual behavior

The whole batch aborts. asyncio.gather propagates the first exception, every other coroutine in that batch is cancelled, and the user sees a stack trace.

  Traceback (most recent call last):
    File ".../king_context/scraper/cli.py", line ..., in main
      asyncio.run(run_pipeline(...))
    ...
    File ".../king_context/scraper/enrich.py", line 341, in enrich_chunks
      results = await asyncio.gather(*[guarded(c) for c in batch])
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File ".../king_context/scraper/enrich.py", line ..., in _enrich_one
      raise
    File ".../llm_providers/openrouter.py", line ..., in complete
      return parse_json_object(content)
  llm_providers.base.ProviderError: openrouter: LLM response was not parseable JSON

Environment

  • King Context version (or commit SHA): v0.4.0 (upstream/main @ 9f0e5b1)
  • Install method: pip install -e '.[all]'
  • OS: macOS (Darwin 25.3.0)
  • Python version: 3.14.4
  • Node version (if installer-related): n/a
  • Claude Code / IDE / agent (if relevant): n/a

Affected component

  • MCP server (king-context)
  • CLI (kctx)
  • Scraper (king-scrape)
  • Researcher (king-research)
  • Installer (@king-context/cli)
  • Skills (.claude/skills/)
  • Documentation
  • Other (please describe)

Additional context

Suggested fix: pass return_exceptions=True to the asyncio.gather call in enrich_chunks (src/king_context/scraper/enrich.py:341), treat any returned Exception as a per chunk miss (same as the existing None path), and continue the run. Counts and checkpoints already tolerate missing chunks. The schema-fallback client at clients.schema_fallback is the right place to absorb malformed JSON, so this becomes mostly an error routing fix rather than a behavior change.

Surfaced during end-to-end testing of #46 (content hash provenance + enrichment cache); pre existing, unrelated to that PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions