{"record":{"id":"987a5473f6712d72","repo":"jax-ml/jax","slug":"custom-vjp-bwd-rule-self-bwd-must-produce-a-tupl","errorCode":null,"errorMessage":"Custom VJP bwd rule {self.bwd} must produce a tuple but got {type(in_cts)}.","messagePattern":"Custom VJP bwd rule (.+?) must produce a tuple but got (.+?)\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"jax/_src/hijax.py","lineNumber":797,"sourceCode":"    in_cts = self.bwd(*static_args, res, out_ct)\n    logs = None\n    if self.with_logs:\n      if not (isinstance(in_cts, (list, tuple)) and len(in_cts) == 2):\n        raise TypeError(\n            f\"Custom VJP bwd rule {self.bwd} was registered with \"\n            \"defvjp_with_logs and so must produce a pair (in_cts, logs), \"\n            f\"but got {in_cts}.\")\n      in_cts, logs = in_cts\n      if logs is not None and type(logs) is not dict:\n        raise TypeError(\n            f\"Custom VJP bwd rule {self.bwd} was registered with \"\n            \"defvjp_with_logs, and so the second element of the pair it \"\n            \"returns must be None or a dict of backward-pass log entries, \"\n            f\"but got {type(logs).__name__}.\")\n    if isinstance(in_cts, list):\n      in_cts = tuple(in_cts)\n    if not isinstance(in_cts, tuple):\n      raise TypeError(f\"Custom VJP bwd rule {self.bwd} must produce a tuple \"\n                      f\"but got {type(in_cts)}.\")\n    in_cts = (None, None, *in_cts)  # zero cts for the consts and fwd_consts args\n    if len(in_cts) != len(self.in_tree.children()) - len(self.static_argnums):\n      raise ValueError(f\"Custom VJP bwd rule {self.bwd} must produce a tuple \"\n                       \"of length equal to the primal args tuple, but got \"\n                       f\"length {len(in_cts)}\")\n    in_cts = broadcast_prefix(in_cts, in_avals_, is_leaf=lambda x: x is None)\n    in_cts = tree_unflatten(self.in_tree, map(_replace_none, self.in_avals_flat, in_cts))\n    tree_map_with_path(partial(_vjp_bwd_aval_mismatch_err, self.traced._fun_sourceinfo),\n                               self.in_avals[2:], in_cts[2:])\n    if self.symbolic_zeros:\n      in_cts = tree_map(ad_util.replace_rule_output_symbolic_zeros, in_cts)\n    return (in_cts, logs) if self.with_logs else in_cts\n\n  def jvp(self, primals, tangents):\n    if self.symbolic_zeros: ad.raise_custom_vjp_error_on_jvp()\n    zero = lambda x: isinstance(x, ad_util.Zero)\n    nzs_in = tuple(tree_map(lambda t: not isinstance(t, ad_util.Zero), t,","sourceCodeStart":779,"sourceCodeEnd":815,"githubUrl":"https://github.com/jax-ml/jax/blob/1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb/jax/_src/hijax.py#L779-L815","documentation":"After optional log handling, the custom VJP backward rule's in_cts must be a tuple (lists are coerced); any other type raises TypeError before cotangents are aligned to inputs.","triggerScenarios":"A bwd rule returning an array, generator, or single value instead of a tuple of per-argument cotangents.","commonSituations":"bwd written for a single-argument function returns a bare array instead of `(ct,)`.","solutions":["Return a tuple with one cotangent per non-static primal argument, e.g. `(ct,)` for one argument","Wrap list returns are auto-converted, but prefer tuples explicitly"],"exampleFix":"# before\ndef bwd(res, ct):\n    return ct * w\n# after\ndef bwd(res, ct):\n    return (ct * w,)","handlingStrategy":"type-guard","validationCode":"cts = bwd(*static_args, res, out_ct)\ncts = cts if type(cts) is tuple else (cts if isinstance(cts, tuple) else None)\nassert isinstance(cts, tuple), 'bwd must return a tuple of cotangents'","typeGuard":"def cts_is_tuple(bwd_out) -> bool:\n    return isinstance(bwd_out, tuple) or isinstance(bwd_out, list)","tryCatchPattern":"try:\n    jax.grad(f)(x)\nexcept TypeError as e:\n    if 'must produce a tuple' in str(e):\n        raise RuntimeError('wrap single cotangent: return (ct,)') from e\n    raise","preventionTips":["Always return a tuple, even for one argument","List returns work but tuples are idiomatic"],"tags":["jax","custom-vjp","return-type"],"backgroundTag":"custom-vjp-return-shape","analyzedSha":"1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb","analyzedAt":"2026-08-27T09:53:25.647Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}