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
- Treat this RemovedBlock.Error as informational: the block was skipped because it is pinned
- Unpin first with `ipfs pin rm <cid>`, then retry the block removal
- Filter pins beforehand by calling pins.CheckIfPinned yourself and excluding pinned CIDs
- 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
- Unpin before block rm: `ipfs pin rm <cid>`
- Check pins with `ipfs pin ls <cid>` before removal
- Treat RemovedBlock.Error on pinned blocks as status, not failure
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
- pin check failed: %w
- assets: could load Asset '%s': %s
- ipfs api address could not be found
- unexpected redirect
- unsupported file type '%s'
AI-assisted analysis of ipfs/kubo@329838acdf (2026-09-03).
Data as JSON: /api/errors/f11d458b8d15da77.
Report an issue: GitHub.