# How the OpenWork Automation Runner Handles Model Access Lost Errors

> Learn how the OpenWork automation runner handles model access lost errors by skipping runs and logging a model_access_lost error code for seamless troubleshooting.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-16

---

**The OpenWork automation runner validates model access every time a run is claimed, and if the owner no longer has permission to use the selected model, it calls `skipRun` to mark the automation as skipped with a structured `model_access_lost` error code.**

The `different-ai/openwork` repository implements a robust automation system that prevents unauthorized model usage by verifying permissions at dispatch time. When an automation runner attempts to claim a run, it checks whether the owner still has valid membership and model access before proceeding. Understanding how the OpenWork automation runner handles model access lost errors is essential for maintaining reliable scheduling and avoiding compute waste.

## Model Access Validation in the Automation Runner

Before executing any automation, the runner invokes **`resolveAutomationModelAccessWithStore`** (exported publicly as `resolveAutomationModelAccess`) from [`ee/apps/den-api/src/automations/authority.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/automations/authority.ts). This function performs a three-layer validation to ensure the automation owner retains the right to invoke the specified model.

### The Three-Layer Permission Check

The authority system validates the following in sequence:

- **Owner membership** – `findActiveMember` verifies the owner is still an active member of the organization. If not, it returns `owner_membership_lost`.
- **Provider availability** – `findProvider` or `findOpenWorkProvider` confirms the provider (OpenWork, custom, or free tier) still exists. Failure yields `provider_unavailable`.
- **Model existence and permission** – `findModel` combined with `canAccessProvider` ensures the model is present and the member retains access. If either check fails, the function returns `model_access_lost`.

When any layer fails, the function returns an **`AutomationAuthorityFailure`** object:

```ts
{
  ok: false,
  code: "model_access_lost",
  message: "...human-readable explanation..."
}

```

## Handling Failed Access in the Repository Layer

When the runner receives a failure result from the authority check, the automation service immediately calls **`skipRun`** implemented in [`ee/apps/den-api/src/automations/repository.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/src/automations/repository.ts). This method durably marks the run as non-executable to prevent wasted compute and infinite retry loops.

The `skipRun` method updates the `AutomationRun` record with:

- `status: "skipped"`
- `error: { code: "model_access_lost", ... }`
- `result_summary` and `finished_at` timestamps

This guarantees the run is permanently recorded as not executed according to the source code in [`repository.ts`](https://github.com/different-ai/openwork/blob/main/repository.ts).

## Complete Error Flow from Claim to Skip

The end-to-end handling follows this strict sequence:

1. **Claim** – The runner fetches the automation definition and calls `resolveAutomationModelAccess`.
2. **Success** – If the model remains accessible, the run proceeds from queued to running status.
3. **Failure** – If resolution returns `ok: false` with `code: "model_access_lost"`, the runner invokes `skipRun` with the error details.
4. **Result** – The run appears in the UI as *skipped* with a clear error message, and the automation’s next-due time updates according to its schedule.

The test suite in [`ee/apps/den-api/test/automations-model-authority.test.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/test/automations-model-authority.test.ts) explicitly verifies this behavior, including a test that ensures removed memberships cannot inherit model access and that revoked providers trigger the appropriate error codes.

## Code Examples

### Resolving Model Access

This snippet demonstrates how the runner checks permissions before dispatch:

```ts
import { resolveAutomationModelAccess } from '@/automations/authority'

const input = {
  organizationId: 'org_123',
  ownerMemberId: 'mem_456',
  providerId: 'openwork',
  modelId: 'z-ai/glm-5.2',
}

const result = await resolveAutomationModelAccess(input)

if (!result.ok) {
  // result.code will be "model_access_lost", "owner_membership_lost", etc.
  console.error(`Cannot run automation: ${result.message}`)
  // Runner proceeds to call repository.skipRun(...)
}

```

### Skipping a Run Due to Lost Access

When validation fails, the runner persists the skip state:

```ts
import { automationRepository } from '@/automations/repository'

await automationRepository.skipRun({
  runId: 'run_789',
  code: 'model_access_lost',
  message: 'The selected model is no longer available for this owner.',
  now: Date.now(),
})

```

### Runner Dispatch Workflow

A simplified view of the dispatch logic shows the guard pattern:

```ts
async function dispatchRun(run) {
  const access = await resolveAutomationModelAccess({
    organizationId: run.organizationId,
    ownerMemberId: run.ownerMemberId,
    providerId: run.revision.model.providerId,
    modelId: run.revision.model.modelId,
  })

  if (!access.ok) {
    await automationRepository.skipRun({
      runId: run.id,
      code: access.code,
      message: access.message,
      now: Date.now(),
    })
    return // abort dispatch
  }

  // Continue with normal execution...
}

```

## Summary

- **Proactive validation** – The runner checks `resolveAutomationModelAccessWithStore` before every dispatch to verify the owner, provider, and model remain valid.
- **Structured error codes** – Failures return specific codes like `model_access_lost`, `owner_membership_lost`, or `provider_unavailable` defined in [`authority.ts`](https://github.com/different-ai/openwork/blob/main/authority.ts).
- **Durable skip mechanism** – The `skipRun` method in [`repository.ts`](https://github.com/different-ai/openwork/blob/main/repository.ts) marks runs as `skipped` with full error metadata, preventing retries and wasted resources.
- **Test coverage** – [`automations-model-authority.test.ts`](https://github.com/different-ai/openwork/blob/main/automations-model-authority.test.ts) validates that removed memberships and revoked providers correctly trigger access errors.

## Frequently Asked Questions

### What triggers a `model_access_lost` error in OpenWork?

The error triggers when the owner of an automation is no longer an active member of the organization, the provider has been removed, or the specific model is no longer accessible to that member. The `resolveAutomationModelAccess` function in [`authority.ts`](https://github.com/different-ai/openwork/blob/main/authority.ts) performs these checks and returns the error code before any model invocation occurs.

### How does the runner prevent retries after access is lost?

Instead of throwing an unhandled exception that might trigger retry logic, the runner explicitly calls `skipRun` from [`repository.ts`](https://github.com/different-ai/openwork/blob/main/repository.ts). This updates the database record with `status: "skipped"` and timestamps, signaling to the scheduler that the run was intentionally abandoned and should not be retried.

### Where is the error information stored when a run is skipped?

The error details are stored in the `AutomationRun` table within the `error` column as a JSON object containing the `code` (e.g., `"model_access_lost"`) and a human-readable `message`. The `result_summary` and `finished_at` fields are also populated to indicate completion state.

### How can I test the model access validation logic?

The repository includes comprehensive tests in [`ee/apps/den-api/test/automations-model-authority.test.ts`](https://github.com/different-ai/openwork/blob/main/ee/apps/den-api/test/automations-model-authority.test.ts). These tests simulate revoked memberships, deleted providers, and removed models to ensure the `resolveAutomationModelAccess` function returns the expected `AutomationAuthorityFailure` objects and that the runner correctly skips affected automations.