neondatabase/neon · critical
ExceptionalCondition: %s:%d: %s
Error message
ExceptionalCondition: %s:%d: %s
What it means
walproposer_compat.c reimplements Postgres's ExceptionalCondition for the standalone walproposer used by safekeepers. When an internal invariant fails, for example pq_copymsgbytes finding fewer bytes in the message than requested, it prints the failing file, line, and condition name, then terminates the process with exit(1). There is no unwinding or recovery path.
Source
Thrown at pgxn/neon/walproposer_compat.c:19
/*
* Contains copied/adapted functions from libpq and some internal postgres functions.
* This is needed to avoid linking to full postgres server installation. This file
* is compiled as a part of libwalproposer static library.
*/
#include "postgres.h"
#include <stdio.h>
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "utils/datetime.h"
#include "walproposer.h"
void
ExceptionalCondition(const char *conditionName,
const char *fileName, int lineNumber)
{
fprintf(stderr, "ExceptionalCondition: %s:%d: %s\n",
fileName, lineNumber, conditionName);
fprintf(stderr, "aborting...\n");
exit(1);
}
void
pq_copymsgbytes(StringInfo msg, char *buf, int datalen)
{
if (datalen < 0 || datalen > (msg->len - msg->cursor))
ExceptionalCondition("insufficient data left in message", __FILE__, __LINE__);
memcpy(buf, &msg->data[msg->cursor], datalen);
msg->cursor += datalen;
}
/* --------------------------------
* pq_getmsgint - get a binary integer from a message buffer
*
* Values are treated as unsigned.View on GitHub (pinned to 8f60b04da4)
Solutions
- Read the preceding line 'ExceptionalCondition: file:line: condition'; it names the exact failed check
- Verify the compute (neon extension) and safekeeper run compatible neon versions
- Inspect safekeeper logs around the crash and enable walproposer logging to capture the offending message
- If the input was well-formed, preserve logs and report the crash; exit(1) on valid input is a bug
Example fix
/* before: parser asserts (and exits) on truncated input */
pq_copymsgbytes(msg, buf, datalen);
/* after: validate remaining length and reject the message cleanly */
if (datalen < 0 || datalen > (msg->len - msg->cursor)) {
elog(ERROR, "malformed message: need %d bytes, have %zu",
datalen, msg->len - msg->cursor);
}
pq_copymsgbytes(msg, buf, datalen); Defensive patterns
Strategy: validation
Validate before calling
/* check before every parse: never let the compat layer assert */
static bool
enough_bytes(StringInfo msg, int need)
{
return need >= 0 && need <= (msg->len - msg->cursor);
}
if (!enough_bytes(msg, datalen))
return -1; /* reject the frame instead of exiting */ Prevention
- Version-lock compute and safekeeper neon builds
- Validate frame lengths before pq_* parse calls in custom protocol code
- Run safekeepers under a supervisor that restarts on exit while the cause is investigated
- Keep walproposer logging enabled to capture the failing message
When it happens
Trigger: Any assertion inside the walproposer compat layer: pq_copymsgbytes with datalen exceeding the remaining message, pq_getmsgint with an unsupported width, or any other failed check compiled with this handler.
Common situations: Protocol or version mismatch between the compute's walproposer and the safekeeper producing messages the parser rejects; truncated or corrupted frames on the wire; framing bugs after protocol changes.
Related errors
- aborting...
- unsupported integer size %d
- safekeeper {sk_id} does not exist
- Failed to check node status: {e}
- Safekeeper set up for auth but no private key specified
AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16).
Data as JSON: /api/errors/b1f34655bc7e721b.
Report an issue: GitHub.