PostgreSQL logo PostgreSQL Troubleshooting

Common issues

library "wal2json" may not be used as an output plugin

Symptom: every entity of a pipeline that sources from PostgreSQL goes into error and restarts in a loop, producing a burst of notifications. The agent logs report:

SQL Error: Code 0, State 42501, Message ERROR: library "wal2json" may not be used as an output plugin

The exception class is org.postgresql.util.PSQLException and the server adds its own hint:

HINT: If it is safe for all REPLICATION users to use this library as an output plugin,
add it to "output_plugin_libraries" and reload the server configuration.

SQLSTATE 42501 means insufficient_privilege, so this failure looks like a missing grant on the replication user. It is not. The user can hold the REPLICATION privilege, be able to log in, and read every source table, and the CDC stream will still be refused.

Cause: the PostgreSQL minor releases 18.6, 17.11, 16.15, 15.19 and 14.24, published on 13 August 2026, fix CVE-2026-6471. Before the fix, a user with the REPLICATION privilege could make the server load an arbitrary library as a logical decoding output plugin, which meant arbitrary code execution as the operating system user running the server.

The fix introduces the output_plugin_libraries parameter, whose default value is 'pgoutput, test_decoding', the two output plugins shipped with the standard PostgreSQL distribution. It is a pure allowlist: there is no wildcard value, and the restriction applies to every user, superusers included.

Gluesync reads changes from PostgreSQL through the wal2json output plugin, which on an upgraded server is therefore no longer authorised by default. See Replication configuration parameters in the PostgreSQL documentation for the parameter reference.

The refusal does not appear when the PostgreSQL packages are upgraded. It appears at the first restart of the PostgreSQL service after the upgrade, because the replication stream that is already open survives the package installation.

On distributions that apply security updates automatically (for example unattended-upgrades on Ubuntu) the upgrade may already be in place without the administrator being aware of it. Always check the version the server is actually running, not the date of the last manual maintenance.

The PostgreSQL documentation also requires the plugin name sent by the client to match an entry of the list exactly, "without variations in case or path structure". The entry must therefore be exactly wal2json.

Impact on data

The consequences differ depending on whether the entity was already replicating before the upgrade.

  • Entities already in CDC before the upgrade: the replication slot exists and stops advancing, so PostgreSQL keeps retaining the WAL segments the slot still needs. No change is lost, and once the parameter is fixed CDC resumes from the point where it stopped.

  • Pipelines created after the upgrade: the replication slot is never created, so there is no WAL retention and the changes that happen in the meantime cannot be recovered through CDC. A snapshot of the affected entities is required to realign the target.

While the problem is unresolved, monitor the size of the pg_wal directory on the PostgreSQL server. The stalled slots hold WAL segments indefinitely and a full database filesystem is a considerably more serious outcome than a stopped replication.

Confirming the diagnosis

A useful discriminator: if the same PostgreSQL server is also used as a target by another pipeline, that pipeline keeps working with no errors, because writing to the target does not go through logical decoding. Network reachability, credentials and grants are ruled out by that alone.

  1. Check the running version and date the restart that activated the restriction:

    SELECT version();
    SELECT pg_postmaster_start_time();
  2. Inspect the parameter:

    SELECT name, setting, boot_val, source, context, pending_restart
    FROM pg_settings
    WHERE name = 'output_plugin_libraries';
    if this query returns no rows, the server does not have the security update and the cause of the failure is something else.
  3. List the replication slots and check which ones are no longer advancing:

    SELECT slot_name, plugin, active, wal_status, restart_lsn, confirmed_flush_lsn
    FROM pg_replication_slots;
  4. Reproduce the refusal directly, connected to the database used by the pipeline:

    SELECT pg_create_logical_replication_slot('gs_test_wal2json', 'wal2json');

    The statement fails with the same message when the restriction is in place. If it succeeds, drop the slot immediately, because an unused slot retains WAL:

    SELECT pg_drop_replication_slot('gs_test_wal2json');

Resolution

The list must be extended, not replaced. Other replication tools running against the same server may rely on their own output plugins, and overwriting the value would silently disable them. Include every plugin that must stay allowed as its own SQL string literal: any names from SELECT DISTINCT plugin FROM pg_replication_slots, plus pgoutput, test_decoding and wal2json as applicable. The default allowlist is pgoutput, test_decoding.

ALTER SYSTEM SET with a single SQL string of comma-separated names stores one list element, the whole quoted string. CREATE SLOT then still fails with library "wal2json" may not be used as an output plugin. The working form uses a separate SQL string literal for each plugin.

  1. Read the live value and the plugins already in use:

    SELECT name, setting, source, sourcefile
    FROM pg_settings WHERE name = 'output_plugin_libraries';
    
    SELECT DISTINCT plugin FROM pg_replication_slots WHERE plugin IS NOT NULL;
  2. Reset any value previously written by ALTER SYSTEM, then set the allowlist with separate string literals, reload, and confirm:

    ALTER SYSTEM RESET output_plugin_libraries;
    ALTER SYSTEM SET output_plugin_libraries = 'pgoutput', 'test_decoding', 'wal2json';
    SELECT pg_reload_conf();
    SELECT name, setting, source, sourcefile
    FROM pg_settings WHERE name = 'output_plugin_libraries';
    -- expected setting: pgoutput, test_decoding, wal2json
    -- NO extra quotes around the whole list
    -- sourcefile: .../postgresql.auto.conf
    SELECT pg_create_logical_replication_slot('gs_test_wal2json', 'wal2json');
    SELECT pg_drop_replication_slot('gs_test_wal2json');

    Add any other plugin from the slots query as a further SQL string literal in the same SET. A reload is enough; a restart is not required. Do not drop existing gs_* slots.

    If SHOW output_plugin_libraries or pg_settings.setting still has quotes around the whole list (it looks like "pgoutput, test_decoding, wal2json" as one name), the value is poisoned: PostgreSQL stored one list element. That is the stuck state created by setting the GUC from a single quoted comma-separated string. Run RESET then SET with separate literals, as above.

ALTER SYSTEM writes postgresql.auto.conf in the data directory. That file wins over /etc/postgresql/…​/postgresql.conf. A missing line in the /etc file does not mean the GUC is unset.

The same change can be applied in postgresql.conf instead. That syntax is different from ALTER SYSTEM SET, and it must be done after ALTER SYSTEM RESET output_plugin_libraries;, otherwise the poisoned postgresql.auto.conf value still wins:

output_plugin_libraries = 'pgoutput, test_decoding, wal2json'

Then run SELECT pg_reload_conf();. A reload is enough; a restart is not required.

Once the test CREATE SLOT succeeds, entities that were already in CDC resume from the checkpoint on their own at the next retry. No checkpoint reset and no pipeline reconfiguration are required. A snapshot is needed only for pipelines created after the upgrade, where the replication slot was never created.

if PostgreSQL is maintained with automatic updates, set output_plugin_libraries explicitly now, so that future upgrades cannot reintroduce the problem.