Summary
When cloudsync_payload_apply fails and no error message reached it, the generic fallback is all the caller gets — the return code and SQLSTATE are dropped from the text. A lock, busy or serialization failure then looks exactly like any other apply error.
src/cloudsync.c:4690:
cloudsync_set_error(data, fail_message[0] ? fail_message : "Unable to apply payload changes", rc);
fail_message is captured at :4662/:4675 from cloudsync_errmsg(data), and fail_sqlstate at :4663. When the message is empty the fallback carries neither.
How it showed up
A CI Peer Test failure on one platform (run 36431727289), surfaced by the server as:
{"status":"422","code":"database_cloudsync_not_ready",
"detail":"CloudSync is not available, ready, or correctly configured on database: Unable to apply payload changes"}
It took several steps to establish that this was a transient concurrency failure rather than a version incompatibility or a code regression — a re-run passed with no change. The message on its own gave no way to tell. Every matrix leg targets the same integration database and Peer Test runs five threads, so concurrent applies against one tenant are routine there.
Had the code been in the message, the class of failure would have been visible immediately: on PostgreSQL a 40001 says serialization failure, on SQLite a 5/6 says busy or locked.
Suggested change
cloudsync_set_error takes a plain string with no formatting variant, so the message has to be built first:
char fallback[128];
if (!fail_message[0]) {
// No message reached us with the failure, so carry the codes instead: without
// them a lock, busy or serialization failure is indistinguishable from any
// other apply error in a server log.
if (fail_sqlstate)
snprintf(fallback, sizeof(fallback), "Unable to apply payload changes (code %d, sqlstate %d)", rc, fail_sqlstate);
else
snprintf(fallback, sizeof(fallback), "Unable to apply payload changes (code %d)", rc);
}
cloudsync_set_error(data, fail_message[0] ? fail_message : fallback, rc);
The literal is not matched anywhere in the cloudsync repo, so extending it does not break server-side handling.
Notes
This is diagnostic only. It does not fix whatever returns a non-OK fail_rc with cloudsync_errmsg() empty — it makes the next occurrence identifiable rather than requiring an investigation. Finding and fixing that path is the real follow-up, and the code in the message is how you would identify it.
There is an alternative place for it. rc is already passed to cloudsync_set_error as err_code, so it is not lost inside the extension — it is dropped on the way out through the server's HTTP error. Surfacing the code alongside detail in cloudsync would fix the same diagnosability gap without touching the extension, and would help every error that travels that path, not just this one. Worth deciding which side should own it before implementing.
Introduced with the apply cleanup in #65.
Summary
When
cloudsync_payload_applyfails and no error message reached it, the generic fallback is all the caller gets — the return code and SQLSTATE are dropped from the text. A lock, busy or serialization failure then looks exactly like any other apply error.src/cloudsync.c:4690:fail_messageis captured at:4662/:4675fromcloudsync_errmsg(data), andfail_sqlstateat:4663. When the message is empty the fallback carries neither.How it showed up
A CI
Peer Testfailure on one platform (run 36431727289), surfaced by the server as:{"status":"422","code":"database_cloudsync_not_ready", "detail":"CloudSync is not available, ready, or correctly configured on database: Unable to apply payload changes"}It took several steps to establish that this was a transient concurrency failure rather than a version incompatibility or a code regression — a re-run passed with no change. The message on its own gave no way to tell. Every matrix leg targets the same integration database and
Peer Testruns five threads, so concurrent applies against one tenant are routine there.Had the code been in the message, the class of failure would have been visible immediately: on PostgreSQL a
40001says serialization failure, on SQLite a5/6says busy or locked.Suggested change
cloudsync_set_errortakes a plain string with no formatting variant, so the message has to be built first:The literal is not matched anywhere in the cloudsync repo, so extending it does not break server-side handling.
Notes
This is diagnostic only. It does not fix whatever returns a non-OK
fail_rcwithcloudsync_errmsg()empty — it makes the next occurrence identifiable rather than requiring an investigation. Finding and fixing that path is the real follow-up, and the code in the message is how you would identify it.There is an alternative place for it.
rcis already passed tocloudsync_set_erroraserr_code, so it is not lost inside the extension — it is dropped on the way out through the server's HTTP error. Surfacing the code alongsidedetailin cloudsync would fix the same diagnosability gap without touching the extension, and would help every error that travels that path, not just this one. Worth deciding which side should own it before implementing.Introduced with the apply cleanup in #65.