bytebase/bytebase · error
failed to send signal
Error message
failed to send signal
What it means
SendSignal publishes a payload via pg_notify($1, $2) on the store's LISTEN/NOTIFY channel. This error wraps any failure of that ExecContext call — the payload was marshalled successfully but Postgres refused or could not execute the NOTIFY. Causes include a dead/broken DB connection, the notification payload exceeding NOTIFY's 8000-byte limit, or the session being in a failed transaction state.
Source
Thrown at backend/store/signal.go:30
// SignalChannel is the PostgreSQL NOTIFY channel for HA coordination.
const SignalChannel = "bytebase_signal"
// SendSignal sends a notification to the bytebase_signal channel.
func (s *Store) SendSignal(ctx context.Context, signalType storepb.Signal_Type, projectID string, uid int64, approvalInputVersion *int64) error {
signal := &storepb.Signal{
Type: signalType,
Uid: uid,
Project: projectID,
}
if approvalInputVersion != nil {
signal.ApprovalInputVersion = *approvalInputVersion
}
payload, err := protojson.Marshal(signal)
if err != nil {
return errors.Wrap(err, "failed to marshal signal payload")
}
_, err = s.GetDB().ExecContext(ctx, "SELECT pg_notify($1, $2)", SignalChannel, string(payload))
return errors.Wrap(err, "failed to send signal")
}
View on GitHub (pinned to 1870550677)
Solutions
- Check the wrapped error for context (connection reset, context deadline, or 'payload string too long').
- Log the marshalled payload length before calling SendSignal and trim/split signals approaching the 8000-byte NOTIFY limit.
- Verify metadata DB connectivity (PG_URL) and that the connection pool is healthy; retry transient failures with backoff.
- Ensure SendSignal is not invoked from within an aborted transaction; run it on a fresh session/connection.
Example fix
// before
_, err = s.GetDB().ExecContext(ctx, "SELECT pg_notify($1, $2)", SignalChannel, string(payload))
// after
payloadStr := string(payload)
if len(payloadStr) > 8000 {
return errors.Errorf("signal payload %d bytes exceeds pg_notify 8000-byte limit", len(payloadStr))
}
_, err = s.GetDB().ExecContext(ctx, "SELECT pg_notify($1, $2)", SignalChannel, payloadStr) Defensive patterns
Strategy: retry
Validate before calling
payloadStr := string(marshalledPayload)
if len(payloadStr) > 8000 {
return errors.Errorf("signal payload %d bytes exceeds pg_notify 8000-byte limit", len(payloadStr))
}
if err := ctx.Err(); err != nil {
return err // context already cancelled
} Try / catch
err := store.SendSignal(ctx, ws, signal)
if err != nil && strings.Contains(err.Error(), "failed to send signal") {
// transient DB failure: retry with backoff
select {
case <-time.After(2 * time.Second):
err = store.SendSignal(ctx, ws, signal)
case <-ctx.Done():
return ctx.Err()
}
} Prevention
- Keep signal payloads small; NOTIFY is capped at 8000 bytes.
- Call SendSignal outside of transactions so an aborted tx cannot poison the notify.
- Monitor metadata DB connectivity; alert on connection resets.
- Never ignore the error — the cancel action silently won't reach listeners if dropped.
When it happens
Trigger: Calling CancelPlanCheckRun or BatchCancelTaskRuns when: the metadata DB connection is down or dropped; the protojson payload string exceeds Postgres NOTIFY's 8000-byte maximum; the context is cancelled/timed out before the query runs; or the call is made inside an aborted transaction.
Common situations: Network blips between the Bytebase server and Postgres; very large check-run payloads after schema growth; connection pool exhaustion returning stale connections; Postgres restarts during deploys.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
AI-assisted analysis of bytebase/bytebase@1870550677 (2026-09-06).
Data as JSON: /api/errors/cd4d2101e9725e59.
Report an issue: GitHub.