{"record":{"id":"21bcd830cad28368","repo":"ipfs/kubo","slug":"ls-returned-more-links-than-expected-d","errorCode":null,"errorMessage":"ls returned more links than expected (%d)","messagePattern":"ls returned more links than expected \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/rpc/apifile.go","lineNumber":228,"sourceCode":"\t\tit.err = it.ctx.Err()\n\t\treturn false\n\t}\n\n\tvar out lsOutput\n\tif err := it.dec.Decode(&out); err != nil {\n\t\tif err != io.EOF {\n\t\t\tit.err = err\n\t\t}\n\t\treturn false\n\t}\n\n\tif len(out.Objects) != 1 {\n\t\tit.err = fmt.Errorf(\"ls returned more objects than expected (%d)\", len(out.Objects))\n\t\treturn false\n\t}\n\n\tif len(out.Objects[0].Links) != 1 {\n\t\tit.err = fmt.Errorf(\"ls returned more links than expected (%d)\", len(out.Objects[0].Links))\n\t\treturn false\n\t}\n\n\tit.cur = out.Objects[0].Links[0]\n\tc, err := cid.Parse(it.cur.Hash)\n\tif err != nil {\n\t\tit.err = err\n\t\treturn false\n\t}\n\n\tswitch it.cur.Type {\n\tcase unixfs.THAMTShard, unixfs.TMetadata, unixfs.TDirectory:\n\t\tit.curFile, err = it.core.getDir(it.ctx, path.FromCid(c), int64(it.cur.Size), it.cur.Mode, it.cur.ModTime)\n\t\tif err != nil {\n\t\t\tit.err = err\n\t\t\treturn false\n\t\t}\n\tcase unixfs.TFile:","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/ipfs/kubo/blob/329838acdfafae224582930457efe80aa217afc0/client/rpc/apifile.go#L210-L246","documentation":"Companion invariant to the objects check: after confirming exactly one object in the ls page, apiIter requires exactly one link in that object. If the object carries more (or zero) links, the iterator records this error and stops, since it exposes one directory entry (it.cur) per iteration and cannot represent multiple links in a single step.","triggerScenarios":"Directory iteration via the client where a single ls response returns one object containing multiple links — typically a daemon that returns the whole directory in one object rather than one entry per response, or a changed/streaming ls response shape.","commonSituations":"Client talking to a non-kubo or older/newer server whose ls semantics differ; custom API re-implementations; proxies that collapse multiple responses into one object with many links.","solutions":["Run a kubo daemon matching the client version (version skew is the usual cause)","Query the daemon directly, not through transforming proxies","Verify response shape with `curl -X POST 'http://127.0.0.1:5001/api/v0/ls?arg=<cid>'`","For full directory listings, call the ls RPC directly and iterate Objects/Links yourself"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// one object must carry exactly one link per iteration step\nvar out struct{ Objects []struct{ Links []struct{} `json:\"Links\"` } `json:\"Objects\"` }\n_ = api.Request(\"ls\").Option(\"arg\", p).Exec(ctx, &out)\nif len(out.Objects) == 1 && len(out.Objects[0].Links) != 1 {\n    return fmt.Errorf(\"server batches links; use raw ls RPC\")\n}","typeGuard":null,"tryCatchPattern":"if strings.Contains(err.Error(), \"ls returned more links than expected\") {\n    // fall back to direct ls RPC and walk Objects[0].Links yourself\n    return iterateLsDirect(ctx, api, p)\n}","preventionTips":["Match kubo versions client-side and daemon-side","Avoid middleware that merges ls responses","For directories, prefer the raw ls RPC over the single-entry iterator"],"tags":["go","ipfs","rpc","ls","invariant"],"backgroundTag":"ls-response-shape-mismatch","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"}