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

  1. Check the wrapped error for context (connection reset, context deadline, or 'payload string too long').
  2. Log the marshalled payload length before calling SendSignal and trim/split signals approaching the 8000-byte NOTIFY limit.
  3. Verify metadata DB connectivity (PG_URL) and that the connection pool is healthy; retry transient failures with backoff.
  4. 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

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.