Skip to main content
Last updated on

Error Handling

Trust decisions for Activity execution surface as Temporal ApplicationError exceptions. A Workflow observes the enclosing ActivityError and can inspect its cause. The plugin uses ApplicationError.type to distinguish governance outcomes.

Governance Error Types

The plugin raises ApplicationError with one of these type strings:

Error TypeDecisionRetryableDescription
"GovernanceBlock"BLOCKNoCurrent operation blocked
"GovernanceHalt"HALTNoWorkflow termination requested
"GovernanceConstrainUnsupported"CONSTRAINNoIntegration cannot enforce the returned constraint
"ApprovalPending"REQUIRE_APPROVALYesAwaiting human review
"ApprovalRejected"REQUIRE_APPROVAL (rejected)NoHuman rejected request
"ApprovalExpired"REQUIRE_APPROVAL (timeout)NoNo response before timeout

All governance errors are standard Temporal ApplicationError instances with these properties:

PropertyTypeDescription
messagestrHuman-readable description (e.g., "Governance blocked: PII detected")
typestrThe governance type string from the table above
non_retryableboolIf True, Temporal will not retry the activity

Workflow-level handling

The plugin wraps Activity execution, so a governance ApplicationError normally occurs outside your Activity function. At Workflow level, Temporal wraps it in ActivityError; inspect the cause:

from temporalio.exceptions import ActivityError, ApplicationError


def application_error(error: ActivityError) -> ApplicationError | None:
cause = error.cause
return cause if isinstance(cause, ApplicationError) else None

Handle terminal decisions without blindly retrying the operation:

@workflow.defn
class MyAgentWorkflow:
@workflow.run
async def run(self, input: WorkflowInput) -> WorkflowOutput:
try:
result = await workflow.execute_activity(
sensitive_operation,
input.data,
start_to_close_timeout=timedelta(minutes=10),
)
return WorkflowOutput(result=result)
except ActivityError as error:
cause = application_error(error)
if cause is None:
raise

if cause.type in {"GovernanceBlock", "GovernanceConstrainUnsupported"}:
return WorkflowOutput(status="blocked", reason=cause.message)

if cause.type in {"ApprovalRejected", "ApprovalExpired"}:
return WorkflowOutput(status="rejected", reason=cause.message)

# GovernanceHalt terminates the run; do not recover it as success.
raise

ApprovalPending is retryable for ordinary approval-gated Activities. Let it propagate so Temporal retries and the plugin polls the approval. ApprovalRejected and ApprovalExpired are terminal. GovernanceBlock, GovernanceHalt, and GovernanceConstrainUnsupported are non-retryable.

Governed-command failures

A governed command must not be retried after an indeterminate dispatch, because a second attempt could repeat a side effect. OpenBoxPlugin intercepts the application's Activity and the dispatcher makes at most one possible execution dispatch for each stable dispatch ID.

ApplicationError.typeMeaning
GovernedCommandConfigurationRequiredWorker did not configure sandbox support
GovernedCommandInvalidProfile, arguments, identity, or derived command was rejected
GovernedDispatcherFailureDispatcher failed before returning a valid terminal result
GovernedCommandResultInvalidOutput did not match the registered typed-result schema
GovernedCommandNotExecutedGovernance or execution ended without accepted sandbox execution
GovernedCommandExecutionIndeterminateThe plugin cannot establish whether execution reached a safe terminal outcome
BehavioralSandboxExecutionFailedA behavioral CONSTRAIN replacement profile failed; retained sandbox evidence is attached to the error

At Workflow level, Temporal wraps the intercepted user Activity's ApplicationError in ActivityError. Inspect its cause using the same Workflow-level pattern above, alert or reconcile external state, and do not schedule a replacement command after a possible dispatch.

For a started-hook CONSTRAIN, the plugin aborts the attempted host action and uses the sandbox outcome. An ALLOW decision follows the application's normal host path, so a zero-host workflow must ensure the applicable Core decision is CONSTRAIN. See Governed Sandbox Commands.

Cancellation waits for dispatcher cleanup before the Activity finishes cancelling. Preserve that cancellation path; do not add a second scheduling retry. Raw output and credentials remain outside Workflow history even on failure.

Best Practices

  1. Let ApprovalPending propagate - The plugin handles retries for ordinary approval-gated Activities
  2. Log terminal governance errors with context - Helps debugging
  3. Consider fallback behavior for GovernanceBlock - A blocked operation need not become a successful result
  4. Do not recover GovernanceHalt - Terminate the current run
  5. Don't catch and ignore - These exceptions are intentional
  6. Never retry a governed command - Reconcile its external state instead

Configuration Exceptions

The plugin raises configuration exceptions from openbox.config during OpenBoxPlugin() initialization, not during activity execution. Handle these where you initialize your worker.

ExceptionCause
OpenBoxConfigErrorBase class for all configuration errors
OpenBoxAuthErrorInvalid or missing API key
OpenBoxNetworkErrorCannot reach OpenBox Core
OpenBoxInsecureURLErrorHTTP used for a non-localhost URL

Next Steps

Now that you understand how to handle trust decisions in code:

  1. Governed Sandbox Commands - Understand one-attempt command failures and cleanup
  2. Troubleshooting - Common issues and solutions
  3. Handle Approvals - Review and process HITL requests in the dashboard