{"record":{"id":"2a282a318be4f503","repo":"linera-io/linera-protocol","slug":"crossapplicationcallinfinalize","errorCode":"CrossApplicationCallInFinalize","errorMessage":"ExecutionError::CrossApplicationCallInFinalize { caller_id: Box::new(self.current_application().id), callee_id: Box::new(callee_id) }","messagePattern":"ExecutionError::CrossApplicationCallInFinalize (.+?)","errorType":"exception","errorClass":"ExecutionError","httpStatus":null,"severity":"error","filePath":"linera-execution/src/runtime.rs","lineNumber":454,"sourceCode":"\n                self.applications_to_finalize.push(id);\n                Ok(entry\n                    .insert(LoadedApplication::new(instance, description))\n                    .clone())\n            }\n        }\n    }\n\n    /// Configures the runtime for executing a call to a different contract.\n    fn prepare_for_call(\n        &mut self,\n        this: ContractSyncRuntimeHandle,\n        authenticated: bool,\n        callee_id: ApplicationId,\n    ) -> Result<Arc<Mutex<UserContractInstance>>, ExecutionError> {\n        self.check_for_reentrancy(callee_id)?;\n\n        ensure!(\n            !self.is_finalizing,\n            ExecutionError::CrossApplicationCallInFinalize {\n                caller_id: Box::new(self.current_application().id),\n                callee_id: Box::new(callee_id),\n            }\n        );\n\n        // Load the application.\n        let application = self.load_contract_instance(this, callee_id)?;\n\n        let caller = self.current_application();\n        let caller_id = caller.id;\n        let caller_signer = caller.signer;\n        // Make the call to user code.\n        let authenticated_owner = match caller_signer {\n            Some(signer) if authenticated => Some(signer),\n            _ => None,\n        };","sourceCodeStart":436,"sourceCodeEnd":472,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-execution/src/runtime.rs#L436-L472","documentation":"prepare_for_call blocks cross-application calls while the runtime is finalizing: during an application's finalize stage it may touch its own state but must not call other applications, so try_call_application (or create_application) issued from finalize fails with CrossApplicationCallInFinalize, reporting caller and callee ids (runtime.rs:454).","triggerScenarios":"Calling try_call_application or create_application from inside an application's finalize handler, e.g. cleanup or notification logic left in finalize that reaches another application.","commonSituations":"Moving cleanup or notification logic into finalize while leaving cross-app calls in place; frameworks that run shared handler code in both operation and finalize contexts; upgrades where a code path now executes during finalization.","solutions":["Move the cross-application call out of finalize into the regular operation or message handler","In finalize, only adjust local state; queue the interaction so the next operation performs it","If a shared code path runs in both contexts, gate it on whether the runtime is finalizing"],"exampleFix":"// before\nfn finalize(&mut self) {\n    runtime.try_call_application(other_app, msg)?;\n}\n\n// after\nfn finalize(&mut self) {\n    self.pending.push(msg); // drained by the next operation\n}","handlingStrategy":"validation","validationCode":"// Application-level guard: keep cross-app calls out of finalize\nenum Stage { Operation, Finalize }\n\nfn maybe_call_app(stage: Stage, callee: ApplicationId, arg: Vec<u8>) {\n    match stage {\n        Stage::Operation => { runtime.try_call_application(callee, arg); }\n        Stage::Finalize => { self.pending.push((callee, arg)); } // deferred to next operation\n    }\n}","typeGuard":"fn is_cross_app_call_in_finalize(err: &ExecutionError) -> bool {\n    matches!(err, ExecutionError::CrossApplicationCallInFinalize { .. })\n}","tryCatchPattern":"match runtime.try_call_application(callee, arg) {\n    Ok(out) => out,\n    Err(ref e) if is_cross_app_call_in_finalize(e) => {\n        // defer the interaction; finalize must only touch local state\n        self.pending_calls.push((callee, arg));\n        Ok(vec![])\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Keep finalize handlers limited to local state changes; schedule any cross-app interaction for the next operation","If shared handler code runs in both contexts, gate cross-app calls on the runtime stage","Add a test that runs finalize and asserts no cross-application calls are attempted"],"tags":["finalize","cross-application-call","runtime","linera"],"backgroundTag":"invalid-call-context","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}