jackc/pgx · warning

failed to remove large object

Error message

failed to remove large object

What it means

Returned by LargeObjects.Unlink when SELECT lo_unlink($1) scanned a result that is not 1 (large_objects.go:56-58). lo_unlink returns 1 on success and 0 when the large object did not exist (or could not be removed), so this error means the OID was not found / not removable. A privilege or transaction error would instead surface as the query error from QueryRow before this check.

Source

Thrown at large_objects.go:57

func (o *LargeObjects) Open(ctx context.Context, oid uint32, mode LargeObjectMode) (*LargeObject, error) {
	var fd int32
	err := o.tx.QueryRow(ctx, "select lo_open($1, $2)", oid, mode).Scan(&fd)
	if err != nil {
		return nil, err
	}
	return &LargeObject{fd: fd, tx: o.tx, ctx: ctx}, nil
}

// Unlink removes a large object from the database.
func (o *LargeObjects) Unlink(ctx context.Context, oid uint32) error {
	var result int32
	err := o.tx.QueryRow(ctx, "select lo_unlink($1)", oid).Scan(&result)
	if err != nil {
		return err
	}

	if result != 1 {
		return errors.New("failed to remove large object")
	}

	return nil
}

// A LargeObject is a large object stored on the server. It is only valid within the transaction that it was initialized
// in. It uses the context it was initialized with for all operations. It implements these interfaces:
//
//	io.Writer
//	io.Reader
//	io.Seeker
//	io.Closer
type LargeObject struct {
	ctx context.Context
	tx  Tx
	fd  int32
}

View on GitHub (pinned to ec1a0befd2)

Solutions

  1. Treat this as 'not found' rather than fatal — ignore the error if idempotent deletion is desired.
  2. Verify the OID via SELECT loid FROM pg_largeobject_metadata WHERE oid=$1 before unlinking if you need to distinguish missing vs. failure.
  3. Ensure Unlink runs inside the same transaction that owns the large object.
  4. Re-read the OID fresh (e.g. via lo_create or pg_largeobject_metadata) rather than caching across sessions.

Example fix

// before
if err := lo.Unlink(ctx, oid); err != nil { return err } // fails if already gone

// after — tolerate already-removed
if err := lo.Unlink(ctx, oid); err != nil {
    if err.Error() == "failed to remove large object" { /* already gone */ return nil }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

// confirm existence first if you need to distinguish missing vs. failure
var exists int
conn.QueryRow(ctx, "select count(*) from pg_largeobject_metadata where oid=$1", oid).Scan(&exists)
if exists == 0 { return nil } // idempotent unlink

Try / catch

if err := los.Unlink(ctx, oid); err != nil {
    if err.Error() == "failed to remove large object" {
        // already gone — treat as success
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Calling Unlink(ctx, oid) with an OID that does not correspond to an existing large object, or where the server could not delete it (e.g. it was already unlinked, or the OID is invalid).

Common situations: Deleting a large object that was already removed in another transaction; passing a stale OID cached from a prior session; a race where another process unlinked first; calling Unlink outside a transaction (large objects are transactional).

Related errors


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