{"record":{"id":"749a215bdb3a9cc1","repo":"Lightning-AI/pytorch-lightning","slug":"when-optimizer-step-closure-is-called-the-clos","errorCode":null,"errorMessage":"When `optimizer.step(closure)` is called, the closure should be callable","messagePattern":"When `optimizer\\.step\\(closure\\)` is called, the closure should be callable","errorType":"exception","errorClass":"MisconfigurationException","httpStatus":null,"severity":"error","filePath":"src/lightning/pytorch/core/optimizer.py","lineNumber":151,"sourceCode":"                with opt_gen.toggle_model(sync_grad=accumulated_grad_batches):\n                    opt_gen.step(closure=closure_gen)\n\n                def closure_dis():\n                    loss_dis = self.compute_discriminator_loss(...)\n                    self.manual_backward(loss_dis)\n                    if accumulated_grad_batches:\n                        opt_dis.zero_grad()\n\n                with opt_dis.toggle_model(sync_grad=accumulated_grad_batches):\n                    opt_dis.step(closure=closure_dis)\n\n        \"\"\"\n        self._on_before_step()\n\n        if closure is None:\n            closure = do_nothing_closure\n        elif not callable(closure):\n            raise MisconfigurationException(\"When `optimizer.step(closure)` is called, the closure should be callable\")\n\n        assert self._strategy is not None\n        step_output = self._strategy.optimizer_step(self._optimizer, closure, **kwargs)\n\n        self._on_after_step()\n\n        return step_output\n\n    @classmethod\n    def _to_lightning_optimizer(\n        cls, optimizer: Union[Optimizer, \"LightningOptimizer\"], strategy: \"pl.strategies.Strategy\"\n    ) -> \"LightningOptimizer\":\n        # the user could return a `LightningOptimizer` from `configure_optimizers`, see test:\n        # tests/core/test_lightning_optimizer.py::test_lightning_optimizer[False]\n        lightning_optimizer = optimizer if isinstance(optimizer, LightningOptimizer) else cls(optimizer)\n        lightning_optimizer._strategy = proxy(strategy)\n        return lightning_optimizer\n","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/Lightning-AI/pytorch-lightning/blob/9fed5c27d2a62ff0efd6c3573599921d6ff67c14/src/lightning/pytorch/core/optimizer.py#L133-L169","documentation":"LightningOptimizer.step(closure) requires the closure to be callable (it is re-executed by the strategy during optimization). Passing a non-callable (e.g. a tensor, result of calling the closure, or None-like object) raises MisconfigurationException.","triggerScenarios":"Calling optimizer.step(training_step(...)) — i.e. passing the closure's return value instead of the function — or passing a non-function object in manual optimization.","commonSituations":"Manual optimization code that accidentally invokes the closure: optimizer.step(self.training_step(batch, batch_idx)) instead of passing the method reference.","solutions":["Pass the closure itself, not its result: optimizer.step(closure) where closure is a zero-arg callable","Wrap logic in a def or lambda: optimizer.step(lambda: self.training_step(batch, batch_idx))"],"exampleFix":"# before\noptimizer.step(self.training_step(batch, batch_idx))\n# after\noptimizer.step(lambda: self.training_step(batch, batch_idx))","handlingStrategy":"type-guard","validationCode":"assert callable(closure), \"closure must be callable\"\noptimizer.step(closure)","typeGuard":"def is_closure(c) -> bool:\n    return callable(c)","tryCatchPattern":null,"preventionTips":["Pass method references or lambdas, never call results","Lint for optimizer.step(fn(...)) patterns"],"tags":["optimizer","closure","manual-optimization","lightning"],"backgroundTag":"argument-not-callable","analyzedSha":"9fed5c27d2a62ff0efd6c3573599921d6ff67c14","analyzedAt":"2026-08-28T11:52:41.083Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}