{"record":{"id":"a2d1add4cc60ca20","repo":"jax-ml/jax","slug":"unroll-must-be-a-bool-or-a-non-negative-int","errorCode":null,"errorMessage":"`unroll` must be a `bool` or a non-negative `int`.","messagePattern":"`unroll` must be a `bool` or a non-negative `int`\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"jax/_src/lax/control_flow/loops.py","lineNumber":200,"sourceCode":"  jaxpr, y_avals = pe.trace_to_jaxpr(f, args_avals, dbg_body)\n  jaxpr, consts = pe.separate_consts(jaxpr)\n\n  if config.mutable_array_checks.value:\n    _check_no_aliased_closed_over_refs(dbg_body, consts, list(xs_flat))\n\n  disallowed_effects = effects.control_flow_allowed_effects.filter_not_in(jaxpr.effects)\n  if disallowed_effects:\n    raise NotImplementedError(\n        f'Effects not supported in `scan`: {disallowed_effects}')\n\n  unroll = core.concrete_or_error(\n      None, unroll,\n      \"The `unroll` argument to `scan` expects a concrete `int` or `bool` \"\n      \"value.\")\n  if isinstance(unroll, bool):\n    unroll = max(length, 1) if unroll else 1\n  if unroll < 0:\n    raise ValueError(\"`unroll` must be a `bool` or a non-negative `int`.\")\n\n  args = list(consts) + list(xs_flat)\n  # TODO(dougalm): handle traceable-level forwarding\n  out = Scan3(\n      extensives = [False] * len(consts) + [True] * len(xs_flat),\n      length=length, jaxpr=jaxpr, reverse=reverse, unroll=unroll)(args)\n\n  return y_avals.update(out).unflatten()\n\n@partial(api_boundary, repro_api_name=\"jax.lax.scan\")\ndef scan3[Carry, X, Y](\n    f: Callable[[Carry, X], tuple[Carry, Y]],\n    init: Carry,\n    xs: X | None = None,\n    length: int | None = None,\n    reverse: bool = False,\n    unroll: int | bool = 1,\n    _split_transpose: bool = False) -> tuple[Carry, Y]:","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/jax-ml/jax/blob/1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb/jax/_src/lax/control_flow/loops.py#L182-L218","documentation":"scan_nocarry validates that the unroll argument is a non-negative int or bool. A negative unroll (e.g. -1 meaning 'auto' in other libraries) is meaningless for scan and raises ValueError.","triggerScenarios":"Calling lax.scan(f, init, xs, unroll=-1) or scan_nocarry with a negative integer unroll.","commonSituations":"Porting code from libraries where negative values mean 'auto unroll'; passing a computed unroll that underflows to negative.","solutions":["Pass a positive int (e.g. 1, 2, 4) or a bool","If you wanted automatic unrolling, pick an explicit divisor of the scan length","Validate unroll >= 0 before calling scan"],"exampleFix":"// before\nlax.scan(f, init, xs, unroll=-1)\n// after\nlax.scan(f, init, xs, unroll=4)","handlingStrategy":"validation","validationCode":"assert isinstance(unroll, (bool, int)) and (isinstance(unroll, bool) or unroll >= 0), 'unroll must be bool or non-negative int'","typeGuard":"def valid_unroll(u) -> bool:\n    return isinstance(u, bool) or (isinstance(u, int) and u >= 0)","tryCatchPattern":"null","preventionTips":["Never use negative sentinels for unroll","Clamp computed unroll values: max(1, n)","Document chosen unroll factors next to scan calls"],"tags":["jax","scan","unroll","valueerror","argument-validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb","analyzedAt":"2026-08-27T09:53:25.647Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}