Found while implementing #6322 (PR #6480). Out of that issue's scope — filed rather than fixed, per Prime Directive #10.
Fact (measured on origin/main eb7613c, and unchanged by PR #6480)
Both find copies emit the two pagination transport params on truthiness, not on presence:
packages/client/src/index.ts:4224 if (normalizedOptions.top) queryParams.set('top', normalizedOptions.top.toString());
packages/client/src/index.ts:4225 if (normalizedOptions.skip) queryParams.set('skip', normalizedOptions.skip.toString());
packages/client/src/index.ts:4908 (ScopedProjectClient — same two lines)
packages/client/src/index.ts:4909
Meanwhile the canonical branch normalizes on presence:
if (v2.limit != null) normalizedOptions.top = v2.limit;
if (v2.offset != null) normalizedOptions.skip = v2.offset;
So 0 survives the normalizer and is then discarded by the emitter. find('task', { limit: 0 }) — and equally { top: 0 } — reaches the server with no top param at all, and the caller gets the server's default page size: HTTP 200, no warning. That is the identical silent-wrong-answer shape #6322 was filed for, one value narrower.
{ offset: 0 } / { skip: 0 } are dropped too, but harmlessly — skip=0 is already the default, so the request means the same thing either way. limit is the one that changes the answer.
Why this is finding and not a defect queue item
I could not find a caller that writes limit: 0 today: nothing in the workspace calls client.data.find outside packages/client's own tests. Whether limit: 0 should mean "return no records" or should be refused as meaningless is a contract question the transport layer does not get to answer by accident — right now it answers "return the default page", which is the one reading nobody would choose deliberately.
Worth grading rather than assuming: severity judged at filing time is unreliable in both directions, so this is filed plainly for triage.
Note on scope
Deliberately not fixed in PR #6480. That PR's ruling was to replace the branch-selection predicate and map expand; changing if (x) to if (x != null) on the emitter is a different behavioural decision (it needs a call on what limit: 0 means) and would have ridden in unreviewed. The two find copies are byte-identical here as well, so whoever takes this must change both — the same multi-face constraint #6322 carried.
Found while implementing #6322 (PR #6480). Out of that issue's scope — filed rather than fixed, per Prime Directive #10.
Fact (measured on
origin/maineb7613c, and unchanged by PR #6480)Both
findcopies emit the two pagination transport params on truthiness, not on presence:Meanwhile the canonical branch normalizes on presence:
So
0survives the normalizer and is then discarded by the emitter.find('task', { limit: 0 })— and equally{ top: 0 }— reaches the server with notopparam at all, and the caller gets the server's default page size: HTTP 200, no warning. That is the identical silent-wrong-answer shape #6322 was filed for, one value narrower.{ offset: 0 }/{ skip: 0 }are dropped too, but harmlessly —skip=0is already the default, so the request means the same thing either way.limitis the one that changes the answer.Why this is
findingand not a defect queue itemI could not find a caller that writes
limit: 0today: nothing in the workspace callsclient.data.findoutsidepackages/client's own tests. Whetherlimit: 0should mean "return no records" or should be refused as meaningless is a contract question the transport layer does not get to answer by accident — right now it answers "return the default page", which is the one reading nobody would choose deliberately.Worth grading rather than assuming: severity judged at filing time is unreliable in both directions, so this is filed plainly for triage.
Note on scope
Deliberately not fixed in PR #6480. That PR's ruling was to replace the branch-selection predicate and map
expand; changingif (x)toif (x != null)on the emitter is a different behavioural decision (it needs a call on whatlimit: 0means) and would have ridden in unreviewed. The twofindcopies are byte-identical here as well, so whoever takes this must change both — the same multi-face constraint #6322 carried.