# 39P01 — trigger_protocol_violated

> Source-backed reference for PostgreSQL SQLSTATE 39P01.
---

# 39P01

## At a glance {#at-a-glance}

39P01 is `trigger_protocol_violated`. Fixed PostgreSQL 18.6 paths cover the core trigger manager, contrib/tcn checks, and PL/Perl trigger return handling, with path-specific messages and severity.

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| Field | Value |
| --- | --- |
| SQLSTATE | `39P01` |
| Condition | `trigger_protocol_violated` |
| Status | `active` |
| Known present by | `7.4` |
| Locked snapshots | `9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3` |
| Macros | `ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED` |
| Aliases | `—` |

<!-- source facts: data/errcodes/39P01.json -->
<!-- END SQLSTATE FACTS -->

## Meaning {#meaning}

The code marks a trigger protocol mismatch. Core trigger-manager messages include `trigger function %u returned null value` and `BEFORE STATEMENT trigger cannot return a value`. `contrib/tcn` checks trigger timing, level, argument count, and primary-key setup; PL/Perl has a DELETE-row WARNING and a separate ERROR for an invalid trigger return.

## Diagnosis {#diagnosis}

Use the exact message to identify the path. For the trigger manager, check the `isnull` flag and BEFORE STATEMENT return contract; for tcn, check AFTER timing, row level, argument count, and primary key; for PL/Perl, check whether the return is `undef`, `SKIP`, or `MODIFY`. The DELETE-row WARNING means a modification was ignored, while the invalid-return message is an ERROR.

## Response {#response}

Correct the timing, level, event, arguments, or return value for the identified path. For PL/Perl, decide whether the ignored DELETE-row modification is acceptable before writing again; retry only after the contract is fixed and the original operation is known not to have completed.

## Versions {#versions}

The locked catalogue records this condition from 7.4 in the listed formal snapshots and 19beta3; fixed source coverage is PostgreSQL 18.6.

## Related {#related}

[`39P02`](../39p02/), [`39P03`](../39p03/), [`2F005`](../2f005/)

## Sources {#sources}

See the fixed [trigger.c](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/commands/trigger.c#L2388-L2394), [tcn.c](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/contrib/tcn/tcn.c#L77-L110), and [plperl.c](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/pl/plperl/plperl.c#L2607-L2618) paths alongside the structured [evidence record](../data/evidence/39p01.json).
