Apache Maka SessionStatus States and Blocked Session Handling Explained

Apache Maka defines five SessionStatus states—active, running, waiting_for_user, blocked, and aborted—with blocked sessions recording a typed blockedReason that drives both recovery logic and UI presentation.

The SessionStatus enum in Apache Maka forms the backbone of conversation lifecycle management. This open-source framework models user-driven AI sessions as state machines, where transitions between statuses trigger persistence updates, UI changes, and recovery workflows. Understanding these states—and specifically how the blocked state enables graceful error recovery—is essential for building resilient Maka applications.

The Five SessionStatus States in Apache Maka

The canonical definition lives in packages/core/src/session.ts. The SESSION_STATUSES array and accompanying TypeScript types define five distinct phases:

  • active — The session exists in storage but no Runtime Host has attached to it yet. This is the initial state after session creation.

  • running — A live Runtime Host is actively executing the session. One or more conversation turns may be in flight, with the model generating responses or tools executing.

  • waiting_for_user — The runtime has voluntarily paused because it requires additional input from the user. This commonly occurs when a permission request is pending or the workflow needs explicit confirmation.

  • blocked — Execution has stopped due to a recoverable error. Unlike aborted, blocked sessions can be resumed once the underlying issue is resolved. This state carries a mandatory blockedReason field.

  • aborted — The session has terminated permanently. This occurs when the user explicitly stops the session or an unrecoverable error prevents any further progress.

The SessionStatus type itself is defined as a union of these string literals, with runtime guards like isSessionStatus() available for type narrowing.

SessionBlockedReason: Why Sessions Become Blocked

When a session enters the blocked state, Apache Maka records a specific SessionBlockedReason to enable targeted recovery. Also defined in packages/core/src/session.ts, the SESSION_BLOCKED_REASONS array contains five possible values:

Blocked Reason Recovery Action Required
NO_REAL_CONNECTION Configure a usable model connection before resuming
auth Re-authenticate the user (e.g., refresh expired credentials)
permission_required User must grant a permission through the request UI
tool_failed A tool call failed; user intervention needed to retry or modify parameters
unknown Unexpected error occurred; session can be retried generically

The SessionBlockedReason type is intentionally granular. This allows both automated recovery workflows and precise UI messaging without parsing error strings.

How Blocked Session Handling Works in the Runtime

The transition to blocked status involves three coordinated components: projection helpers that build persistence patches, the session manager that applies updates, and storage layers that ensure durability.

Building Status Patches with buildStatusPatch

In packages/runtime/src/session-projection-helpers.ts, the buildStatusPatch function creates the partial update sent to durable storage:

function buildStatusPatch(
  status: SessionStatus,
  ts: number,
  blockedReason?: SessionBlockedReason,
): Pick<SessionHeader, 'status' | 'blockedReason' | 'statusUpdatedAt'> {
  return {
    status,
    blockedReason: status === 'blocked' ? (blockedReason ?? 'unknown') : undefined,
    statusUpdatedAt: ts,
  };
}

This helper enforces a critical invariant: blockedReason is only persisted when status equals blocked. For any other status, the field is explicitly cleared to undefined, preventing stale reason data from confusing recovery logic.

Mapping Errors to Blocked Reasons

The same file provides blockedReasonFromErrorReason(), which translates internal error strings into public SessionBlockedReason values:

// Internal runtime error → user-facing blocked reason
const reason = blockedReasonFromErrorReason('permission_required');
// Returns: 'permission_required' (typed as SessionBlockedReason)

This indirection layer insulates the persistence schema from internal error naming conventions.

SessionManager Persistence Flow

The SessionManager in packages/runtime/src/session-manager.ts orchestrates the actual status change. When the runtime encounters a recoverable failure, it calls setSessionStatus():

await setSessionStatus(sessionId, 'blocked', 'tool_failed');

Behind the scenes, this invokes updateHeader(sessionId, buildStatusPatch(...)) to atomically persist the new SessionHeader. The storage layer in packages/storage/src/session-store.ts applies the isSessionBlockedReason guard during serialization, ensuring type safety across the persistence boundary.

Rendering Blocked Sessions in the UI

The presentation layer converts raw status data into user-facing elements. In packages/ui/src/session-status-presentation.ts, the presentSessionStatus() function accepts a SessionStatus and optional blockedReason, returning display primitives:

import { presentSessionStatus } from '@maka/ui/session-status-presentation';

