fix(graph-buffer): log why an atomic publish failed instead of a successful dump

cbm_gbuf_dump_to_sqlite() emitted gbuf.dump regardless of the writer's
result, so a run that published nothing still logged node and edge counts
as if it had worked, and the failure reached the user only as the generic
"Pipeline failed. Check repo_path exists and contains source files."

The writer's temp -> final rename in publish_writer_output() returned a
bare ERR_WRITE_FAILED. #1628 taught cbm_rename_replace() to translate the
platform error into errno so callers could report why, and wired up the
stage -> final rename in pipeline.c. This is the other rename on that
path, and it is the one that runs first.

Preserve errno across the cleanup in every publish error branch. That
includes cbm_writer_open(), where the cleanup unlinks a file that was never
created: without the save it leaves ENOENT behind, so a directory that
denied the create is reported as a missing path — the #1620 case, stated
wrongly. Read the value in graph_buffer.c before the profiling macro can
clobber it, and report gbuf.dump_failed with the return code and that errno.
A sticky append failure clears errno rather than attach a reason it does not
have, and the field is omitted when there is none. No logging is added
inside internal/cbm/, which has none today.

Tests cover both halves: the dump reports a failure instead of a summary,
the writer names its truncation reason, and the publish rename's errno
survives a cleanup that itself fails.

Refs #1620, #2001.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012FxkVF773gDGNmBmsLnzN7
Signed-off-by: juan Carlos Estrada Montoya <junk151516@users.noreply.github.com>
This commit is contained in:
juan Carlos Estrada Montoya
2026-09-20 17:52:45 +02:00
committed by Martin Vogel
co-authored by Claude Opus 5
parent 92abefa3f5
commit 460ebefc87
4 changed files with 233 additions and 1 deletions
+37 -1
View File
@@ -39,6 +39,7 @@ enum {
#include "foundation/mem_core.h"
#include <sqlite3.h>
#include <errno.h>
#include <stdatomic.h>
#include <stdint.h> // int64_t
#include <stdio.h>
@@ -1841,6 +1842,32 @@ static void log_dump_summary(int node_count, int edge_count) {
cbm_log_info("gbuf.dump", "nodes", b1, "edges", b2);
}
/* A dump that published nothing gets exactly one signature in the log.
* Without it the writer's return code is the only evidence, and it reaches
* the user as a generic pipeline failure that blames their repository
* (#1620, #2001).
*
* failure_errno is read by the caller at the point of failure: the value is
* only meaningful before the next library call. Zero means the failure did
* not originate at the publish boundary — an append that failed many calls
* earlier — and the field is omitted rather than claim a reason this record
* does not have. */
static void log_dump_failed(int rc, int failure_errno, int node_count, int edge_count) {
char b1[CBM_SZ_16];
char b3[CBM_SZ_16];
char b4[CBM_SZ_16];
snprintf(b1, sizeof(b1), "%d", rc);
snprintf(b3, sizeof(b3), "%d", node_count);
snprintf(b4, sizeof(b4), "%d", edge_count);
if (failure_errno == 0) {
cbm_log_error("gbuf.dump_failed", "rc", b1, "nodes", b3, "edges", b4);
return;
}
char b2[CBM_SZ_16];
snprintf(b2, sizeof(b2), "%d", failure_errno);
cbm_log_error("gbuf.dump_failed", "rc", b1, "errno", b2, "nodes", b3, "edges", b4);
}
static void free_dump_resources(char **url_paths, char **local_names, int edge_count,
CBMDumpEdge *dump_edges, CBMDumpNode *dump_nodes,
int64_t *temp_to_final) {
@@ -1896,6 +1923,7 @@ int cbm_gbuf_dump_to_sqlite(cbm_gbuf_t *gb, const char *path) {
int64_t max_temp_id = gb->next_id;
int64_t *temp_to_final = cbm_calloc(CBM_MEM_CLASS_DUMP, (size_t)max_temp_id * sizeof(int64_t));
if (!temp_to_final) {
log_dump_failed(CBM_NOT_FOUND, errno, 0, 0);
return CBM_NOT_FOUND;
}
@@ -1926,6 +1954,7 @@ int cbm_gbuf_dump_to_sqlite(cbm_gbuf_t *gb, const char *path) {
* uninitialized budget from ever triggering the free). */
cbm_db_writer_t *w = cbm_writer_open(path);
if (!w) {
log_dump_failed(CBM_NOT_FOUND, errno, node_idx, 0);
cbm_free(CBM_MEM_CLASS_DUMP, src_nodes);
cbm_free(CBM_MEM_CLASS_DUMP, dump_nodes);
cbm_free(CBM_MEM_CLASS_DUMP, temp_to_final);
@@ -1980,12 +2009,19 @@ int cbm_gbuf_dump_to_sqlite(cbm_gbuf_t *gb, const char *path) {
int frc = cbm_writer_finalize(w, gb->project, gb->root_path, indexed_at, dump_nodes, node_idx,
dump_edges, edge_idx, gb->dump_vectors, gb->dump_vector_count,
gb->dump_token_vecs, gb->dump_token_vec_count);
/* Read errno before anything else can touch it: the profiling macro
* below is already one library call away from losing the reason. */
int finalize_errno = errno;
CBM_PROF_END_N("dump", "6_write_db_finalize", t_finalize, node_idx + edge_idx);
if (rc == 0) {
rc = frc;
}
log_dump_summary(node_idx, edge_idx);
if (rc != 0) {
log_dump_failed(rc, finalize_errno, node_idx, edge_idx);
} else {
log_dump_summary(node_idx, edge_idx);
}
free_dump_resources(url_paths, local_names, edge_idx, dump_edges, dump_nodes, temp_to_final);
cbm_free(CBM_MEM_CLASS_DUMP, src_nodes);
return rc;