#11454·kubo

namesys is permanently locked out of an IPNS name if a pubsub RPC subscription joins its record topic first

Author: Rinse12Created Sep 7, 2026Updated Sep 7, 2026

Checklist

  • This is a bug report, not a question.
  • I have searched on the issue tracker for my bug.
  • I am running the latest kubo version or have an issue updating.

Installation method

third-party binary (the kubo npm package)

Version

Kubo version: 0.43.0-e9914bb47
Repo version: 18
System version: amd64/linux
Golang version: go1.26.5

Config

Two default --profile=test repos with Pubsub.Enabled=true and Ipns.UsePubsub=true. On the resolving daemon Routing.Type=none, so IPNS can only resolve over pubsub (with the DHT on, the DHT masks the failure but the namesys subscription is still never created, see below).

Description

If a client opens a pubsub sub on an IPNS record topic (/record/<base64url("/ipns/" + multihash)>) before namesys has resolved that name, namesys can never join the topic on that daemon: name resolve <name> fails with could not resolve name, name pubsub subs never lists the name, and cancelling the RPC subscription does not repair it. Only a daemon restart does. Nothing is logged, even with namesys and pubsub-valuestore at debug.

The topic name is deterministic from the IPNS name, so any application that uses the pubsub RPC to watch for IPNS record arrivals (a kubo-rpc-client user, for instance) can lock its own daemon out of a name with one ordering mistake.

Steps to reproduce

Daemon B is the publisher, daemon A the resolver. Both have pubsub enabled and are connected to each other; A has Routing.Type=none.

bash
# B: two keys, both published
ipfs key gen victim
ipfs key gen control
CID=$(echo hello | ipfs add -q)
ipfs name publish --key=victim  --allow-offline --ttl=1m /ipfs/$CID
ipfs name publish --key=control --allow-offline --ttl=1m /ipfs/$CID

The record topic for a name is /record/ + base64url (no padding) of the bytes "/ipns/" + <peer id multihash bytes>, i.e. what KeyToTopic in go-libp2p-pubsub-router produces for the key /ipns/<mh>.

bash
# A: control name, resolve first (namesys joins the topic)
ipfs name resolve --nocache <control>
# -> /ipfs/Qm... (0.2s)
ipfs name pubsub subs
# -> /ipns/<control>

# A: victim name, RPC-subscribe to its record topic first, then resolve
ipfs pubsub sub /record/<topic of victim> &
ipfs name resolve --nocache <victim>
# -> Error: could not resolve name (0.2s)
ipfs name pubsub subs
# -> only /ipns/<control>; the victim never appears

# A: cancel the RPC subscription, resolve again
kill %1
ipfs pubsub ls
# -> the victim topic is still listed
ipfs name resolve --nocache <victim>
# -> Error: could not resolve name

# A: restart the daemon, resolve first, THEN subscribe over the RPC
ipfs name resolve --nocache <victim>
# -> /ipfs/Qm...
ipfs pubsub sub /record/<topic of victim> &
# B republishes: the RPC stream receives the record and name resolve returns the new value

Root cause

  • The pubsub RPC subscribes with the deprecated PubSub.Subscribe(topic) (core/coreapi/pubsub.go), which creates the Topic handle via tryJoin and keeps it in myTopics. Cancelling the subscription only calls Subscription.Cancel(), never Topic.Close(), so the handle stays for the life of the process.
  • namesys' PubsubValueStore.Subscribe (go-libp2p-pubsub-router/pubsub.go, createTopicHandler) calls p.ps.Join(topic), which returns topic already exists when the handle is already in myTopics (go-libp2p-pubsub/pubsub.go, Join).
  • That error propagates out of GetValue/SearchValue, so the resolve fails, and it is not logged anywhere.

The reverse order works because Subscribe tolerates an existing topic handle (tryJoin returns the existing one) while Join does not.

Expected behavior

Either order should work: an RPC subscription on a record topic should not prevent namesys from resolving that name. Failing that, the error should at least be logged and surfaced by name resolve instead of the generic could not resolve name, and pubsub sub cancellation should release the topic handle when it was the only subscriber.

Possible fixes

  • Make the pubsub RPC and namesys share topic handles (e.g. a per-node topic registry, or have PubsubValueStore.createTopicHandler fall back to the existing handle when Join reports it already exists).
  • Have the coreapi Subscribe use Join + Topic.Subscribe and close the topic on cancel when it was the last subscriber.
  • Log the Join error in PubsubValueStore.Subscribe.