{"record":{"id":"4335c2d6135b01b6","repo":"openclaw/openclaw","slug":"codex-session-is-active-in-this-app-server-wait-f","errorCode":null,"errorMessage":"Codex session is active in this App Server; wait for it to finish before ${action === \"continue\" ? \"starting a branch\" : \"archiving\"}","messagePattern":"Codex session is active in this App Server; wait for it to finish before (.+?)","errorType":"validation","errorClass":"CatalogParamsError","httpStatus":null,"severity":"warning","filePath":"extensions/codex/src/session-catalog.ts","lineNumber":832,"sourceCode":"  return {\n    hostId: params.hostId,\n    label: nodeLabel(node),\n    threadId: params.threadId,\n    items: flattenTranscriptPageDesc(page),\n    ...(page.nextCursor ? { nextCursor: page.nextCursor } : {}),\n    ...(page.backwardsCursor ? { backwardsCursor: page.backwardsCursor } : {}),\n  };\n}\n\nfunction requireIdleThread(thread: CodexThread, action: \"continue\" | \"archive\"): void {\n  if (\n    thread.status?.type === \"idle\" ||\n    (action === \"archive\" && thread.status?.type === \"notLoaded\")\n  ) {\n    return;\n  }\n  if (thread.status?.type === \"active\") {\n    throw new CatalogParamsError(\n      `Codex session is active in this App Server; wait for it to finish before ${action === \"continue\" ? \"starting a branch\" : \"archiving\"}`,\n    );\n  }\n  throw new CatalogParamsError(\n    action === \"archive\"\n      ? \"Codex session cannot be archived in its current state\"\n      : \"Codex session cannot start a branch in its current state\",\n  );\n}\n\nfunction adoptionSessionKey(threadId: string): string {\n  const digest = createHash(\"sha256\").update(threadId).digest(\"hex\");\n  return `${CODEX_SUPERVISION_SESSION_KEY_PREFIX}${digest}`;\n}\n\nfunction isAdoptionSessionKeyForThread(sessionKey: string, threadId: string): boolean {\n  return adoptionSessionKeyRest(sessionKey) === adoptionSessionKey(threadId);\n}","sourceCodeStart":814,"sourceCodeEnd":850,"githubUrl":"https://github.com/openclaw/openclaw/blob/01804a75319da4b69c9ab98ceaa30477e22b8c0b/extensions/codex/src/session-catalog.ts#L814-L850","documentation":"Thrown by requireIdleThread when a thread's status.type is 'active' and the caller is trying to either continue (start a branch) or archive it. The message is parameterized: 'starting a branch' for continue, 'archiving' for archive. This is a transient, expected guard — the thread is genuinely busy and the operation must wait for the run to settle.","triggerScenarios":"requireIdleThread(thread, action) is called with thread.status.type === 'active' (a harness run is in progress on that App Server). Reached from continueLocalCodexSessionInner (continue) or archiveLocalCodexSession (archive) after a fresh status read.","commonSituations":"User clicks Continue/Archive in the UI while a Codex run is mid-execution; a previous run has not reported terminal status; UI status cache is stale so the action was offered prematurely; long-running turn that has not flushed idle status.","solutions":["Wait for the active run to finish, then re-read thread status (control.readThread) and retry the continue/archive.","Surface a 'session busy, retry shortly' message in the UI and poll status until it returns idle.","Ensure the UI only offers canContinue/canArchive when the latest status read is idle/notLoaded (toGenericCatalogHost already gates these on continuableStatus).","Avoid issuing continue/archive from automation without first confirming idle status."],"exampleFix":"// before: acting on possibly-stale status\nrequireIdleThread(thread, 'archive');\n// after: re-read then act\nconst fresh = await control.readThread(threadId, false);\nif (fresh.status?.type === 'active') throw new Error('busy, retry');\nrequireIdleThread(fresh, 'archive');","handlingStrategy":"retry","validationCode":"const fresh = await control.readThread(threadId, false);\nif (fresh.status?.type === 'active') {\n  // wait and retry, or surface 'busy' to the user\n}","typeGuard":"function isIdleFor(t: { status?: { type?: string } }, action: 'continue' | 'archive'): boolean {\n  return t.status?.type === 'idle' || (action === 'archive' && t.status?.type === 'notLoaded');\n}","tryCatchPattern":"try {\n  requireIdleThread(thread, 'archive');\n} catch (e) {\n  if (e instanceof CatalogParamsError && /active in this App Server/.test(e.message)) {\n    await waitForIdle(threadId); await retry();\n  }\n  throw e;\n}","preventionTips":["Re-read thread status immediately before continue/archive.","Only offer canContinue/canArchive when status is idle/notLoaded.","Poll status after a busy result rather than retrying blindly."],"tags":["codex","thread-state","transient","busy","continue","archive"],"backgroundTag":null,"analyzedSha":"01804a75319da4b69c9ab98ceaa30477e22b8c0b","analyzedAt":"2026-08-12T04:37:58.197Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}