{"record":{"id":"2b5a26b919c72e2a","repo":"Yeachan-Heo/oh-my-codex","slug":"session-pointer-unusable","errorCode":"session_pointer_unusable","errorMessage":"Selected session pointer is ${pointer.status} and is preserved.","messagePattern":"Selected session pointer is (.+?) and is preserved\\.","errorType":"exception","errorClass":"SessionPointerLaunchAbort","httpStatus":null,"severity":"error","filePath":"src/hooks/session.ts","lineNumber":3047,"sourceCode":"    let pointerBeforeTransaction: SessionPointerReadResult;\n    try {\n      pointerBeforeTransaction = await readSessionPointer(binding.context);\n    } catch (error) {\n      throw resolvedAbort(binding.context, {\n        code: 'session_pointer_io_failure', operation: 'pointer-read', candidateSessionId: binding.canonicalSessionId,\n        lockPath: binding.context.lockPath, reason: `Unable to read selected session pointer: ${errorMessage(error)}`, cause: error,\n      });\n    }\n    // Read against an ALREADY-authorized binding: isAuthorizedBoundPointer verifies the pointer\n    // belongs to this binding (canonical session id + launch lineage token), which is a stricter\n    // check than liveness. Admitting an unobservable-live pointer here only stops a running session\n    // on a platform without process-birth evidence from reading as absent; it grants no new\n    // authority, because the binding authorization still has to match.\n    if ((pointerBeforeTransaction.status !== 'usable'\n      && !(pointerBeforeTransaction.status === 'identity-indeterminate'\n        && isUnobservableLivePointerState(pointerBeforeTransaction.state)))\n      || !isAuthorizedBoundPointer(binding, binding.context, binding.canonicalSessionId, pointerBeforeTransaction.state)) {\n      throw unusablePointerAbort(binding.context, binding.canonicalSessionId, pointerBeforeTransaction);\n    }\n    const authorization = await authorizeBoundDirectoryBeforeTransaction(\n      binding,\n      pointerBeforeTransaction,\n      binding.context.cwd,\n    );\n    capability.push(...authorization.capability);\n\n    const result = await writePointerTransaction(\n      binding.context.cwd,\n      binding.canonicalSessionId,\n      { context: binding.context },\n      DEFAULT_POINTER_TIMEOUT_MS,\n      async (pointer, context) => {\n        const state = pointer.status === 'usable'\n          || (pointer.status === 'identity-indeterminate' && isUnobservableLivePointerState(pointer.state))\n          ? pointer.state\n          : undefined;","sourceCodeStart":3029,"sourceCodeEnd":3065,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/hooks/session.ts#L3029-L3065","documentation":"Thrown when, for an already-authorized binding, the freshly read pointer is either not 'usable' (and not an unobservable-live 'identity-indeterminate') or fails isAuthorizedBoundPointer (the pointer must belong to this binding: canonical session id + launch lineage token match). The existing pointer is deliberately preserved and the transaction aborts via unusablePointerAbort with code session_pointer_unusable.","triggerScenarios":"Resuming a session whose pointer state on disk belongs to a different session ID or lineage (e.g. the pointer was replaced by another concurrent session), or the pointer is stale-dead/malformed while the caller expected a live authorized one.","commonSituations":"Two processes resuming the 'same' session from different launch lineages (tmux respawn vs new attach); the state dir was reinitialized while an old binding was reused; a session ID typo causing an ID mismatch with the stored pointer.","solutions":["Start a fresh session instead of resuming, or re-derive the binding from the current pointer state.","Verify you are passing the exact session ID returned by the begin/create call for this lineage.","Inspect the pointer file to see which session_id/lineage token it records and reconcile.","Ensure only one process mutates a given session's pointer at a time."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const stored = JSON.parse(await fsp.readFile(pointerPath, 'utf8'));\nif (normalize(stored.session_id) !== normalize(binding.canonicalSessionId)) throw new Error('binding does not own pointer');","typeGuard":"const ownsPointer = (b: Binding, s: SessionState): boolean =>\n  normalizeSessionId(s.session_id) === b.canonicalSessionId;","tryCatchPattern":"try { await op(binding); } catch (e) { if (errCode(e) === 'session_pointer_unusable') return beginFreshSession(); throw e; }","preventionTips":["Always pass the session ID returned by begin/create — no retyping.","One process per session pointer at a time.","Re-create bindings after the underlying session restarts."],"tags":["session","authorization","pointer","lineage","stale"],"backgroundTag":"session-state-mismatch","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}