Passing a prefix without a trailing separator produces paths that start with /, which are invalid for io/fs and make NewFS fail.
r, _ := archives.OpenBytesWithPrefix("express-4.19.2.tgz", buf, "package")
fsys, err := archives.NewFS(r)
// err: newfs /LICENSE: invalid argument
"package/" works. The difference comes from stripPrefix in prefix.go, which does a bare strings.TrimPrefix with no separator handling:
stripped.Path = strings.TrimPrefix(f.Path, p.prefix)
ListDir and Extract concatenate the other way (p.prefix + dirPath), so a prefix with a trailing separator is the only form that works consistently, and nothing states that or enforces it.
Normalising the prefix to exactly one trailing separator on construction would make both forms behave the same. A check that the resulting path satisfies fs.ValidPath would turn the remaining cases into an error at OpenBytesWithPrefix rather than a confusing failure later in NewFS.
Passing a prefix without a trailing separator produces paths that start with
/, which are invalid forio/fsand makeNewFSfail."package/"works. The difference comes fromstripPrefixinprefix.go, which does a barestrings.TrimPrefixwith no separator handling:ListDirandExtractconcatenate the other way (p.prefix + dirPath), so a prefix with a trailing separator is the only form that works consistently, and nothing states that or enforces it.Normalising the prefix to exactly one trailing separator on construction would make both forms behave the same. A check that the resulting path satisfies
fs.ValidPathwould turn the remaining cases into an error atOpenBytesWithPrefixrather than a confusing failure later inNewFS.