const { label, icon, variant, tone } = presentSessionStatus('blocked', 'permission_required', 'en');
// Returns localized label "Permission Required", warning variant, etc.

A companion file, conversation-copy.ts, provides message templates that incorporate the blocked reason into explanatory text. This separation between data (SessionStatus), reason (SessionBlockedReason), and presentation (presentSessionStatus) enables consistent UX across web, desktop, and CLI clients.

Complete Code Examples

Blocking a Session from Runtime Code

import { setSessionStatus } from '@maka/runtime/session-manager';
import { SessionStatus, SessionBlockedReason } from '@maka/core/session';

async function handleConnectionFailure(sessionId: string) {
  const blockedReason: SessionBlockedReason = 'NO_REAL_CONNECTION';
  await setSessionStatus(sessionId, 'blocked', blockedReason);
}

Reading Blocked Status from Storage

import { getSessionHeader } from '@maka/storage/session-store';

async function checkSessionHealth(sessionId: string): Promise<boolean> {
  const header = await getSessionHeader(sessionId);
  
  if (header.status === 'blocked') {
    console.error(`Blocked: ${header.blockedReason}`);
    return false;
  }
  return header.status === 'running';
}

React Component for Session Status Badge

import { presentSessionStatus } from '@maka/ui/session-status-presentation';
import type { SessionSummary } from '@maka/core/session';

function SessionStatusBadge({ session }: { session: SessionSummary }) {
  const presentation = presentSessionStatus(
    session.status, 
    session.blockedReason,
    navigator.language
  );
  
  return (
    <span className={`badge badge-${presentation.variant}`}>
      {presentation.label}
    </span>
  );
}

Key Source Files for SessionStatus Handling

File Responsibility
packages/core/src/session.ts Core type definitions: SessionStatus, SessionBlockedReason, SessionHeader, type guards
packages/runtime/src/session-projection-helpers.ts buildStatusPatch(), blockedReasonFromErrorReason() for status transitions
packages/runtime/src/session-manager.ts setSessionStatus(), updateHeader() for orchestrating persistence
packages/storage/src/session-store.ts SQLite persistence with isSessionBlockedReason validation
packages/ui/src/session-status-presentation.ts presentSessionStatus() for converting status to UI primitives

Summary

  • Apache Maka defines five session states in packages/core/src/session.ts: active, running, waiting_for_user, blocked, and aborted.

  • The blocked state is recoverable and always carries a blockedReason from the five possible values: NO_REAL_CONNECTION, auth, permission_required, tool_failed, or unknown.

  • Status transitions use buildStatusPatch() in session-projection-helpers.ts to enforce that blockedReason only persists alongside blocked status.

  • Error mapping through blockedReasonFromErrorReason() decouples internal errors from the public schema.

  • Full lifecycle flow: Runtime detects error → reason mapping → patch building → SessionManager persistence → session-store durability → UI presentation via presentSessionStatus().

Frequently Asked Questions

What triggers a session to enter the blocked state?

A session becomes blocked when the runtime encounters a recoverable error that requires user action before execution can continue. Common triggers include: a tool call throwing an exception (tool_failed), the user needing to re-authenticate (auth), a missing model connection (NO_REAL_CONNECTION), or pending permission grants (permission_required). Unlike aborted, blocked sessions retain their conversation history and can be resumed.

How does Apache Maka prevent stale blockedReason values in session storage?

The buildStatusPatch() helper in session-projection-helpers.ts explicitly sets blockedReason to undefined whenever the status is not 'blocked'. This ensures that transitioning from blocked to running or active automatically clears the reason field. The storage layer validates this through the isSessionBlockedReason guard when reading headers back from SQLite.

Can I customize the UI messages shown for each blocked reason?

Yes, but through the presentation layer rather than core types. The presentSessionStatus() function in packages/ui/src/session-status-presentation.ts maps each SessionBlockedReason to a label, icon variant, and tone. To customize messaging, you can wrap or replace this function while keeping the underlying SessionBlockedReason types unchanged—ensuring compatibility with the runtime's error handling logic.

What is the difference between waiting_for_user and blocked?

waiting_for_user represents an intentional pause by the runtime, typically for permission requests that are part of normal workflow execution. The system expects the user to respond, but no error occurred. blocked represents an unexpected or error condition that stopped execution—while recoverable, it indicates something went wrong rather than proceeding as designed.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →