GET /bzz/<ref>/ can 404 off-node, and the 404 is indistinguishable from a missing index document
Maintainers usually reply within 2 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 52/100
Research direction
Start in pkg/api/bzz.go at the empty-path branch around lines 624–643 and read manifestMetadataLoad around lines 820–836 to trace how lookup errors are handled. The issue suggests distinguishing a failed manifest lookup from a missing index-document key; check the related retrieval tests and add coverage for both cases. Done means the failure and the legitimate missing-index case produce distinct responses.
Written by the indexing model from the issue text.
Description
GET /bzz/<ref>/ on a single-file upload can 404 on a node that did not upload it, while
every other route for the same reference answers 200. The 404 is indistinguishable from
"no index document set", so there is no way to tell from the outside what went wrong.
Measured on a 5-node local cluster, bee v2.8.2, uploaded via the queen:
queen /bzz/<ref>/ 200
worker /bzz/<ref>/ 404 {"message":"address not found or incorrect","code":404}
worker /bzz/<ref>/manifest.json 200
worker /bytes/<ref> 200
worker /chunks/<ref> 200
The data is entirely present on the worker. Only the root form fails.
Why
GET /bzz/<ref>/ is the only retrieval path that has to load the manifest's / metadata
node — pkg/api/bzz.go:624-643 at v2.8.2:
if pathVar == "" {
if indexDocumentSuffixKey, ok := manifestMetadataLoad(ctx, m, manifest.RootPath, manifest.WebsiteIndexDocumentSuffixKey); ok {
...
}
jsonhttp.NotFound(w, "address not found or incorrect")
return
}
For a single-file upload that node is created at bzz.go:266-269 with
swarm.ZeroAddress and only the index-document metadata:
rootMetadata := map[string]string{
manifest.WebsiteIndexDocumentSuffixKey: queries.FileName,
}
err = m.Add(ctx, manifest.RootPath, manifest.NewEntry(swarm.ZeroAddress, rootMetadata))
The metadata is serialised into the parent's fork record, not into this node's own
chunk, so the chunk itself carries no information — and with a zero obfuscation key it
serialises identically for every unencrypted single-file upload. LookupNode fetches it
anyway and then discards it, because the recursion terminates immediately on an empty
remaining path.
So the root redirect for every single-file POST /bzz on the network depends on one
shared, content-free chunk. We believe its address is
0cc878d32c96126d47f63fbe391114ee1438cd521146fc975dea1546d302b6c0 — derived from
marshal.go rather than instrumented, but GET /chunks/<that address> does return 200 on
our cluster, which is consistent.
That also explains why the failure looks intermittent: once any upload has pushed that
chunk successfully, every later upload on the same cluster inherits it. On a
re-measurement the worker returned 200 for the root form.
The part that is squarely a bug
manifestMetadataLoad (bzz.go:820-836) returns ("", false) for both "the node could
not be fetched" and "the key is not set":
me, err := manifest.Lookup(ctx, path)
if err != nil {
return "", false
}
Both paths then reach the same jsonhttp.NotFound(w, "address not found or incorrect")
with no distinguishing log line beyond bzz download: address not found or incorrect. A
retrieval failure and a manifest that legitimately has no index document are reported
identically. That cost us a day of looking in the wrong place.
Suggested fix: propagate the lookup error and answer 500 (or a 404 with a distinct
message) when the / node cannot be loaded, keeping the current 404 for a manifest that
genuinely has no index document.
Two smaller observations
- There is no "manifest has exactly one entry, serve it" fallback in the empty-path
branch. Given a single-file upload always has its index document set to the filename,
the only way this branch 404s in practice is the retrieval failure above. Swarm-Index-DocumentandSwarm-Error-Documentare in the CORS allowed-headers list
(pkg/api/api.go:605) but are only read indirs.go, inside the directory handler. The
single-file upload header struct (bzz.go:70-81) does not include them, so on a
single-filePOST /bzzthey are accepted by preflight and silently ignored. Rejecting
them, or documenting that they are directory-only, would save someone the experiment.
Workaround, for anyone who finds this
Request the entry by name — GET /bzz/<ref>/<filename>. It never loads the / node.
Note that GET /bytes/<ref> is not a workaround for a /bzz upload: it returns the
manifest node's own bytes rather than the file, so it answers 200 with the wrong content
and the failure surfaces much later.
Filed with AI assistance.
- Dominant language
- Go
- Stars
- 1.5k
- Forks
- 390
- Avg merge
- 2d 11h
- Merged PRs (30d)
- 23
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from ethersphere/bee
-
Chequebook deployment uses a fixed 175,000 gas limit; it needs about 630,000 under Glamsterdam (live on Sepolia since 2026-10-06)Possibly taken @sbackend123 claimed this 9 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
ethersphere/bee#5650 · 1 assignee ·
Maintainers usually reply within 2 days
-
Add a transaction inbox for outbound chain transactionsPossibly taken @sbackend123 claimed this 3 days ago. Openseen
Difficulty 5/5 Over a week Newbie friendliness 35/100
ethersphere/bee#5660 · 1 assignee ·
Maintainers usually reply within 2 days
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
ethersphere/bee#5659 ·
Maintainers usually reply within 2 days
-
accepted seen
Difficulty 4/5 3-5 days Newbie friendliness 40/100
ethersphere/bee#5648 · 1 comment ·
Maintainers usually reply within 2 days
-
pusher: chunksWorker deadlocks on a deferred chunk that fails IdentityAddress (send on nil op.Err), halting all push-syncPossibly taken @sbackend123 claimed this 11 days ago. Openseen
Difficulty 3/5 1-2 days Newbie friendliness 75/100
ethersphere/bee#5641 · 1 assignee ·
Maintainers usually reply within 2 days
Similar issues
-
documentation priority:p1
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
yeixio/toskar-core#697 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
keyxmakerx/Chronicle#1532 ·
Maintainers usually reply within 1 day
-
The acquisition fixture's search path skips the argument table production enforces (regression from #285)Possibly taken A pull request linked to this issue is open or already merged. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Judgment-Pack/judgment-pack-gateway#293 ·
Maintainers usually reply within 1 day
-
bug desktop v3 windows
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
esengine/DeepSeek-Reasonix#13697 · 1 comment ·
Maintainers usually reply within 1 day
-
good first issue help wanted
Difficulty 1/5 Under an hour Newbie friendliness 95/100
eugenioenko/ttt#959 ·
Maintainers usually reply within 1 day