666ghj/MiroFish · error · RuntimeError

Zep batch {batch_id} processing is unconfirmed

Error message

Zep batch {batch_id} processing is unconfirmed

What it means

RuntimeError at the end of submit_document_batch: client.batch.process(batch_id) raised, and since a process POST can succeed server-side while the response is lost, the code reconciles with a safe GET (batch.get with read retry). If the fetched status is None or 'draft', the batch was never actually queued for processing — all items were submitted but processing is unconfirmed, and re-POSTing process is deliberately avoided (the comment notes a second POST is unsafe).

Source

Thrown at backend/app/services/graph_builder.py:555

                        f"Zep batch {batch_id} acknowledged {len(item_details or [])} "
                        f"of {len(items)} items"
                    )
            for item in item_details:
                episode_uuid = getattr(item, "episode_uuid", None)
                if episode_uuid:
                    episode_uuids.append(episode_uuid)

        try:
            self.client.batch.process(batch_id=batch_id)
        except Exception as error:
            # A process response can be lost after the server accepted it.
            # Reconcile with a safe GET instead of issuing a second POST.
            summary = call_zep_read_with_retry(
                lambda: self.client.batch.get(batch_id=batch_id),
                operation_name=f"reconcile batch {batch_id}",
            )
            if getattr(summary, "status", None) in {None, "draft"}:
                raise RuntimeError(
                    f"Zep batch {batch_id} processing is unconfirmed"
                ) from error

        return BatchSubmission(
            batch_id=batch_id,
            operation_id=operation_id,
            episode_uuids=episode_uuids,
            item_count=total_chunks,
        )

    @staticmethod
    def validate_batch_chunks(chunks: List[str], *, batch_size: int = 350) -> None:
        """Validate every Batch API limit before the first Cloud mutation."""

        if not chunks:
            raise ValueError("At least one text chunk is required")
        if not 1 <= batch_size <= 350:
            raise ValueError("batch_size must be between 1 and 350")

View on GitHub (pinned to b5b53acc57)

Solutions

  1. Manually trigger processing once in the Zep console (or via a single careful re-POST) and confirm status leaves draft, then resume the build.
  2. If status was merely lagging, retry the build/resume flow after a short wait — the GET will then show processing/processed.
  3. Raise the client timeout for the process call and lengthen read-retry patience in call_zep_read_with_retry.
  4. Check network intermediaries (nginx, service mesh) for idle timeouts that kill the long process response.

Example fix

# before
except Exception as error:
    summary = call_zep_read_with_retry(lambda: self.client.batch.get(batch_id=batch_id), operation_name=f"reconcile batch {batch_id}")
    if getattr(summary, "status", None) in {None, "draft"}:
        raise RuntimeError(f"Zep batch {batch_id} processing is unconfirmed") from error

# after - poll the GET briefly before declaring unconfirmed, include status
except Exception as error:
    for delay in (1, 3, 5):
        summary = call_zep_read_with_retry(lambda: self.client.batch.get(batch_id=batch_id), operation_name=f"reconcile batch {batch_id}")
        if getattr(summary, "status", None) not in {None, "draft"}:
            break
        time.sleep(delay)
    else:
        raise RuntimeError(
            f"Zep batch {batch_id} processing is unconfirmed (status={getattr(summary, 'status', None)!r}); "
            "trigger processing once via the Zep console, then resume"
        ) from error
Defensive patterns

Strategy: retry

Try / catch

try:
    submission = builder.submit_document_batch(graph_id, chunks, batch_size=bs)
except RuntimeError as e:
    if 'processing is unconfirmed' in str(e):
        summary = call_zep_read_with_retry(lambda: builder.client.batch.get(batch_id=project.zep_batch_id), operation_name='recheck')
        if getattr(summary, 'status', None) in {None, 'draft'}:
            raise  # operator must trigger process once in Zep console, then resume
        submission = BatchSubmission(batch_id=project.zep_batch_id, operation_id=project.zep_batch_operation_id, episode_uuids=[], item_count=len(chunks))
    else:
        raise

Prevention

When it happens

Trigger: process() times out or hits a retryable network error; Zep accepted items but the process trigger never landed, leaving status='draft'; Zep Cloud incident where both POST and GET degrade.

Common situations: Long-running builds whose final process call crosses a proxy/gateway timeout; transient 5xx from Zep; the GET reconcile racing Zep's own state update so status still reads draft.

Related errors


AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14). Data as JSON: /api/errors/acca4d000fa0e2b8. Report an issue: GitHub.