Skip to content

Codex restore leaves ocx1-compacted threads unreplayable on the native backend #3916

Closed

Description

@cb8010d6

Client or integration

Codex App

Area

Service lifecycle

Summary

After OpenCodeX has produced a routed remote-compaction V2 item, ocx restore or the dashboard's Codex OFF switch can successfully return Codex to its native ChatGPT path while leaving the persisted thread incompatible with that path. Resuming the affected thread directly sends OpenCodeX's ocx1: envelope to ChatGPT as encrypted_content; ChatGPT rejects it with invalid_encrypted_content. Re-enabling OpenCodeX makes the same thread usable because the proxy lowers ocx1: to plain summary text.

Expected behavior: returning to the native path should either provide a safe, explicit migration for an affected thread or clearly identify the required recovery step. It should not report a complete native restore while leaving previously routed threads with no supported recovery path.

Reproduction

  1. Route Codex through OpenCodeX to a non-native Responses provider.
  2. Continue a thread until remote compaction V2 stores a compacted.payload.replacement_history entry whose compaction encrypted_content begins with ocx1:.
  3. Run ocx restore or disable the Codex integration in the dashboard. Leave the proxy process running; only the Codex route changes.
  4. Resume that same thread through the native ChatGPT backend and send any message.
  5. The request fails before model execution with HTTP 400 invalid_encrypted_content for the persisted cmp_... item.
  6. Run ocx restore back and retry through OpenCodeX. The proxy decodes the same ocx1: envelope and the thread can continue.

The failure is deterministic for a thread containing that persisted envelope. Fresh threads and routed threads that never compacted are unaffected.

Version

Observed with 2.43.0. The incompatible restore path is still present on dev commit f802f71122 (package version 2.47.0).

Operating system

Windows 11

Provider and model

Routed OpenAI Responses-compatible provider, followed by native ChatGPT Codex. The persisted format, rather than a specific routed model, determines the failure.

Logs or error output

HTTP 400 invalid_request_error / invalid_encrypted_content
The encrypted content for item cmp_<redacted> could not be verified.
Reason: Encrypted content could not be decrypted or parsed.

Persisted item shape:
compacted.payload.replacement_history[].encrypted_content = "ocx1:<base64 summary>"

Screenshots and supporting files

Related upstream reports describe the same native replay failure and explicitly identify ocx1: as OpenCodeX-owned state:

OpenCodeX currently decodes the envelope only while its Responses adapter is in the request path (src/responses/compaction.ts and src/adapters/openai-responses.ts). The restore lifecycle changes provider metadata and configuration but does not migrate the persisted compaction snapshot.

Redacted configuration

