{"record":{"id":"01a25cb3389d22b9","repo":"ipfs/kubo","slug":"got-unexpected-number-of-keys-back","errorCode":null,"errorMessage":"got unexpected number of keys back","messagePattern":"got unexpected number of keys back","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/rpc/key.go","lineNumber":137,"sourceCode":"\nfunc (api *KeyAPI) Self(ctx context.Context) (iface.Key, error) {\n\tvar id struct{ ID string }\n\tif err := api.core().Request(\"id\").Exec(ctx, &id); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn newKey(\"self\", id.ID)\n}\n\nfunc (api *KeyAPI) Remove(ctx context.Context, name string) (iface.Key, error) {\n\tvar out struct {\n\t\tKeys []keyOutput\n\t}\n\tif err := api.core().Request(\"key/rm\", name).Exec(ctx, &out); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(out.Keys) != 1 {\n\t\treturn nil, errors.New(\"got unexpected number of keys back\")\n\t}\n\n\treturn newKey(out.Keys[0].Name, out.Keys[0].Id)\n}\n\nfunc (api *KeyAPI) core() *HttpApi {\n\treturn (*HttpApi)(api)\n}\n\nfunc (api *KeyAPI) Sign(ctx context.Context, name string, data []byte) (iface.Key, []byte, error) {\n\tvar out struct {\n\t\tKey       keyOutput\n\t\tSignature string\n\t}\n\n\terr := api.core().Request(\"key/sign\").\n\t\tOption(\"key\", name).\n\t\tFileBody(bytes.NewReader(data)).","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/ipfs/kubo/blob/329838acdfafae224582930457efe80aa217afc0/client/rpc/key.go#L119-L155","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the key exists first with api.Key().List(ctx) and only remove names present in that list.","Make removals idempotent: treat a second Remove of the same name as success by pre-checking or catching the error.","Upgrade the kubo daemon (and go-ipfs-api) to matching versions so the key/rm response shape matches the parser."],"exampleFix":"// before\n_, err := api.Key().Remove(ctx, \"publish-key\")\n// after\nkeys, _ := api.Key().List(ctx)\nif slices.ContainsFunc(keys, func(k ciutil.Key) bool { return k.Name() == \"publish-key\" }) {\n    _, err = api.Key().Remove(ctx, \"publish-key\")\n}","handlingStrategy":"validation","validationCode":"keys, err := api.Key().List(ctx)\nif err != nil { return err }\nexists := slices.ContainsFunc(keys, func(k ciutil.Key) bool { return k.Name() == name })\nif !exists {\n    return nil // nothing to remove; skip Remove entirely\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Check Key().List before removing so the key is known to exist","Design removal as idempotent; tolerate 'key already gone' outcomes","Avoid concurrent Remove calls for the same key name","Pin kubo daemon and client versions together"],"tags":["keystore","http-rpc","response-validation"],"backgroundTag":"unexpected-rpc-response-shape","analyzedSha":"329838acdfafae224582930457efe80aa217afc0","analyzedAt":"2026-09-03T18:30:52.135Z","contentChangedAt":"2026-09-03T18:30:52.135Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}