openclaw/openclaw · critical · CodexAppServerUnsafeSubscriptionError

Codex thread/resume outcome is indeterminate for ${threadId}

Error message

Codex thread/resume outcome is indeterminate for ${threadId}

What it means

Same thread/resume error path in resumeCodexAppServerThread, reached after the client was successfully abandoned. The caught value is not an Error instance (and not already a CodexAppServerUnsafeSubscriptionError), so there is no message to propagate; the outcome of the resume is genuinely unknown and is wrapped as an unsafe-subscription error.

Source

Thrown at extensions/codex/src/app-server/thread-resume.ts:56

      throw error;
    }
    if (error instanceof CodexAppServerRpcError) {
      // A structured RPC error proves Codex rejected the resume, so the client
      // holds no hidden subscription and can safely stay in the shared pool.
      throw error;
    }
    try {
      await params.abandonClient();
    } catch (abandonError) {
      throw new CodexAppServerUnsafeSubscriptionError(
        `Codex thread/resume client could not be retired for ${threadId}`,
        { cause: abandonError },
      );
    }
    if (error instanceof CodexAppServerUnsafeSubscriptionError) {
      throw error;
    }
    throw new CodexAppServerUnsafeSubscriptionError(
      error instanceof Error
        ? error.message
        : `Codex thread/resume outcome is indeterminate for ${threadId}`,
      { cause: error },
    );
  }
  return response;
}

View on GitHub (pinned to 01804a7531)

Solutions

  1. Identify the upstream component that rejected with a non-Error value and make it throw a real Error subclass.
  2. After this error, reconnect the app-server client since the prior client was abandoned.
  3. Log the raw cause to diagnose which dependency produced the non-Error rejection.
Defensive patterns

Strategy: try-catch

Type guard

import { CodexAppServerUnsafeSubscriptionError } from './app-server/attempt-client-cleanup.js';
function isUnsafeSubscriptionError(e) { return e instanceof CodexAppServerUnsafeSubscriptionError; }

Try / catch

try { await resumeCodexAppServerThread({ client, abandonClient, request }); }
catch (e) {
  if (e instanceof CodexAppServerUnsafeSubscriptionError && /indeterminate/.test(e.message)) {
    logRawCause(e.cause); await reconnectAppServerClient();
  }
  throw e;
}

Prevention

When it happens

Trigger: The resume promise rejects with a non-Error value (throw "string", throw 42, or Promise.reject with a plain object) and abandonClient() then succeeds.

Common situations: A transport or validator dependency that rejects with a string or plain object instead of a real Error; defensive coding in an upstream module that throws non-Error values.

Related errors


AI-assisted analysis of openclaw/openclaw@01804a7531 (2026-08-12). Data as JSON: /api/errors/2f6d55675275e7fb. Report an issue: GitHub.