{"id":"093474d9805e9d67","repo":"jackc/pgx","slug":"failed-to-remove-large-object","errorCode":null,"errorMessage":"failed to remove large object","messagePattern":"failed to remove large object","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"large_objects.go","lineNumber":57,"sourceCode":"func (o *LargeObjects) Open(ctx context.Context, oid uint32, mode LargeObjectMode) (*LargeObject, error) {\n\tvar fd int32\n\terr := o.tx.QueryRow(ctx, \"select lo_open($1, $2)\", oid, mode).Scan(&fd)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &LargeObject{fd: fd, tx: o.tx, ctx: ctx}, nil\n}\n\n// Unlink removes a large object from the database.\nfunc (o *LargeObjects) Unlink(ctx context.Context, oid uint32) error {\n\tvar result int32\n\terr := o.tx.QueryRow(ctx, \"select lo_unlink($1)\", oid).Scan(&result)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif result != 1 {\n\t\treturn errors.New(\"failed to remove large object\")\n\t}\n\n\treturn nil\n}\n\n// A LargeObject is a large object stored on the server. It is only valid within the transaction that it was initialized\n// in. It uses the context it was initialized with for all operations. It implements these interfaces:\n//\n//\tio.Writer\n//\tio.Reader\n//\tio.Seeker\n//\tio.Closer\ntype LargeObject struct {\n\tctx context.Context\n\ttx  Tx\n\tfd  int32\n}\n","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/jackc/pgx/blob/ec1a0befd22592cffffdeeb0a50311b506372f4c/large_objects.go#L39-L75","documentation":"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.","triggerScenarios":"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).","commonSituations":"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).","solutions":["Treat this as 'not found' rather than fatal — ignore the error if idempotent deletion is desired.","Verify the OID via SELECT loid FROM pg_largeobject_metadata WHERE oid=$1 before unlinking if you need to distinguish missing vs. failure.","Ensure Unlink runs inside the same transaction that owns the large object.","Re-read the OID fresh (e.g. via lo_create or pg_largeobject_metadata) rather than caching across sessions."],"exampleFix":"// before\nif err := lo.Unlink(ctx, oid); err != nil { return err } // fails if already gone\n\n// after — tolerate already-removed\nif err := lo.Unlink(ctx, oid); err != nil {\n    if err.Error() == \"failed to remove large object\" { /* already gone */ return nil }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// confirm existence first if you need to distinguish missing vs. failure\nvar exists int\nconn.QueryRow(ctx, \"select count(*) from pg_largeobject_metadata where oid=$1\", oid).Scan(&exists)\nif exists == 0 { return nil } // idempotent unlink","typeGuard":null,"tryCatchPattern":"if err := los.Unlink(ctx, oid); err != nil {\n    if err.Error() == \"failed to remove large object\" {\n        // already gone — treat as success\n        return nil\n    }\n    return err\n}","preventionTips":["Treat Unlink 'failed to remove' as not-found and tolerate it for idempotent deletes.","Keep large-object operations inside the owning transaction.","Re-read OIDs fresh rather than caching across sessions."],"tags":["large-objects","unlink","not-found"],"analyzedSha":"ec1a0befd22592cffffdeeb0a50311b506372f4c","analyzedAt":"2026-08-04T22:52:11.263Z","schemaVersion":2}