{
  "providers": {
    "example": {
      "adapter": "openai-responses",
      "baseUrl": "https://example.invalid/v1"
    }
  }
}

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    bugSomething isn't working
    serviceService lifecycle (WinSW/launchd/scheduler)
    on Sep 7, 2026
  2. lidge-jun commented on Sep 7, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 55 / 80

    이 이슈는 Codex를 OpenCodeX로 라우팅한 뒤 원격 컴팩션 V2가 만든 ocx1: 요약을, ocx restore(또는 대시보드 Codex OFF)로 네이티브 ChatGPT 경로로 되돌린 다음 같은 스레드를 이어갈 때 터지는 복원 구멍이다. 재현 단계는 분명하다. 라우티드 Responses 경로에서 컴팩션이 돌면 src/responses/compaction.ts가 요약을 ocx1: + base64로 감싸고, Codex 클라이언트가 그 encrypted_content를 스레드에 그대로 저장한다. 프록시가 앞에 있을 때는 src/adapters/openai-responses.ts / compaction 디코더가 봉투를 풀어 평문 요약으로 다시 넣으니 스레드가 산다. 그런데 src/integrations/writer.ts의 restoreIntegration과 src/cli/dispatch.ts restore 경로는 Codex 설정·통합 메타만 네이티브로 되돌리고, 이미 디스크에 남은 cmp_… / replacement_history 안의 ocx1: 블롭은 손대지 않는다. 그 상태에서 네이티브 ChatGPT가 같은 블롭을 자기 암호문으로 검증하려다 invalid_encrypted_content 400을 낸다. ocx restore back으로 프록시를 다시 끼우면 디코드가 돌아와 같은 스레드가 다시 된다. 보고자는 2.43.0에서 봤고, dev f802f7112(패키지 2.47.0)에서도 복원 경로가 스냅샷을 이주하지 않는다고 짚었다. 지금 HEAD(273a3ab86)도 같은 구조다. 릴리즈 트레인 readiness가 열어 둔 슬라이스(#3719 라이브 리플레이/캐시, #3782 클라이언트 소유 등)와는 다른 축이지만, “restore 성공”이 사용자에게 네이티브로 안전하게 돌아갔다고 읽히는 UX와 어긋난다. 신선한 스레드·컴팩션 없는 라우티드 스레드는 괜찮고, ocx1:가 들어간 스레드만 결정적으로 죽는다.

    기대 동작은 두 갈래다. restore 때 영향 스레드를 찾아 ocx1:를 평문 요약(또는 네이티브가 받는 형태)으로 바꾸거나, 바꾸지 않을 거면 restore가 “완료”라고만 말하지 말고 복구 절차·영향 범위를 분명히 보여 주는 것이다. 지금 코드 주석도 compaction이 “라우티드 전용 투명 봉투”라고 이미 적혀 있어서, 네이티브 복원과 충돌하는 설계 경계가 숨겨져 있지 않다. 다만 그 경계가 restore 성공 메시지까지는 연결되어 있지 않다.

    src/responses/compaction.ts OCX_COMPACTION_PREFIX / encodeCompactionSummary / decodeCompactionSummary - 라우티드 컴팩션이 ocx1: 봉투를 만드는 지점. 네이티브 백엔드는 이 접두사를 해독하지 못한다.
    src/adapters/openai-responses.ts compaction replay 경로 - 프록시가 앞에 있을 때만 봉투를 평문으로 낮춘다. restore 후에는 이 코드가 요청 경로에 없다.
    src/integrations/writer.ts restoreIntegration / restoreIntegrationCoordinated - Codex 통합 OFF·네이티브 설정 복원만 하고 persisted compaction snapshot 이주는 없다.
    src/cli/dispatch.ts ocx restore / restore back - 성공 문구가 “native”로 읽히지만 스레드 호환성 경고가 없다.
    이슈 본문 upstream Codex #36704 / #25290 - 같은 네이티브 재플레이 실패와 ocx1: 소유를 가리킨다. OpenCodeX 쪽 수정 범위와 클라이언트 쪽 수정 범위를 나눌 때 참고.

    메인테이너의 판단이 필요한 지점

    • restore 시 자동 이주(디스크의 ocx1: → 평문/대체 히스토리)를 넣을지, 경고+수동 복구만 할지
    • 영향 스레드를 Codex home에서 어떻게 탐지할지(파일 포맷·플랫폼 차이, Windows 재현)
    • “완료된 restore” UX를 깨지 않으려면 실패/경고 exit code를 쓸지, doctor 서브커맨드로 분리할지
    • Bug: preserve Anthropic thinking replay through proxy-auth translation and clarify prompt-cache behavior #3719(Anthropic replay/cache) 등 열린 컴팩션/리플레이 작업과 한 열차에 묶을지, restore 전용 작은 PR로 먼저 막을지

    너의 추천
    버그로 유지하고 help wanted에 가깝게 범위를 좁힌다. 최소 수정은 restore 성공 전에 ocx1: compaction이 남아 있으면 경고하고 ocx restore back 또는 스레드 폐기/새 스레드 안내를 출력하는 것. 가능하면 restore 경로에서 해당 항목을 decodeCompactionSummary로 풀어 평문 요약 아이템으로 바꾸는 이주 헬퍼를 compaction.ts 옆에 두고 테스트로 잠근다. 라우팅 본체를 건드리지 않는 복원-UX 슬라이스로 따로 받는 편이 #3719와 안 섞인다.

    이 댓글은 grok-bot이 작성했습니다

  3. added a commit that references this issue on Sep 8, 2026
    9587750
  4. lidge-jun commented on Sep 8, 2026

    @lidge-jun
    Owner

    Addressed on dev in 9587750 (via #4051, carrying #3920 by @cb8010d6).

    What shipped is the recovery-step branch of your request, not an automatic migration: ocx recover-history --ocx-compaction <thread-id> --yes restores an ocx1-compacted thread for native replay, backing the file up byte-for-byte before an atomic rewrite. ocx restore and the dashboard Codex toggle now both point at it at the moment you switch back, so the required recovery step is identified rather than left for you to discover.

    Being explicit about the remaining gap: restore still reports success without enumerating affected threads, so you run the command per thread yourself. Scanning and rewriting rollouts during ocx restore would be a larger behavior change than the one it fixes, which is why it was not done here. If per-thread manual recovery is not sufficient for your case, please reopen or file a follow-up and we will look at enumeration separately.

    Closing manually because pull requests here target dev, and GitHub only auto-closes on merge into main.

  5. lidge-jun commented on Sep 8, 2026

    @lidge-jun
    Owner

    Landed via #4051 at 9587750

  6. added a commit that references this issue on Sep 17, 2026
    c28998d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinglanded-via-maintainerOriginal PR closed after landing via a maintainer merge trainserviceService lifecycle (WinSW/launchd/scheduler)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions