Yeachan-Heo/oh-my-codex · error · SessionPointerLaunchAbort

session_pointer_unusable

session_pointer_unusable

Error message

Selected session pointer is ${pointer.status} and is preserved.

What it means

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.

Source

Thrown at src/hooks/session.ts:3047

    let pointerBeforeTransaction: SessionPointerReadResult;
    try {
      pointerBeforeTransaction = await readSessionPointer(binding.context);
    } catch (error) {
      throw resolvedAbort(binding.context, {
        code: 'session_pointer_io_failure', operation: 'pointer-read', candidateSessionId: binding.canonicalSessionId,
        lockPath: binding.context.lockPath, reason: `Unable to read selected session pointer: ${errorMessage(error)}`, cause: error,
      });
    }
    // Read against an ALREADY-authorized binding: isAuthorizedBoundPointer verifies the pointer
    // belongs to this binding (canonical session id + launch lineage token), which is a stricter
    // check than liveness. Admitting an unobservable-live pointer here only stops a running session
    // on a platform without process-birth evidence from reading as absent; it grants no new
    // authority, because the binding authorization still has to match.
    if ((pointerBeforeTransaction.status !== 'usable'
      && !(pointerBeforeTransaction.status === 'identity-indeterminate'
        && isUnobservableLivePointerState(pointerBeforeTransaction.state)))
      || !isAuthorizedBoundPointer(binding, binding.context, binding.canonicalSessionId, pointerBeforeTransaction.state)) {
      throw unusablePointerAbort(binding.context, binding.canonicalSessionId, pointerBeforeTransaction);
    }
    const authorization = await authorizeBoundDirectoryBeforeTransaction(
      binding,
      pointerBeforeTransaction,
      binding.context.cwd,
    );
    capability.push(...authorization.capability);

    const result = await writePointerTransaction(
      binding.context.cwd,
      binding.canonicalSessionId,
      { context: binding.context },
      DEFAULT_POINTER_TIMEOUT_MS,
      async (pointer, context) => {
        const state = pointer.status === 'usable'
          || (pointer.status === 'identity-indeterminate' && isUnobservableLivePointerState(pointer.state))
          ? pointer.state
          : undefined;

View on GitHub (pinned to 3ad79a8a6f)

Solutions

  1. Start a fresh session instead of resuming, or re-derive the binding from the current pointer state.
  2. Verify you are passing the exact session ID returned by the begin/create call for this lineage.
  3. Inspect the pointer file to see which session_id/lineage token it records and reconcile.
  4. Ensure only one process mutates a given session's pointer at a time.
Defensive patterns

Strategy: try-catch

Validate before calling

const stored = JSON.parse(await fsp.readFile(pointerPath, 'utf8'));
if (normalize(stored.session_id) !== normalize(binding.canonicalSessionId)) throw new Error('binding does not own pointer');

Type guard

const ownsPointer = (b: Binding, s: SessionState): boolean =>
  normalizeSessionId(s.session_id) === b.canonicalSessionId;

Try / catch

try { await op(binding); } catch (e) { if (errCode(e) === 'session_pointer_unusable') return beginFreshSession(); throw e; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Yeachan-Heo/oh-my-codex@3ad79a8a6f (2026-08-27). Data as JSON: /api/errors/2b5a26b919c72e2a. Report an issue: GitHub.