abhigyanpatwari/GitNexus · critical · Error
GraphEmitSink: ${errors.length} streamed CSV writer(s) hit a
Error message
GraphEmitSink: ${errors.length} streamed CSV writer(s) hit an IO error (disk-full / out-of-fds) during the emit — the persisted graph would be truncated, so the run is failed rather than COPYing a partial CSV: ${first} What it means
Thrown by `GraphEmitSink.finalize()` when one or more streamed CSV writers recorded an IO fault during the graph emit. Each per-relationship-pair writer sets a `poison` on any write/flush/open failure; `finalize()` closes all writers, collects the poisoned ones (plus any `openFailure`), and if any exist it fails the run rather than handing a truncated CSV to the bulk COPY. The design is explicit: a partial persisted graph is worse than a clean failure.
Source
Thrown at gitnexus/src/core/lbug/graph-emit-sink.ts:501
finalize(): GraphEmitManifest {
if (this.finalized) throw new Error('GraphEmitSink.finalize() called twice');
this.finalized = true;
const errors: unknown[] = [];
if (this.openFailure !== undefined) errors.push(this.openFailure);
const relsByPair = new Map<string, { csvPath: string; rows: number }>();
let totalRows = 0;
for (const [pairKey, writer] of this.relWriters) {
writer.close();
if (writer.poison !== undefined) errors.push(writer.poison);
relsByPair.set(pairKey, { csvPath: writer.csvPath, rows: writer.rows });
totalRows += writer.rows;
}
if (errors.length > 0) {
const first = errors[0];
throw new Error(
`GraphEmitSink: ${errors.length} streamed CSV writer(s) hit an IO error ` +
`(disk-full / out-of-fds) during the emit — the persisted graph would be ` +
`truncated, so the run is failed rather than COPYing a partial CSV: ${
first instanceof Error ? first.message : String(first)
}`,
);
}
return { relsByPair, totalRows, structuralRows: this.structuralRows };
}
/** Best-effort fd release for the error path — when the pipeline throws
* before {@link finalize} runs, the caller's `finally` calls this so the
* per-pair fds never leak. Idempotent with finalize via `finalized`. */
close(): void {
if (this.finalized) return;
this.finalized = true;
for (const writer of this.relWriters.values()) {View on GitHub (pinned to d540b00184)
Solutions
- Free disk space on the volume holding the GitNexus storage directory (`.gitnexus/`).
- Raise the file-descriptor limit (`ulimit -n 65536`) before running analyze, especially in containers.
- Move the storage directory to a local non-overlay filesystem if you are on a network mount.
- Re-run `gitnexus analyze --force` after freeing resources — the emit is not transactional and a partial CSV may have been written.
Example fix
// before — CI defaults to a low fd limit ulimit -n 1024 # → GraphEmitSink: 3 streamed CSV writer(s) hit an IO error (out-of-fds)... // after ulimit -n 65536 gitnexus analyze --force repo
Defensive patterns
Strategy: validation
Validate before calling
import { checkDiskSpace } from './disk-utils.js'; // your helper
// Before analyze, ensure the storage volume has headroom and fds are plentiful
const free = await checkDiskSpace(storageDir);
const minBytes = estimatedGraphBytes * 2; // CSVs + DB
if (free < minBytes) {
throw new Error(`Insufficient disk space: ${free} bytes free, need ~${minBytes}.`);
}
// Raise fd limit if the repo will open many relationship-pair writers
if (Number(process.env.UV_THREADPOOL_SIZE ?? 4) < 16 && manyRelPairs) {
throw new Error('Raise ulimit -n before emitting a wide relationship set.');
} Try / catch
try {
sink.finalize();
} catch (err) {
if (/GraphEmitSink.*IO error/i.test(err.message)) {
// disk-full / out-of-fds: free space + raise ulimit, then --force rebuild.
console.error(err.message, '— free disk, raise `ulimit -n`, re-run with --force');
process.exit(5);
}
throw err;
} Prevention
- Raise `ulimit -n` (e.g. 65536) in CI/containers before analyzing large repos.
- Ensure the storage volume has at least 2x the estimated graph size free.
- Keep the storage directory on a local filesystem, not a network/overlay mount.
When it happens
Trigger: A disk-full condition, out-of-file-descriptors (EMFILE/ENFILE), a write/flush error, or a writer-open failure during the relationship CSV emit — any of these poison a writer, and `finalize()` surfaces the aggregate count with the first error's message.
Common situations: Analyzing a very large repo that exhausts disk space in the staging dir; a CI/container with a low `ulimit -n` that runs out of fds because there is one writer per relationship pair; a network/overlay filesystem that errors under heavy write load.
Related errors
- PdgEmitSink: ${errors.length} streamed CSV writer(s) hit an
- short write while staging candidate overlay
- short write while copying task assets
- short write while staging oracle
- short write while staging overlay replacement
AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12).
Data as JSON: /api/errors/5562014cbb3ad25c.
Report an issue: GitHub.