{"record":{"id":"2c9d7b5f19cb57c1","repo":"openclaw/openclaw","slug":"codex-retired-session-subscription-could-not-be-re","errorCode":null,"errorMessage":"Codex retired session subscription could not be released: ${binding.threadId}","messagePattern":"Codex retired session subscription could not be released: (.+?)","errorType":"exception","errorClass":"CodexAppServerUnsafeSubscriptionError","httpStatus":null,"severity":"critical","filePath":"extensions/codex/src/app-server/session-retirement.ts","lineNumber":63,"sourceCode":"    const clientLease = retainSharedCodexAppServerClientByInstanceId(binding.clientId);\n    if (!clientLease) {\n      return result;\n    }\n    try {\n      // Reset retires native-child ownership before unsubscribing its parent;\n      // late child completions must never reach a replacement session generation.\n      codexNativeSubagentMonitorRuntime.retireParent(clientLease.client, binding.threadId);\n      const released = await releaseCodexAppServerLiveThread(clientLease.client, binding.threadId);\n      if (!released && isIncognitoSessionKey(params.identity.sessionKey)) {\n        // Ephemeral threads have no rollout to resume, so they intentionally\n        // bypass idle eviction but still end with their owning OpenClaw session.\n        const unsubscribed = await unsubscribeCodexThreadBestEffort(clientLease.client, {\n          threadId: binding.threadId,\n          timeoutMs: CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS,\n        });\n        if (!unsubscribed) {\n          await closeCodexStartupClientBestEffort(clientLease.client);\n          throw new CodexAppServerUnsafeSubscriptionError(\n            `Codex retired session subscription could not be released: ${binding.threadId}`,\n          );\n        }\n      }\n    } finally {\n      clientLease.release();\n    }\n    return result;\n  });\n}\n","sourceCodeStart":45,"sourceCodeEnd":74,"githubUrl":"https://github.com/openclaw/openclaw/blob/01804a75319da4b69c9ab98ceaa30477e22b8c0b/extensions/codex/src/app-server/session-retirement.ts#L45-L74","documentation":"Thrown as CodexAppServerUnsafeSubscriptionError during incognito-session retirement when neither releaseCodexAppServerLiveThread nor unsubscribeCodexThreadBestEffort could release the native Codex thread subscription. Incognito/ephemeral threads bypass idle eviction, so they must be explicitly unsubscribed at session end; failing both release paths means a native subscription could outlive its owning OpenClaw session, which the code refuses to allow silently.","triggerScenarios":"Retiring or resetting an incognito session whose binding holds a live threadId; the Codex app-server is unreachable, the client lease's physical client is closed, or the unsubscribe RPC times out (CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS) and releaseCodexAppServerLiveThread also reports not released.","commonSituations":"Codex app-server process crashed or was restarted between turn and retirement; network proxy interruption to the app-server socket; aggressive session teardown while a native turn is still settling; corrupted client lease whose underlying socket is dead but the lease object is still retained.","solutions":["Check whether the Codex app-server process backing binding.clientId is alive and reachable before retirement.","Retry retirement after re-establishing the shared client (retainSharedCodexAppServerClientByInstanceId returns null once the physical client is gone, which short-circuits safely).","Increase CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS if the app-server is healthy but slow to acknowledge unsubscribe.","If the client is irrecoverable, close it via closeCodexStartupClientBestEffort (already attempted before the throw) and let the next session start fresh."],"exampleFix":"// before: retire while app-server is down\nawait retireCodexAppServerSessionGeneration({ bindingStore, identity, mode: \"retire\" }); // throws CodexAppServerUnsafeSubscriptionError\n\n// after: confirm client health, then retire\nconst lease = retainSharedCodexAppServerClientByInstanceId(binding.clientId);\nif (!lease) return; // physical client already gone, nothing to release\ntry { await retireCodexAppServerSessionGeneration({ bindingStore, identity, mode: \"retire\" }); }\nfinally { lease.release(); }","handlingStrategy":"try-catch","validationCode":"import { retainSharedCodexAppServerClientByInstanceId } from \"./shared-client.js\";\n\nasync function canReleaseIncognitoThread(clientId: string | undefined, threadId: string): Promise<boolean> {\n  if (!clientId) return true; // nothing to release\n  const lease = retainSharedCodexAppServerClientByInstanceId(clientId);\n  if (!lease) return true; // physical client gone, retirement will short-circuit\n  try {\n    return await isCodexAppServerThreadLive(lease.client, threadId);\n  } finally {\n    lease.release();\n  }\n}","typeGuard":"import { isIncognitoSessionKey } from \"../incognito-session.js\";\n\nfunction isIncognitoRetirementAtRisk(identity: { sessionKey?: string }, binding?: { threadId?: string; clientId?: string }): boolean {\n  return Boolean(identity.sessionKey && isIncognitoSessionKey(identity.sessionKey) && binding?.threadId && binding?.clientId);\n}","tryCatchPattern":"import { CodexAppServerUnsafeSubscriptionError } from \"./attempt-client-cleanup.js\";\n\ntry {\n  await retireCodexAppServerSessionGeneration({ bindingStore, identity, mode: \"retire\" });\n} catch (err) {\n  if (err instanceof CodexAppServerUnsafeSubscriptionError) {\n    // Log threadId, surface to operator; the client was already closed best-effort.\n    // Do not retry unchanged; re-establish the physical client first.\n    logger.error({ threadId: err.message }, \"incognito subscription release failed\");\n    throw err;\n  }\n  throw err;\n}","preventionTips":["Confirm the Codex app-server process is reachable before retiring incognito sessions.","Keep CODEX_APP_SERVER_UNSUBSCRIBE_TIMEOUT_MS sized to the slowest healthy app-server ack.","Treat CodexAppServerUnsafeSubscriptionError as fatal-for-the-session, not as a transient retry; re-establish the client first."],"tags":["codex","session","retirement","incognito","subscription","network"],"backgroundTag":null,"analyzedSha":"01804a75319da4b69c9ab98ceaa30477e22b8c0b","analyzedAt":"2026-08-12T04:37:58.197Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}