fix(remote): report what pg_restore printed when schema sync fails - #215
Merged
Conversation
pipeTo already reads the restore's stderr and re-emits it, but the only subscriber forwards to a websocket that CI has no client for. A failed job therefore reported an exit code while pg_restore had already named the extension or collation it could not create. Closes Query-Doctor/Site#3836
There was a problem hiding this comment.

Query Doctor — 6 successful checks
More details via MCP → get_ci_run({ runId: "019fcae8-3895-7485-b878-a1363143dc62" }) · view run · docs
3 queries read against main on assumed statistics of 10,000,000 rows per table. Sync production stats for costs measured against your real data.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal
A failed schema sync should say what failed. It reported an exit code, and the explanation it had already captured went to a websocket that CI does not subscribe to. Closes Query-Doctor/Site#3836.
Found by putting four open-source repositories through the agent onboarding flow. Two of them, NetBox and Immich, failed here.
What
Before:
That was the only diagnostic in a 1,981-line CI log. Not one line of
pg_restoreoutput appeared anywhere.After:
Both blocks above are real output from the same scenario run against this branch and against
main, not illustrations.NetBox cost two 11-minute CI runs and was still unexplained afterwards. The cause turned out to be Query-Doctor/Site#2762, open since April: NetBox declares an ICU collation and the analyzer's Postgres is built
--without-icu. One line of the discarded output would have identified it on the first run.How
Read
command-failure.tsfirst, then thepipeSchemachange inremote.ts.pipeToalready reads the restore's stderr chunk by chunk and re-emits each one. The only subscriber forwards them to a websocket, which the live UI attaches to and a CI job does not, so the lines were produced and then dropped.pipeSchemanow keeps them as it forwards them, and hands them todescribeCommandFailurewhen the command reports failure.describeCommandFailurekeeps the end of the output rather than the start.pg_restoreprints one line per object it could not create and then a summary, so truncating from the end would drop the line that says how the run finished. The cap is 2000 characters, marked when it applies.Both the dump and the restore go through it, since a failing dump had the same hole.
Tests
command-failure.test.tscovers the message: it carries the captured output, it still names the command and code when nothing was captured, it keeps the tail of a long output and stays bounded, and it handles a process that exited on a signal rather than a code. The first case uses the exactpg_restoretext from the Immich failure. I wrote these before the module existed and confirmed them red.End-to-end, against real Postgres. I drove
Remote.syncFromthrough a genuinely failing restore: a source container holding a table, and a target whosetemplate1already holds the same table, so the freshly created optimizing database collides on it. Onmainthat producedRestore failed with status 1. On this branch it produced the block quoted above, naming the object and the reason. Same script, same containers, one file changed between the two runs.npm run typecheckis clean andsrc/syncpasses at 42 across 8 files.What is not here
That end-to-end check is a script, not a committed test. I tried three times to express it as a vitest case in
remote.test.tsand it hangs on container startup every time, while the identical sequence completes in about two seconds outside vitest. The neighbouringsyncs correctlytest starts two containers the same way and passes, so this is something specific to that file's harness that I could not pin down in reasonable time. I removed the test rather than commit one that hangs, and I would rather flag the gap than leave a 120-second timeout in the suite.I also confirmed one thing the original bug report got wrong:
RestoreCommandalready passes--no-owner --no-acl, so object ownership is not a failure mode here. The ownership theory in the Site issue's evidence came from a hand-rolledpg_restorethat did not use the flags this code uses.