Repository navigation
Description
Bug
The via-service client's results-paging loop has no deadline and no progress guard (pkgs/atlas-search/crates/atlas-search/src/service.rs, search()):
loop {
// GET {job_url}/results?limit=50[&cursor=...]
...
results.extend(page.results.iter().cloned());
if results.len() >= request.k || page.next_cursor.is_none() {
break;
}
cursor = page.next_cursor;
}The poll loop directly above it in the same function is deadline-disciplined (CLIENT_DEADLINE, 330 s, checked every iteration, service.rs:148 and :177). The paging loop checks nothing: if a page stream keeps returning a next_cursor without the collected results reaching request.k — a repeating cursor, an empty page with a cursor, a future server/protocol bug — the client loops forever, growing results unboundedly. The ureq agent's timeout_global is per-request, so it does not bound the loop.
Why it matters
The module's stated contract (service.rs:1-13) is prefer service, fall back to standalone loudly: "any failure — unreachable, an HTTP error, a failed/expired job, a malformed response — is reported by the caller on stderr and recorded in the envelope's notes, and standalone serves the query." A non-advancing page stream is exactly the malformed-response case that contract exists for, but instead of falling back the CLI hangs forever with no output and no error. Today's server advances cursors by construction (page() in server.rs encodes a strictly increasing offset), so this needs a server-side bug or a protocol change to bite — the client's own contract says it should not depend on that.
Evidence (verified this run, main d4539fc)
- Code reading: the poll loop (service.rs:~148-185) carries
deadlineand returnsErr("service job did not complete within 330 seconds"); the paging loop (service.rs:~190-222) has noInstantcheck, no comparison of the new cursor against the previous one, and no cap on pages or collected results. - The only exits from the paging loop are
results.len() >= request.k,next_cursor.is_none(), or an HTTP/parse error — none of which a well-formed-but-non-advancing page stream triggers.
Acceptance criteria
- The paging loop honors the same 330 s client deadline as the poll loop and returns an
Err(triggering the caller's loud standalone fallback) when it expires mid-paging. - A page whose
next_cursordoes not advance (equal to the cursor just used), or an empty page that still returns a cursor, is treated as a malformed response:Err, not another iteration. - A unit/integration test with a stub service returning a repeating cursor shows
search()returningErrpromptly instead of looping; the normal multi-page happy path (more results than one page,kreached mid-stream) is unchanged. cargo test -p atlas-searchgreen (run on the laptop or CI per fleet norms).
Activity
dezren39 commentedon Nov 4, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 4, 2026
🤖 Agent note · picked up
atlas-search(service): bound the results-paging loop — deadline + cur…
dezren39 commentedon Nov 4, 2026
🤖 Agent note · done
cargo test -p atlas-searchgreen on the laptop; Codex review completed with no findings; basic CI checks passed.