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
- Identify the upstream component that rejected with a non-Error value and make it throw a real Error subclass.
- After this error, reconnect the app-server client since the prior client was abandoned.
- 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
- Ensure every dependency in the resume path rejects with a real Error, never a string or plain object.
- Wrap third-party callbacks so they never throw non-Error values.
- Treat indeterminate outcomes as reconnect triggers.
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
- Codex thread/resume client could not be retired for ${thread
- Codex resumed thread lost its native subscription owner.
- Codex thread/resume returned ${effectiveThreadId} for ${norm
- Codex thread/resume returned no cwd for ${normalizedThreadId
- Codex thread binding changed while attaching the resumed thr
AI-assisted analysis of openclaw/openclaw@01804a7531 (2026-08-12).
Data as JSON: /api/errors/2f6d55675275e7fb.
Report an issue: GitHub.