paperclipai/paperclip · error

persisted Codex ACPX resultless recovery requires a complete

Error message

persisted Codex ACPX resultless recovery requires a completed terminal turn

What it means

For a resultless recovery (a Codex ACPX session recovered without a durably settled result), the driver requires that the settlement turn is the session's latest terminal turn and that its fingerprint marks a completed terminal state. This prevents resuming from an incomplete or superseded turn, which could duplicate work or lose results.

Source

Thrown at packages/paperclip-runner/src/drivers/acpx/codex-acpx-driver.ts:2002

        )
      ) {
        throw new Error(
          "persisted Codex ACPX active turn is not the completed semantic settlement",
        );
      }
    }
  } else if (terminalTurns.length > 0) {
    const latestTerminalTurnId = terminalTurns.at(-1)!.turnId;
    const settlementTurnId = snapshot.activeTurnId ?? latestTerminalTurnId;
    const settlement = terminalTurns.find(
      (terminal) => terminal.turnId === settlementTurnId,
    );
    if (
      settlementTurnId !== latestTerminalTurnId ||
      !settlement ||
      !isCompletedTerminal(settlement.fingerprint)
    ) {
      throw new Error(
        "persisted Codex ACPX resultless recovery requires a completed terminal turn",
      );
    }
  }
}

function validProviderLifetimeFenceCandidates(
  value: unknown,
): value is readonly [number, number, number] {
  return (
    Array.isArray(value) &&
    value.length === 3 &&
    value.every(
      (port) => Number.isSafeInteger(port) && port >= 49_152 && port <= 65_535,
    ) &&
    new Set(value).size === 3
  );
}

View on GitHub (pinned to 01ad858492)

Solutions

  1. Discard the stale snapshot and start a new session/turn rather than recovering an incomplete one.
  2. Ensure the turn reaches a completed terminal state and its result is settled before the process can be recovered.
  3. Fix persistence flushing so settlement records are written atomically with terminal turn state.
  4. If a newer turn exists, re-run recovery logic against the latest terminal turn instead of the old settlement.

Example fix

// before
recoverResultless(snapshot); // settlementTurnId points at superseded turn 7, latest is 9
// after
if (settlementTurnId === latestTerminalTurnId && isCompletedTerminal(settlement.fingerprint)) {
  recoverResultless(snapshot);
} else {
  await driver.openSession(); // fresh start
}
Defensive patterns

Strategy: try-catch

Validate before calling

function canRecoverResultless(snapshot) {
  return snapshot.settlementTurnId === snapshot.latestTerminalTurnId &&
    snapshot.settlement != null && isCompletedTerminal(snapshot.settlement.fingerprint);
}
if (!canRecoverResultless(snapshot)) planFreshSession = true;

Type guard

function hasCompletedSettlement(s) {
  return s.settlement != null && isCompletedTerminal(s.settlement.fingerprint) &&
    s.settlementTurnId === s.latestTerminalTurnId;
}

Try / catch

try {
  recoverResultless(snapshot);
} catch (err) {
  if (err.message.includes("completed terminal turn")) {
    await driver.openSession(); // cannot safely resume
  } else throw err;
}

Prevention

When it happens

Trigger: Recovering a resultless session where settlementTurnId !== latestTerminalTurnId, the settlement record is missing, or the settlement fingerprint is not a completed terminal (e.g. aborted/failed/in-progress terminal).

Common situations: Crash mid-turn leaving only a non-completed terminal turn; a later turn superseding the settled one before recovery; persistence truncated so the settlement record is absent while turn IDs exist.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/a17c70e1694d0177. Report an issue: GitHub.