jackc/pgx · error

failed to write to large object

Error message

failed to write to large object

What it means

Returned by LargeObject.Write when SELECT lowrite($1,$2) scanned a byte count n < 0 (large_objects.go:93-95). The lowrite server function returns -1 (and raises an exception) on internal failure; pgx surfaces the negative count as this error, meaning the server rejected the write (e.g. the descriptor is invalid, the large object was not opened for writing, or the descriptor's transaction ended).

Source

Thrown at large_objects.go:94

// Write writes p to the large object and returns the number of bytes written and an error if not all of p was written.
func (o *LargeObject) Write(p []byte) (int, error) {
	nTotal := 0
	for {
		expected := len(p) - nTotal
		if expected == 0 {
			break
		} else if expected > maxLargeObjectMessageLength {
			expected = maxLargeObjectMessageLength
		}

		var n int
		err := o.tx.QueryRow(o.ctx, "select lowrite($1, $2)", o.fd, p[nTotal:nTotal+expected]).Scan(&n)
		if err != nil {
			return nTotal, err
		}

		if n < 0 {
			return nTotal, errors.New("failed to write to large object")
		}

		nTotal += n

		if n < expected {
			return nTotal, errors.New("short write to large object")
		} else if n > expected {
			return nTotal, errors.New("invalid write to large object")
		}
	}

	return nTotal, nil
}

// Read reads up to len(p) bytes into p returning the number of bytes read.
func (o *LargeObject) Read(p []byte) (int, error) {
	nTotal := 0
	for {

View on GitHub (pinned to ec1a0befd2)

Solutions

  1. Open the large object with LargeObjectModeWrite (bit 0x20000) before calling Write.
  2. Keep all Write/Read/Seek/Close calls within the same transaction that called LargeObjects.Open; commit/rollback invalidates the fd.
  3. Do not call Close() before Write(); structure the lifecycle: Open → Write → Close within one tx.
  4. Use Tx.LargeObjects() to obtain the LargeObjects handle tied to the active transaction.

Example fix

// before
lo, _ := los.Open(ctx, oid, pgx.LargeObjectModeRead) // wrong mode
_, err := lo.Write(data) // "failed to write to large object"

// after
lo, err := los.Open(ctx, oid, pgx.LargeObjectModeWrite)
if err != nil { return err }
if _, err := lo.Write(data); err != nil { return err }
return lo.Close()
Defensive patterns

Strategy: try-catch

Validate before calling

// open with Write mode and verify fd >= 0 before writing
lo, err := los.Open(ctx, oid, pgx.LargeObjectModeWrite)
if err != nil { return err }
// keep within the same transaction

Type guard

func openForWrite(m pgx.LargeObjectMode) bool {
    return m&pgx.LargeObjectModeWrite != 0
}

Try / catch

if _, err := lo.Write(p); err != nil {
    if err.Error() == "failed to write to large object" {
        // re-open in Write mode within a live transaction, then retry
    }
    return err
}

Prevention

When it happens

Trigger: Calling Write on a LargeObject whose fd is stale: opened in LargeObjectModeRead instead of Write, already Closed, or used after its creating transaction committed/rolled back (large objects are only valid within their creating transaction).

Common situations: Forgetting LargeObjectModeWrite when opening; writing after Close(); writing across a transaction boundary (the fd is invalid once the transaction ends); using a LargeObject from a different transaction.

Related errors


AI-assisted analysis of jackc/pgx@ec1a0befd2 (2026-08-04). Data as JSON: /data/errors/e2b80118dbda184e.json. Report an issue: GitHub.