Context
src/orb/relay.ts's readOrbRelayRegisterBody wraps its stream-read loop in a try/catch specifically because, per its own doc comment:
the underlying stream itself errors (a dropped connection / network reset mid-read...) — every caller already treats null identically to "reject this request"... fixed once here rather than wrapping all three callers.
export async function readOrbRelayRegisterBody(request: Request, contentLengthHeader: string | null | undefined): Promise<string | null> {
...
try {
for (;;) {
const { done, value } = await reader.read();
...
}
return out + decoder.decode();
} catch {
return null;
}
}
src/orb/ingest.ts's readOrbIngestBody is the same pattern — declared-length check, reader.read() loop, byte-cap check — but has no try/catch:
export async function readOrbIngestBody(request: Request, contentLengthHeader: string | null | undefined): Promise<string | null> {
const declared = parseContentLength(contentLengthHeader);
if (declared !== null && declared > MAX_ORB_INGEST_BODY_BYTES) return null;
const stream = request.body;
if (!stream) return "";
const reader = stream.getReader();
const decoder = new TextDecoder();
let total = 0;
let out = "";
for (;;) {
const { done, value } = await reader.read(); // can reject — unguarded
if (done) break;
total += value.byteLength;
if (total > MAX_ORB_INGEST_BODY_BYTES) {
await reader.cancel();
return null;
}
out += decoder.decode(value, { stream: true });
}
return out + decoder.decode();
}
Both /v1/orb/ingest and /v1/ams/ingest (src/api/routes.ts) call readOrbIngestBody directly with no surrounding try/catch of their own, and there is no app.onError handler in routes.ts covering this. A dropped connection mid-upload will throw an uncaught rejection out of the route handler instead of the clean 413/400 JSON response the route otherwise returns for an oversized or malformed body — exactly the bug class relay.ts already fixed for its own three call sites. test/integration/orb-relay.test.ts (~line 184-200) explicitly tests the dropped-connection case for readOrbRelayRegisterBody; no equivalent test exists for readOrbIngestBody in test/integration/orb-ingest.test.ts.
Requirements
- Wrap
readOrbIngestBody's read loop in the same try/catch pattern as readOrbRelayRegisterBody in src/orb/relay.ts: on any error from reader.read() (or elsewhere in the loop), return null instead of letting the rejection propagate.
- Do not change the function's byte-cap or declared-length-check behavior — only add the error handling around the read loop.
Deliverables
Test Coverage Requirements
This repo's Codecov patch gate requires 99%+ coverage of changed lines and branches. Cover both the new catch branch (read rejects → returns null) and confirm the existing successful-read and oversized-body branches remain covered.
Expected Outcome
A dropped connection or network reset mid-upload to /v1/orb/ingest or /v1/ams/ingest now degrades to the same clean 413/400 JSON response every other rejected-body case already produces, instead of an uncaught rejection reaching the framework as a bare 500.
Links & Resources
src/orb/ingest.ts — readOrbIngestBody, around line 28-49
src/orb/relay.ts — readOrbRelayRegisterBody, the pattern to mirror, around line 168-200
test/integration/orb-relay.test.ts — the dropped-connection test to mirror, around line 184-200
src/api/routes.ts — the two call sites (/v1/orb/ingest, /v1/ams/ingest)
Context
src/orb/relay.ts'sreadOrbRelayRegisterBodywraps its stream-read loop in a try/catch specifically because, per its own doc comment:src/orb/ingest.ts'sreadOrbIngestBodyis the same pattern — declared-length check,reader.read()loop, byte-cap check — but has no try/catch:Both
/v1/orb/ingestand/v1/ams/ingest(src/api/routes.ts) callreadOrbIngestBodydirectly with no surrounding try/catch of their own, and there is noapp.onErrorhandler inroutes.tscovering this. A dropped connection mid-upload will throw an uncaught rejection out of the route handler instead of the clean413/400JSON response the route otherwise returns for an oversized or malformed body — exactly the bug classrelay.tsalready fixed for its own three call sites.test/integration/orb-relay.test.ts(~line 184-200) explicitly tests the dropped-connection case forreadOrbRelayRegisterBody; no equivalent test exists forreadOrbIngestBodyintest/integration/orb-ingest.test.ts.Requirements
readOrbIngestBody's read loop in the same try/catch pattern asreadOrbRelayRegisterBodyinsrc/orb/relay.ts: on any error fromreader.read()(or elsewhere in the loop), returnnullinstead of letting the rejection propagate.Deliverables
readOrbIngestBodyinsrc/orb/ingest.tswraps its stream-read loop in try/catch, returningnullon any read error, mirroringreadOrbRelayRegisterBodyinsrc/orb/relay.ts.test/integration/orb-ingest.test.tsmirroringtest/integration/orb-relay.test.ts's dropped-connection case: a request body stream whosereader.read()rejects, assertingreadOrbIngestBodyreturnsnull(not an uncaught rejection) and that the calling route returns its normal clean error response rather than a framework 500.Test Coverage Requirements
This repo's Codecov patch gate requires 99%+ coverage of changed lines and branches. Cover both the new catch branch (read rejects → returns null) and confirm the existing successful-read and oversized-body branches remain covered.
Expected Outcome
A dropped connection or network reset mid-upload to
/v1/orb/ingestor/v1/ams/ingestnow degrades to the same clean413/400JSON response every other rejected-body case already produces, instead of an uncaught rejection reaching the framework as a bare 500.Links & Resources
src/orb/ingest.ts—readOrbIngestBody, around line 28-49src/orb/relay.ts—readOrbRelayRegisterBody, the pattern to mirror, around line 168-200test/integration/orb-relay.test.ts— the dropped-connection test to mirror, around line 184-200src/api/routes.ts— the two call sites (/v1/orb/ingest,/v1/ams/ingest)