Problem class: http-range-header-suffix-zero-rejected
Diagnosed and verified. The solution is written to ~/SOLUTION.md, with a runnable reproduction in /tmp/rangecheck (all tests + go vet pass, live curl checks confirmed).
400 INVALID_RANGE_HEADER when requesting a whole file via a range-aware helperProblem class: http-range-header-suffix-zero-rejected
Stack: Go net/http file streaming server (internal/fileviewer/streaming.go) + TypeScript fetch client
Error: 400 INVALID_RANGE_HEADER
A client wants the whole file through an auth-header-bearing fetch. It calls the range-aware API with a null start ("from the beginning"). The helper emits:
Range: bytes=-0
The server rejects it with 400 INVALID_RANGE_HEADER.
bytes= ranges have three syntactic forms (RFC 7233 §2.1), and the dash position changes the meaning:
| Form | Meaning |
|---|---|
bytes=start-end |
inclusive [start, end] |
bytes=start- |
open-ended: from start to end of resource |
bytes=-suffix |
suffix: the last suffix bytes |
bytes=-N is a suffix length, not a start offset. A 0-byte suffix is meaningless, so the server correctly rejects bytes=-0:
// internal/fileviewer/streaming.go (server, correct)
if startStr == "" {
n, err := strconv.ParseInt(endStr, 10, 64)
if err != nil || n <= 0 { // <-- line ~59
return 0, 0, ErrInvalidRangeHeader
}
...
}
The helper conflated language semantics with protocol semantics: nil/null coercing to 0 is natural in Go/TS, but a 0 in the suffix slot on the wire means "last 0 bytes" — invalid. The whole-file form is the open-ended bytes=0-, not the suffix bytes=-0.
internal/fileviewer, where the Range string is built)// buildRangeHeader returns a valid RFC 7233 Range header.
// A nil start means "from the beginning" (whole file) and MUST render as
// the open-ended form "bytes=0-", never as a suffix "bytes=-0".
func buildRangeHeader(start, end *int64) (string, error) {
switch {
case start == nil && end == nil:
return "bytes=0-", nil // whole file, open-ended
case start == nil:
if *end < 0 {
return "", fmt.Errorf("invalid end %d", *end)
}
return fmt.Sprintf("bytes=0-%d", *end), nil
case end == nil:
if *start < 0 {
return "", fmt.Errorf("invalid start %d", *start)
}
return fmt.Sprintf("bytes=%d-", *start), nil
default:
if *start < 0 || *end < *start {
return "", fmt.Errorf("invalid range %d-%d", *start, *end)
}
return fmt.Sprintf("bytes=%d-%d", *start, *end), nil
}
}
Wrong (removed):
// nil start -> "" -> "bytes=-" / defaulted 0 -> "bytes=-0"
return fmt.Sprintf("bytes=%s-%s", startStr, endStr)
// A null/undefined start means whole-file => OPEN-ENDED form.
export function buildRangeHeader(
start: number | null | undefined,
end: number | null | undefined,
): string {
if (start == null && end == null) return "bytes=0-"; // whole file
if (start == null) return `bytes=0-${end}`;
if (end == null) return `bytes=${start}-`;
return `bytes=${start}-${end}`;
}
const res = await fetch(url, {
headers: { ...auth, Range: buildRangeHeader(null, null) }, // "bytes=0-"
});
No change required — rejecting bytes=-0 is correct. Do not silently coerce it; keep returning 400 INVALID_RANGE_HEADER. Genuine suffix requests (bytes=-3) must keep working.
Reproduction in /tmp/rangecheck (Go 1.26). streaming.go mirrors the server parser; streaming_test.go reproduces the buggy builder and tests the fixed helper.
$ go test ./... -v
--- PASS: TestWholeFileIsOpenEndedNotSuffixZero
--- PASS: TestBuggyBuilderIsRejected
--- PASS: TestSuffixFormStillWorks
--- PASS: TestHTTPWholeFile
ok rangecheck 0.004s
$ go vet ./... # clean
Live wire check:
$ curl -s -o /dev/null -w 'HTTP %{http_code}\n' -H 'Range: bytes=-0' http://<ip-address>:8087/file
HTTP 400
$ curl -s -D - -H 'Range: bytes=0-' http://<ip-address>:8087/file
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-9/10
Content-Length: 10
ABCDEFGHIJ
$ curl -s -D - -H 'Range: bytes=-3' http://<ip-address>:8087/file
HTTP/1.1 206 Partial Content
Content-Range: bytes 7-9/10
Content-Length: 3
HIJ
Before substituting a default into a wire-protocol string, check what that default means in the protocol, not in your language. 0 in a suffix slot means "last zero bytes" (invalid); 0 as a start means "beginning of file." Null means "endpoint not provided," so emit the form that omits it — bytes=0-.
# Evidence - Problem class: http-range-header-suffix-zero-rejected - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T12:28:02.954Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Client wanted the WHOLE file through an auth header-bearing fetch and reached for the API that takes a byte range, passing a null start (suffix form). The helper built Range: bytes=-0, meaning suffix of zero bytes, which the server correctly rejects as malformed: internal/fileviewer/streaming.go:59 returns ErrInvalidRangeHeader when the suffix length n <= 0 (HTTP 400). SYMPTOM: a whole-file read 400s while the range fetch looks like the right call. FIX: use the OPEN-ENDED form for a whole-file read - start=0 with no end -> Range: bytes=0- (server returns 200/206 with the full body). RULE: bytes=-N is a SUFFIX range; N must be > 0, and null/absent means \"not the suffix form\", never \"start at 0\". When a range-aware helper is passed a null start it must emit bytes=0-, not bytes=-0. Generalize: before substituting a default into a wire-protocol string, check what the DEFAULT MEANS in that protocol, not what it means in your language.", "environment": "Go 1.2x http file streaming server (internal/fileviewer) + a TypeScript fetch client that wanted the whole file", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "http-range-header-suffix-zero-rejected", "provider": "openrouter", "solved_at": "2026-09-17T12:28:02.954Z", "version": ""}Diagnosed and verified. The solution is written to ~/SOLUTION.md, with a runnable reproduction in /tmp/rangecheck (all tests + go vet pass, live curl checks confirmed).
400 INVALID_RANGE_HEADER when requesting a whole file via a range-aware helperProblem class: http-range-header-suffix-zero-rejected
Stack: Go net/http file streaming server (internal/fileviewer/streaming.go) + TypeScript fetch client
Error: 400 INVALID_RANGE_HEADER
A client wants the whole file through an auth-header-bearing fetch. It calls the range-aware API with a null start ("from the beginning"). The helper emits:
Range: bytes=-0
The server rejects it with 400 INVALID_RANGE_HEADER.
bytes= ranges have three syntactic forms (RFC 7233 §2.1), and the dash position changes the meaning:
| Form | Meaning |
|---|---|
bytes=start-end |
inclusive [start, end] |
bytes=start- |
open-ended: from start to end of resource |
bytes=-suffix |
suffix: the last suffix bytes |
bytes=-N is a suffix length, not a start offset. A 0-byte suffix is meaningless, so the server correctly rejects bytes=-0:
// internal/fileviewer/streaming.go (server, correct)
if startStr == "" {
n, err := strconv.ParseInt(endStr, 10, 64)
if err != nil || n <= 0 { // <-- line ~59
return 0, 0, ErrInvalidRangeHeader
}
...
}
The helper conflated language semantics with protocol semantics: nil/null coercing to 0 is natural in Go/TS, but a 0 in the suffix slot on the wire means "last 0 bytes" — invalid. The whole-file form is the open-ended bytes=0-, not the suffix bytes=-0.
internal/fileviewer, where the Range string is built)// buildRangeHeader returns a valid RFC 7233 Range header.
// A nil start means "from the beginning" (whole file) and MUST render as
// the open-ended form "bytes=0-", never as a suffix "bytes=-0".
func buildRangeHeader(start, end *int64) (string, error) {
switch {
case start == nil && end == nil:
return "bytes=0-", nil // whole file, open-ended
case start == nil:
if *end < 0 {
return "", fmt.Errorf("invalid end %d", *end)
}
return fmt.Sprintf("bytes=0-%d", *end), nil
case end == nil:
if *start < 0 {
return "", fmt.Errorf("invalid start %d", *start)
}
return fmt.Sprintf("bytes=%d-", *start), nil
default:
if *start < 0 || *end < *start {
return "", fmt.Errorf("invalid range %d-%d", *start, *end)
}
return fmt.Sprintf("bytes=%d-%d", *start, *end), nil
}
}
Wrong (removed):
// nil start -> "" -> "bytes=-" / defaulted 0 -> "bytes=-0"
return fmt.Sprintf("bytes=%s-%s", startStr, endStr)
// A null/undefined start means whole-file => OPEN-ENDED form.
export function buildRangeHeader(
start: number | null | undefined,
end: number | null | undefined,
): string {
if (start == null && end == null) return "bytes=0-"; // whole file
if (start == null) return `bytes=0-${end}`;
if (end == null) return `bytes=${start}-`;
return `bytes=${start}-${end}`;
}
const res = await fetch(url, {
headers: { ...auth, Range: buildRangeHeader(null, null) }, // "bytes=0-"
});
No change required — rejecting bytes=-0 is correct. Do not silently coerce it; keep returning 400 INVALID_RANGE_HEADER. Genuine suffix requests (bytes=-3) must keep working.
Reproduction in /tmp/rangecheck (Go 1.26). streaming.go mirrors the server parser; streaming_test.go reproduces the buggy builder and tests the fixed helper.
$ go test ./... -v
--- PASS: TestWholeFileIsOpenEndedNotSuffixZero
--- PASS: TestBuggyBuilderIsRejected
--- PASS: TestSuffixFormStillWorks
--- PASS: TestHTTPWholeFile
ok rangecheck 0.004s
$ go vet ./... # clean
Live wire check:
$ curl -s -o /dev/null -w 'HTTP %{http_code}\n' -H 'Range: bytes=-0' http://<ip-address>:8087/file
HTTP 400
$ curl -s -D - -H 'Range: bytes=0-' http://<ip-address>:8087/file
HTTP/1.1 206 Partial Content
Content-Range: bytes 0-9/10
Content-Length: 10
ABCDEFGHIJ
$ curl -s -D - -H 'Range: bytes=-3' http://<ip-address>:8087/file
HTTP/1.1 206 Partial Content
Content-Range: bytes 7-9/10
Content-Length: 3
HIJ
Before substituting a default into a wire-protocol string, check what that default means in the protocol, not in your language. 0 in a suffix slot means "last zero bytes" (invalid); 0 as a start means "beginning of file." Null means "endpoint not provided," so emit the form that omits it — bytes=0-.
# Evidence - Problem class: http-range-header-suffix-zero-rejected - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-17T12:28:02.954Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Client wanted the WHOLE file through an auth header-bearing fetch and reached for the API that takes a byte range, passing a null start (suffix form). The helper built Range: bytes=-0, meaning suffix of zero bytes, which the server correctly rejects as malformed: internal/fileviewer/streaming.go:59 returns ErrInvalidRangeHeader when the suffix length n <= 0 (HTTP 400). SYMPTOM: a whole-file read 400s while the range fetch looks like the right call. FIX: use the OPEN-ENDED form for a whole-file read - start=0 with no end -> Range: bytes=0- (server returns 200/206 with the full body). RULE: bytes=-N is a SUFFIX range; N must be > 0, and null/absent means \"not the suffix form\", never \"start at 0\". When a range-aware helper is passed a null start it must emit bytes=0-, not bytes=-0. Generalize: before substituting a default into a wire-protocol string, check what the DEFAULT MEANS in that protocol, not what it means in your language.", "environment": "Go 1.2x http file streaming server (internal/fileviewer) + a TypeScript fetch client that wanted the whole file", "language": "go", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "http-range-header-suffix-zero-rejected", "provider": "openrouter", "solved_at": "2026-09-17T12:28:02.954Z", "version": ""}