abhigyanpatwari/GitNexus · error
Batch execution failed for rows
Error message
Batch execution failed for rows ${firstRow + 1}-${firstRow + subBatch.length}: ${msg} (${queryPreview}) What it means
executeWithReusedStatement (gitnexus/src/core/lbug/lbug-adapter.ts:1835) wraps the per-row execute/drain loop in 4-row sub-batches. When the native engine throws mid-sub-batch (type/constraint violation, lock or checkpoint IO error, read-only connection), the wrapper rethrows with the 1-based row range of the failing sub-batch and a 120-char collapsed preview of the Cypher. Earlier sub-batches are already committed — there is no cross-sub-batch transaction.
Solutions
- Use the reported row range to inspect paramsList[firstRow-1 .. firstRow+3] for the offending value (type, null, size)
- If the underlying message is an IO/lock class — free disk space, stop competing processes, then re-run
- If the schema drifted, rebuild with gitnexus analyze --force so table definitions match the emitted rows
- Fix the row builder producing malformed values, then re-run the analyze
Example fix
// before — malformed row surfaces mid-batch with only a row range
await executeWithReusedStatement(cypher, paramsList);
// after — reject malformed rows before any sub-batch commits
const bad = paramsList.findIndex((p) => p.line !== undefined && typeof p.line !== 'number');
if (bad !== -1) throw new Error(`paramsList[${bad}] has non-numeric line`);
await executeWithReusedStatement(cypher, paramsList); Defensive patterns
Strategy: validation
Validate before calling
// pre-validate row shapes so a bad row fails BEFORE any sub-batch commits
for (const [i, p] of paramsList.entries()) {
for (const [k, v] of Object.entries(p)) {
if (v === undefined) throw new Error(`paramsList[${i}].${k} is undefined`);
}
} Type guard
const isBatchExecutionFailed = (e: unknown): boolean =>
e instanceof Error && e.message.startsWith('Batch execution failed for rows '); Try / catch
try {
await executeWithReusedStatement(cypher, paramsList);
} catch (e) {
if (isBatchExecutionFailed(e)) {
const m = e.message.match(/rows (\d+)-(\d+)/);
const from = m ? Number(m[1]) - 1 : 0;
logger.error(`failing rows: ${JSON.stringify(paramsList.slice(from, from + 4))}`);
}
throw e;
} Prevention
- Validate extracted values (types, nulls, sizes) before building the writeback batch
- Monitor disk space during long writebacks — ENOSPC mid-batch is a common root cause
- Remember earlier sub-batches are already committed: after a failure, plan for a full-rebuild rerun rather than resuming
When it happens
Trigger: Batch writeback where one row of paramsList violates the schema (wrong property type, null in a required field, oversized value), the disk fills, a WAL checkpoint IO error interleaves, or the connection is read-only.
Common situations: Incremental analyze writebacks on machines with aggressive antivirus or low disk; property types that changed between GitNexus versions while the old index kept the previous schema; batches built from unvalidated extracted data.
Related errors
- [ ] failed to clear existing edges before incremental…
- [spring-aop] failed to clear synthetic evidence before…
- Connection pool integrity error: expected
- deleteNodesForFiles: table does not exist — skipping…
- Failed to remove the LadybugDB index files — still present…
AI-assisted analysis of abhigyanpatwari/GitNexus@ac9a4e9abd (2026-08-20).
Data as JSON: /api/errors/1f3ae3db6f666846.
Report an issue: GitHub.
Appendix: source
Thrown at gitnexus/src/core/lbug/lbug-adapter.ts:1947
const firstRow = subBatchIndex * SUB_BATCH_SIZE;
// One critical section per sub-batch: the prepare + its executes run with
// exclusive access to the connection (so the WAL checkpoint driver cannot
// interleave a CHECKPOINT mid-batch), while the lock is released between
// sub-batches to let the driver checkpoint during a long writeback.
await withConnLock(async () => {
const stmt = await c.prepare(cypher);
if (!stmt.isSuccess()) {
const errMsg = await stmt.getErrorMessage();
throw new Error(`Prepare failed: ${errMsg}`);
}
try {
for (const params of subBatch) {
await drainQueryResult(await c.execute(stmt, params));
}
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
const queryPreview = cypher.replace(/\s+/g, ' ').slice(0, 120);
throw new Error(
`Batch execution failed for rows ${firstRow + 1}-${firstRow + subBatch.length}: ${msg} (${queryPreview})`,
);
}
// Note: LadybugDB PreparedStatement doesn't require explicit close()
});
}
};
/**
* Node and edge totals for the open index.
*
* `edges` is `undefined` when the count could NOT BE TAKEN, and that is a
* different fact from zero. It used to be initialised to 0 with the query in a
* swallowing `catch`, so a WAL/lock contention throw during finalize — a
* documented hazard on this exact call — returned a measured-looking 0. The
* collapse check downstream then read a perfectly healthy index as a total
* write collapse, which is precisely the confident-zero failure that check
* exists to prevent.View on GitHub (pinned to ac9a4e9abd)