linera-io/linera-protocol · error · ExecutionError
CrossApplicationCallInFinalize
CrossApplicationCallInFinalize
Error message
ExecutionError::CrossApplicationCallInFinalize { caller_id: Box::new(self.current_application().id), callee_id: Box::new(callee_id) } What it means
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).
Source
Thrown at linera-execution/src/runtime.rs:454
self.applications_to_finalize.push(id);
Ok(entry
.insert(LoadedApplication::new(instance, description))
.clone())
}
}
}
/// Configures the runtime for executing a call to a different contract.
fn prepare_for_call(
&mut self,
this: ContractSyncRuntimeHandle,
authenticated: bool,
callee_id: ApplicationId,
) -> Result<Arc<Mutex<UserContractInstance>>, ExecutionError> {
self.check_for_reentrancy(callee_id)?;
ensure!(
!self.is_finalizing,
ExecutionError::CrossApplicationCallInFinalize {
caller_id: Box::new(self.current_application().id),
callee_id: Box::new(callee_id),
}
);
// Load the application.
let application = self.load_contract_instance(this, callee_id)?;
let caller = self.current_application();
let caller_id = caller.id;
let caller_signer = caller.signer;
// Make the call to user code.
let authenticated_owner = match caller_signer {
Some(signer) if authenticated => Some(signer),
_ => None,
};View on GitHub (pinned to 6c226ddcb3)
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
Example fix
// before
fn finalize(&mut self) {
runtime.try_call_application(other_app, msg)?;
}
// after
fn finalize(&mut self) {
self.pending.push(msg); // drained by the next operation
} Defensive patterns
Strategy: validation
Validate before calling
// Application-level guard: keep cross-app calls out of finalize
enum Stage { Operation, Finalize }
fn maybe_call_app(stage: Stage, callee: ApplicationId, arg: Vec<u8>) {
match stage {
Stage::Operation => { runtime.try_call_application(callee, arg); }
Stage::Finalize => { self.pending.push((callee, arg)); } // deferred to next operation
}
} Type guard
fn is_cross_app_call_in_finalize(err: &ExecutionError) -> bool {
matches!(err, ExecutionError::CrossApplicationCallInFinalize { .. })
} Try / catch
match runtime.try_call_application(callee, arg) {
Ok(out) => out,
Err(ref e) if is_cross_app_call_in_finalize(e) => {
// defer the interaction; finalize must only touch local state
self.pending_calls.push((callee, arg));
Ok(vec![])
}
Err(e) => Err(e),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ReentrantCall
- Failed to deserialize `Response` in cross-application call
- no signer found for owner ${owner}
- Invalid owner address
- Invalid owner address
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/2a282a318be4f503.
Report an issue: GitHub.