Description: @helia/utils AbstractGraphWalker.walk runs block loads as queue jobs. When a load fails, the job's rejection is routed via queue.add(...).catch(err => gen.throw(err)), but the queue going idle can end the generator first — the consumer sees a clean end of stream, and the gen.throw on the finished generator surfaces the error as an unhandled rejection instead. The race goes both ways: sometimes the error propagates, sometimes the walk truncates silently. Any CAR assembled through these walkers (@helia/car export, @helia/verified-fetch CAR responses) can under-represent the DAG with no error.
Steps to Reproduce: load the pinned 21-block CAR (test/commp-piece-cid-regression.test.ts) into a MemoryBlockstore, delete one mid leaf, export via @helia/car with SubgraphExporter + depthFirstWalker and an exact block filter, drain the stream.
Expected: the export rejects with the load error.
Actual: the stream ends at 525,472 of 5,010,728 bytes with no error; NotFoundError fires later as an unhandled rejection.
Environment: @helia/utils 2.5.2, @helia/car 5.4.2, Node 26.
Additional Context: no exposure in current code — ipfs2foc-core/car-export drives its own traversal, so a failed load rejects the export deterministically. Tracked so the constraint stays visible (nothing in the commitment path may return to the upstream walkers until this is fixed upstream) and so the upstream report can lift this repro. The unhandledRejection orphan guard in src/verified-fetch.ts absorbs this same leak on the fallback path.
Description:
@helia/utilsAbstractGraphWalker.walkruns block loads as queue jobs. When a load fails, the job's rejection is routed viaqueue.add(...).catch(err => gen.throw(err)), but the queue going idle can end the generator first — the consumer sees a clean end of stream, and thegen.throwon the finished generator surfaces the error as an unhandled rejection instead. The race goes both ways: sometimes the error propagates, sometimes the walk truncates silently. Any CAR assembled through these walkers (@helia/carexport,@helia/verified-fetchCAR responses) can under-represent the DAG with no error.Steps to Reproduce: load the pinned 21-block CAR (
test/commp-piece-cid-regression.test.ts) into aMemoryBlockstore, delete one mid leaf, export via@helia/carwithSubgraphExporter+depthFirstWalkerand an exact block filter, drain the stream.Expected: the export rejects with the load error.
Actual: the stream ends at 525,472 of 5,010,728 bytes with no error;
NotFoundErrorfires later as an unhandled rejection.Environment:
@helia/utils2.5.2,@helia/car5.4.2, Node 26.Additional Context: no exposure in current code —
ipfs2foc-core/car-exportdrives its own traversal, so a failed load rejects the export deterministically. Tracked so the constraint stays visible (nothing in the commitment path may return to the upstream walkers until this is fixed upstream) and so the upstream report can lift this repro. TheunhandledRejectionorphan guard insrc/verified-fetch.tsabsorbs this same leak on the fallback path.