666ghj/MiroFish · error · RuntimeError
Zep batch item submission failed
Error message
Zep batch {batch_id} item submission failed What it means
RuntimeError in submit_document_batch: client.batch.add failed with an error that is_retryable_zep_error classified as non-retryable (e.g. 400 validation, 401 auth, 404 unknown batch). Since replaying a non-idempotent add against an ambiguous state is unsafe and recovery reconciliation is only attempted for retryable failures, the code fails immediately with the original error as cause.
Solutions
- Read the chained cause exception — it carries the true Zep error code and message.
- 401/403: refresh ZEP_API_KEY and restart the build; the batch journal allows clean resume.
- 404/409 on the batch: the batch no longer accepts items — clear project.zep_batch_id/operation_id and start a new batch.
- 400/validation: fix the payload (re-check chunk sizes/counts with validate_batch_chunks) before resubmitting.
- Pin the SDK version if a client upgrade changed the add-item schema.
Example fix
# before
else:
raise RuntimeError(f"Zep batch {batch_id} item submission failed") from e
# after - classify the refusal with the underlying status for faster diagnosis
else:
status = getattr(getattr(e, 'response', None), 'status_code', None)
raise RuntimeError(
f"Zep batch {batch_id} item submission failed"
+ (f" (HTTP {status})" if status else "")
+ "; batch may be deleted, processed, or the payload rejected"
) from e Defensive patterns
Strategy: try-catch
Validate before calling
builder.validate_batch_chunks(chunks, batch_size=batch_size) # catches size/count issues pre-flight assert graph_id and project.zep_batch_id, 'batch must exist before adding items'
Try / catch
try:
submission = builder.submit_document_batch(graph_id, chunks, batch_size=bs)
except RuntimeError as e:
cause = e.__cause__
status = getattr(getattr(cause, 'response', None), 'status_code', None)
if status in (401, 403):
logger.error('Zep auth failed during item add; check ZEP_API_KEY')
elif status in (404, 409):
project.zep_batch_id = None # batch gone; next run starts a new batch
raise Prevention
- Classify the chained cause's HTTP status before deciding retry vs restart.
- Rotate ZEP_API_KEY proactively so long builds do not die mid-run on auth expiry.
- Do not delete batches in the Zep console while a build is resumable, or clear the persisted ids if you do.
- Re-run validate_batch_chunks before every resume to catch payload regressions.
When it happens
Trigger: Adding items to a batch_id that was deleted or already processed (404/409); auth token expired mid-build (401); item payload violates Zep limits (e.g. a chunk >10,000 chars slipping past validation); malformed request after SDK version change (400).
Common situations: ZEP_API_KEY rotated or revoked during a long build; the batch was manually deleted in the Zep console mid-run; SDK upgrade changing add-item request shape; chunker regression producing oversized chunks.
Related errors
- Multiple Zep batches match operation
- Persisted Zep batch does not match the current graph input
- Zep Batch API returned no batch_id
- Zep batch acknowledged of items
- Zep batch item submission is unconfirmed; the draft was not…
AI-assisted analysis of 666ghj/MiroFish@b5b53acc57 (2026-08-14).
Data as JSON: /api/errors/4d424d11d246f77e.
Report an issue: GitHub.
Appendix: source
Thrown at backend/app/services/graph_builder.py:517
batch_id,
expected_item_count,
)
recovered_indexes = {
getattr(item, "sequence_index", None)
for item in recovered_items
}
if (
len(recovered_items) == expected_item_count
and recovered_indexes == set(range(expected_item_count))
):
item_details = recovered_items[i:expected_item_count]
else:
raise RuntimeError(
f"Zep batch {batch_id} item submission is unconfirmed; "
"the draft was not processed or replayed"
) from e
else:
raise RuntimeError(
f"Zep batch {batch_id} item submission failed"
) from e
if len(item_details or []) != len(items):
recovered_items = self._reconcile_batch_item_count(
batch_id,
expected_item_count,
)
recovered_indexes = {
getattr(item, "sequence_index", None)
for item in recovered_items
}
if (
len(recovered_items) == expected_item_count
and recovered_indexes == set(range(expected_item_count))
):
item_details = recovered_items[i:expected_item_count]
else:View on GitHub (pinned to b5b53acc57)