# Hard Aborts vs UI Handoff Interrupts in Session Management: Key Differences Explained

> Understand hard aborts vs UI handoff interrupts in session management. Learn how hard aborts permanently end sessions while UI handoffs temporarily pause for user interaction.

- Repository: [Craft Ai Agents/craft-agents-oss](https://github.com/craft-ai-agents/craft-agents-oss)
- Tags: deep-dive
- Published: 2026-07-06

---

**Hard aborts permanently terminate a Craft Agents session by aborting the underlying `AbortController`, while UI handoff interrupts temporarily pause execution at specific pause points—such as authentication requests—to allow user interaction before resuming.**

The craft-ai-agents/craft-agents-oss repository distinguishes between two critical session lifecycle mechanisms that control how agent processing stops. Understanding the difference between hard aborts and UI handoff interrupts in session management is essential for building responsive agent applications that handle both definitive cancellations and temporary UI-driven pauses correctly.

## What Are Hard Aborts in Session Management?

### Purpose and Use Cases

**Hard aborts** represent true cancellation events that tear down the entire session. According to the documentation in [`packages/shared/CLAUDE.md`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/CLAUDE.md), these occur when a user explicitly stops the chat, when a redirect fallback is needed, or when the session is being dismantled. The system treats these as terminal states that require full resource cleanup.

The typical reasons for hard aborts include:
- `AbortReason.UserStop` – User explicitly terminates the conversation
- `AbortReason.Redirect` – Navigation requires ending the current session
- `AbortReason.Teardown` – System-initiated session destruction

### Implementation via forceAbort

In [`packages/shared/src/agent/base-agent.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/base-agent.ts), the `BaseAgent` class defines the abstract `forceAbort(reason)` method. Concrete implementations abort the underlying `AbortController` and trigger comprehensive cleanup routines. The `AbortReason` enum—defined in [`packages/shared/src/agent/backend/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/backend/types.ts)—provides the specific reason codes that inform the backend why the termination occurred.

When `forceAbort` executes, the session manager logs the completion and runs cleanup logic. As implemented in [`packages/server-core/src/sessions/SessionManager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/server-core/src/sessions/SessionManager.ts), this results in the log message "Chat completed after explicit handoff/stop" and explicitly skips normal completion handling.

```typescript
// Somewhere in UI code handling a "Stop" button
await session.abort(AbortReason.UserStop);
// Triggers BaseAgent.forceAbort downstream, aborting the AbortController

```

## What Are UI Handoff Interrupts?

### Purpose and Pause Points

**UI handoff interrupts** occur at designated "pause points" where control passes back to the UI without terminating the session. These handle events like `AuthRequest` or `PlanSubmitted`, allowing the user to authenticate or review plans before the agent continues processing. Unlike hard aborts, the session is not finished; it merely yields to the UI so the user can act.

### Implementation via interruptForHandoff

The `BaseAgent` class provides `interruptForHandoff(reason)` with a default implementation that delegates to `forceAbort`. However, backend-specific agents can override this to use lighter-weight interruptions. For example, the Claude backend overrides this method to call `this.query.interrupt()` instead of aborting the `AbortController`, preserving the underlying connection and session state while the UI handles the handoff.

The test file [`packages/shared/src/agent/__tests__/claude-agent-handoff.test.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/__tests__/claude-agent-handoff.test.ts) verifies that these handoff interrupts use `Query.interrupt()` rather than triggering a hard abort.

```typescript
// In ClaudeAgent implementation (simplified)
interruptForHandoff(reason: AbortReason) {
  this.debug('Claude handoff interrupt');
  // Uses SDK interrupt instead of AbortController abort
  this.query.interrupt();
}

```

## Key Differences Between Hard Aborts and UI Handoff Interrupts

Understanding how these mechanisms diverge helps you choose the correct approach for your session management logic:

- **Session Lifecycle Impact**: Hard aborts mark the session as finished, releasing resources and preventing further events for that turn. UI handoff interrupts defer cleanup and allow the session to resume after the UI completes its work.

- **Implementation Mechanism**: Hard aborts always call `forceAbort` and abort the `AbortController`. UI handoff interrupts call `interruptForHandoff`, which may use backend-specific interruption logic (like Claude's `Query.interrupt()`) or default to a hard abort.

- **Abort Reasons**: Hard aborts use `AbortReason.UserStop`, `AbortReason.Redirect`, and `AbortReason.Teardown`. UI handoff interrupts use `AbortReason.AuthRequest` and `AbortReason.PlanSubmitted`.

## Practical Code Examples

### Triggering a Hard Abort

When implementing a "Stop" button or handling fatal errors, use `forceAbort` to ensure complete termination:

```typescript
import { BaseAgent, AbortReason } from '@craft-agents/shared';

class MyAgent extends BaseAgent {
  async handleUserStop() {
    // This triggers a full session teardown
    await this.forceAbort(AbortReason.UserStop);
  }
}

```

### Implementing UI Handoff Interrupts

For authentication flows or plan reviews, override `interruptForHandoff` to preserve session state:

```typescript
// Conceptual implementation in Claude backend
class ClaudeAgent extends BaseAgent {
  interruptForHandoff(reason: AbortReason) {
    if (reason === AbortReason.AuthRequest) {
      // Lighter weight than forceAbort - preserves the stream
      this.query.interrupt();
      return;
    }
    super.interruptForHandoff(reason);
  }
}

```

### Backend-Agnostic Control Flow

You can conditionally choose between the two approaches based on the abort reason:

```typescript
function stopOrHandoff(session: BaseAgent, reason: AbortReason) {
  if (reason === AbortReason.UserStop || reason === AbortReason.Teardown) {
    session.forceAbort(reason);   // Hard abort
  } else {
    session.interruptForHandoff(reason); // UI handoff interrupt
  }
}

```

## Summary

- **Hard aborts** permanently terminate Craft Agents sessions via `forceAbort`, aborting the `AbortController` and running full cleanup routines in `SessionManager`.
- **UI handoff interrupts** use `interruptForHandoff` to pause at specific points, allowing the UI to handle authentication or plan review before resuming the session.
- The default implementation of `interruptForHandoff` delegates to `forceAbort`, but backends like Claude override this to preserve session state using `Query.interrupt()` as verified in [`claude-agent-handoff.test.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/claude-agent-handoff.test.ts).
- Hard aborts use reasons like `UserStop` and `Redirect`, while handoff interrupts use `AuthRequest` and `PlanSubmitted` defined in [`packages/shared/src/agent/backend/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/backend/types.ts).

## Frequently Asked Questions

### When should I use forceAbort versus interruptForHandoff?

Use `forceAbort` when you need to completely stop the session and release all resources, such as when a user clicks a "Stop" button or when handling a redirect. Use `interruptForHandoff` when you need to pause execution for UI interaction but expect the session to continue afterward, such as during authentication flows or plan submission reviews.

### Does interruptForHandoff always perform a hard abort?

No. While the default implementation in `BaseAgent` delegates to `forceAbort`, specific backends can override this behavior. For example, the Claude backend implements a lighter-weight interruption via `Query.interrupt()`, which pauses the model without tearing down the underlying stream or aborting the `AbortController`.

### What happens to session state during a UI handoff interrupt?

The session state is preserved during a UI handoff interrupt. Unlike hard aborts, which trigger cleanup and mark the session as finished, handoff interrupts defer session-level cleanup. The system skips normal completion handling for the current turn but maintains the session context, allowing execution to resume after the UI completes the handoff process.

### Where are the AbortReason enum values defined?

The `AbortReason` enum is defined in [`packages/shared/src/agent/backend/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/agent/backend/types.ts). This enum includes values for both hard aborts (`UserStop`, `Redirect`, `Teardown`) and UI handoff interrupts (`AuthRequest`, `PlanSubmitted`), allowing the system to distinguish between terminal cancellations and temporary pause points.