ipfs/kubo · error

got unexpected number of keys back

Error message

got unexpected number of keys back

What it means

KeyAPI.Remove issues key/rm and asserts the daemon responds with exactly one removed key entry. Any other count (zero, or more than one) indicates the daemon did not do what was asked, so Remove fails rather than guessing which key was removed.

Source

Thrown at client/rpc/key.go:137

func (api *KeyAPI) Self(ctx context.Context) (iface.Key, error) {
	var id struct{ ID string }
	if err := api.core().Request("id").Exec(ctx, &id); err != nil {
		return nil, err
	}

	return newKey("self", id.ID)
}

func (api *KeyAPI) Remove(ctx context.Context, name string) (iface.Key, error) {
	var out struct {
		Keys []keyOutput
	}
	if err := api.core().Request("key/rm", name).Exec(ctx, &out); err != nil {
		return nil, err
	}
	if len(out.Keys) != 1 {
		return nil, errors.New("got unexpected number of keys back")
	}

	return newKey(out.Keys[0].Name, out.Keys[0].Id)
}

func (api *KeyAPI) core() *HttpApi {
	return (*HttpApi)(api)
}

func (api *KeyAPI) Sign(ctx context.Context, name string, data []byte) (iface.Key, []byte, error) {
	var out struct {
		Key       keyOutput
		Signature string
	}

	err := api.core().Request("key/sign").
		Option("key", name).
		FileBody(bytes.NewReader(data)).

View on GitHub (pinned to 329838acdf)

Solutions

  1. Check the key exists first with api.Key().List(ctx) and only remove names present in that list.
  2. Make removals idempotent: treat a second Remove of the same name as success by pre-checking or catching the error.
  3. Upgrade the kubo daemon (and go-ipfs-api) to matching versions so the key/rm response shape matches the parser.

Example fix

// before
_, err := api.Key().Remove(ctx, "publish-key")
// after
keys, _ := api.Key().List(ctx)
if slices.ContainsFunc(keys, func(k ciutil.Key) bool { return k.Name() == "publish-key" }) {
    _, err = api.Key().Remove(ctx, "publish-key")
}
Defensive patterns

Strategy: validation

Validate before calling

keys, err := api.Key().List(ctx)
if err != nil { return err }
exists := slices.ContainsFunc(keys, func(k ciutil.Key) bool { return k.Name() == name })
if !exists {
    return nil // nothing to remove; skip Remove entirely
}

Prevention

When it happens

Trigger: Calling api.Key().Remove(ctx, name) where the daemon returns an empty Keys array (key already gone / never existed / racy double-remove) or, on mismatched daemon versions, a response shape the client parses into a different-length slice.

Common situations: Removing a key that was already deleted (e.g. retried request, concurrent caller), a name typo so nothing matched, or an old kubo daemon whose key/rm response format differs from what this client expects.

Related errors


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