ipfs/kubo · warning

r.String()

Error message

r.String()

What it means

When FilterPinned finds a CID that IS pinned, it does not return an error; it emits a RemovedBlock whose Error field contains the text of pin.Info r.String() — i.e. a human-readable reason such as 'recursive', 'direct', or 'through <parent>'. This is the library's way of telling the caller per-CID why a block was skipped from removal, not a real failure.

Source

Thrown at blocks/blockstoreutil/remove.go:91

// FilterPinned takes a slice of Cids and returns it with the pinned Cids
// removed. If a Cid is pinned, it will place RemovedBlock objects in the given
// out channel, with an error which indicates that the Cid is pinned.
// This function is used in RmBlocks to filter out any blocks which are not
// to be removed (because they are pinned).
func FilterPinned(ctx context.Context, pins pin.Pinner, out chan<- any, cids []cid.Cid) []cid.Cid {
	stillOkay := make([]cid.Cid, 0, len(cids))
	res, err := pins.CheckIfPinned(ctx, cids...)
	if err != nil {
		out <- &RemovedBlock{Error: fmt.Errorf("pin check failed: %w", err)}
		return nil
	}
	for _, r := range res {
		if !r.Pinned() {
			stillOkay = append(stillOkay, r.Key)
		} else {
			out <- &RemovedBlock{
				Hash:  r.Key.String(),
				Error: errors.New(r.String()),
			}
		}
	}
	return stillOkay
}

View on GitHub (pinned to 329838acdf)

Solutions

  1. Treat this RemovedBlock.Error as informational: the block was skipped because it is pinned
  2. Unpin first with `ipfs pin rm <cid>`, then retry the block removal
  3. Filter pins beforehand by calling pins.CheckIfPinned yourself and excluding pinned CIDs
  4. Parse the info string or, better, consume CheckIfPinned results directly instead of the string

Example fix

// before
out <- &RemovedBlock{Hash: r.Key.String(), Error: errors.New(r.String())}
// after: caller-side guard
res, _ := pins.CheckIfPinned(ctx, cids...)
var removable []cid.Cid
for _, r := range res { if !r.Pinned() { removable = append(removable, r.Key) } }
Defensive patterns

Strategy: type-guard

Validate before calling

res, err := pins.CheckIfPinned(ctx, cids...)
var removable []cid.Cid
for _, r := range res {
    if !r.Pinned() { removable = append(removable, r.Key) }
}

Type guard

func isPinnedNotice(err error) bool {
    // pinned-skip 'errors' carry the pin info string, not a failure
    return err != nil && (strings.Contains(err.Error(), "recursive") ||
        strings.Contains(err.Error(), "direct") ||
        strings.Contains(err.Error(), "through "))
}

Try / catch

for rb := range out {
    b, ok := rb.(*blockstoreutil.RemovedBlock)
    if ok && b.Error != nil && isPinnedNotice(b.Error) {
        log.Printf("skipped %s (pinned): %s", b.Hash, b.Error)
        continue // informational, not a failure
    }
}

Prevention

When it happens

Trigger: Calling RmBlocks (or 'ipfs block rm') with CIDs that are pinned directly, recursively, or indirectly through a parent pin; the pinned info string becomes the per-block 'error'.

Common situations: Scripts doing 'ipfs block rm <cid>' on blocks still pinned by 'ipfs pin add'; batch GC scripts that did not run 'ipfs pin rm' first; confusion when per-block errors look like failures but mean 'skipped, pinned'.

Related errors


AI-assisted analysis of ipfs/kubo@329838acdf (2026-09-03). Data as JSON: /api/errors/f11d458b8d15da77. Report an issue: GitHub.