Skip to content

fix(codex): bound aggregate rollout inspection during startup - #5703

Closed
konstantinosbotonakis wants to merge 1 commit into
lidge-jun:devfrom
konstantinosbotonakis:fix/bounded-native-history-inspection
Closed

konstantinosbotonakis wants to merge 1 commit into
lidge-jun:devfrom
konstantinosbotonakis:fix/bounded-native-history-inspection

Conversation

@konstantinosbotonakis

@konstantinosbotonakis konstantinosbotonakis commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Bound native Codex residue inspection during startup to 64 MiB of rollout content per observation. ocx ensure can invoke this path before starting Codex; the existing per-file limit still allowed it to parse gigabytes across many files.

  • Share one decreasing budget across the history database and backup references, resetting it on each call.
  • Return indeterminate before reading a file that would exceed the budget. Keep existing refusal and legacy compatibility behavior; never treat an incomplete scan as clean.
  • Stop after the first indeterminate surface. Preserve first-residue ordering when all remaining surfaces can be classified.
  • Add regressions for aggregate exhaustion, budget reset, and sharing the budget across history and backup scans. Document the limit in structure/config.md.

On the same live macOS history, the read-only preflight improved from 5.732–6.397 s / 872–885 MiB peak RSS to 0.934 s / 232 MiB after applying the patch to 2.64.0. These are local observations, not a controlled benchmark or proof that every machine freeze is fixed.

Large histories that exceed the new total budget now remain indeterminate instead of completing an unbounded inspection. History is not deleted or rewritten. Provider, authentication, and routing settings are unchanged.

Closes #5701.

Verification

  • bun test tests/codex-integration/codex-native-residue.test.ts: 87 passed, 0 failed.
  • bun run typecheck: passed.
  • bun run privacy:scan: passed.
  • bun run structure:check: passed.
  • bun run test:changed --parallel=2: intentionally stopped after more than six minutes when the two test workers exceeded 10 GiB combined resident memory. Incomplete, not a passing result. Focused regression coverage was used under the repository's resource-cost exception; broader coverage remains for maintainer-triggered CI.
  • A fresh read-only preflight against the locally patched installation succeeded in 934 ms with 237,936 KiB maximum RSS.

The full cross-platform suite and repository CI have not been run. This is a draft contributor PR; no merge or release is requested by this operation.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Review readiness checklist

This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:

  • Required local validation passed; commands, results, and any full-suite exception are documented.

  • I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).

  • I resolved all correct Codex and CodeRabbit findings.

  • My PR is ready for review.

@coderabbitai

coderabbitai Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Sep 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • review readiness checklist open (0/4 boxes ticked).

What to do

  • Tick all four boxes in the PR description once you're done (currently 0/4).

Review readiness checklist

  • ⬜ Required local validation passed; commands, results, and any full-suite exception are documented.
  • ⬜ I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).
  • ⬜ I resolved all correct Codex and CodeRabbit findings.
  • ⬜ My PR is ready for review.

0/4 boxes ticked.

This PR stays in draft until every box above is ticked.

@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 66 / 80

이 PR은 Codex를 켜기 전에 하는 기록 검사를, 한 번 호출할 때 롤아웃 내용 64 MiB까지만 읽게 막습니다. ocx ensure가 이 검사를 부릅니다. 지금은 파일 하나당 64 MiB만 막습니다. 파일이 많으면 그 합은 제한이 없습니다. #5701의 맥 기록에서는 읽기 전 확인이 5.7초에서 6.4초, 메모리가 872 MiB에서 885 MiB였습니다. 그때 판정은 이미 indeterminate였습니다. 뒤쪽 파일 하나가 파일당 한도를 넘었기 때문입니다.

검사 결과는 세 가지입니다. clean이면 코디네이터를 만들 수 있습니다. residue이면 이미 라우팅된 홈을 입양(adopt)합니다. indeterminate이면 옛 경로(legacy-uncoordinated)로 쓰고, 코디네이터 행은 만들지 않습니다. 쓰기는 막지 않습니다.

이번 패치는 히스토리 데이터베이스와 백업 목록이 예산 하나를 같이 쓰게 합니다. 호출할 때마다 64 MiB로 다시 채웁니다. 다음 파일이 남은 예산을 넘으면 그 파일은 읽지 않고 indeterminate를 돌려줍니다. 끝까지 못 본 기록을 clean으로 두지 않습니다. 첫 indeterminate에서 뒤 검사는 멈춥니다. residue는 기억했다가, 끝까지 indeterminate가 없으면 처음 residue를 돌려줍니다. 베이스는 dev입니다. 아직 드래프트이고 준비 체크는 0/4입니다. #5701을 가리키는 다른 열린 PR은 없습니다.

src/codex/native-residue.ts:465 - 파일을 읽기 전에 파일 크기 전체를 예산에서 뺍니다. 가운데서 residue나 깨진 줄을 만나 멈춰도, 안 읽은 뒷부분은 이미 쓴 예산입니다. 그다음 백업 검사가 남은 예산으로 돌아갑니다.

src/codex/native-residue.ts:703 - PR 본문은 뒤 표면을 다 분류할 수 있을 때만 처음 residue를 유지한다고 적습니다. 이 줄이 그 코드입니다. indeterminate가 나오면 그 자리에서 끝이고, 앞에서 기억한 residue는 버려집니다. 히스토리에서 residue를 본 뒤 백업이 예산에 걸리면 결과는 indeterminate입니다. 입양 대신 옛 경로로 갑니다.

src/codex/native-residue.ts:590 - 히스토리는 model_provider가 opencodex인 줄의 롤아웃을 읽지 않습니다. 그 파일은 백업 목록에서 다시 크기를 뺍니다. 경로가 같아도 두 번 셉니다. 테스트 1037행이 이 동작을 고정합니다. 파일을 한 번만 세면 64 MiB 안이어도, 히스토리와 백업이 각각 빼면 indeterminate가 됩니다.

src/codex/native-residue.ts:86 - 총량 한도가 파일당 한도와 같은 64 MiB입니다. 파일 하나가 한도를 꽉 채우면, 다음 파일은 크기가 1바이트여도 총량에서 걸립니다.

tests/codex-integration/codex-native-residue.test.ts:1007 - 새 테스트는 세 개입니다. 네이티브 파일 두 개가 총량을 넘는 경우, 예산이 호출마다 다시 채워지는 경우, 같은 파일을 히스토리와 백업이 같이 세는 경우입니다. 먼저 residue를 찾고 나중 파일이 예산을 넘는 경우, opencodex 줄만 있는 큰 백업이 residue 대신 indeterminate가 되는 경우는 없습니다.

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

64 MiB가 기가바이트 읽기를 막는 상한인지, 입양과 coordinated를 포기하는 상한인지 정해야 합니다. 총량을 파일당 한도와 같게 두면, 긴 대화가 두 개만 되어도 매번 옛 경로로 갑니다. 예산은 실제 읽은 바이트만 뺄지, 같은 경로는 한 번만 뺄지, 이미 본 residue를 나중 indeterminate가 지워도 되는지도 정해야 합니다. 준비 체크는 0/4입니다. bun run test:changed는 6분이 넘고 테스트 워커 메모리가 합쳐 10 GiB를 넘어, 작성자가 멈춘 미완료입니다. 포커스 테스트 87개, typecheck, privacy:scan, structure:check는 작성자 기록상 통과입니다. 저장소 CI는 이 헤드에서 돌지 않았습니다.

너의 추천

총량 상한은 두세요. 못 본 기록은 clean으로 두지 마세요. 상한은 64 MiB보다 크게, 이번 맥 관측 872 MiB보다는 작게 두는 편이 이 버그에 맞습니다. 465행은 실제로 읽은 만큼만 빼거나, 이미 센 경로는 다시 빼지 마세요. 703행에서 앞 residue를 지울지는 입양을 남길지와 같이 정하세요. residue를 남기면 그 경우를 1007행 옆에 테스트로 두세요. opencodex 줄만 있고 백업이 총량을 넘는 경우도 같이 두세요. 드래프트와 준비 체크는 그 다음에 채우면 됩니다. 닫을 중복 PR은 없습니다.

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

lidge-jun added a commit that referenced this pull request Sep 24, 2026
The native-residue classifier limited each referenced rollout to 64 MiB but
not the total, so the read-only startup preflight could parse gigabytes of
conversation history on every Codex launch (#5701). One observation now reads
at most 64 MiB of rollout content across the history database and backup
manifest together; a file that would exceed the remaining budget is reported
indeterminate before it is read, and classification stops at the first
indeterminate surface. Exhausting the budget never reports clean.

Carries #5703.
Closes #5701.

Co-authored-by: Konstantinos <37538071+konstantinosbotonakis@users.noreply.github.com>
lidge-jun added a commit that referenced this pull request Sep 24, 2026
The native-residue classifier limited each referenced rollout to 64 MiB but
not the total, so the read-only startup preflight could parse gigabytes of
conversation history on every Codex launch (#5701). One observation now reads
at most 64 MiB of rollout content across the history database and backup
manifest together; a file that would exceed the remaining budget is reported
indeterminate before it is read, and classification stops at the first
indeterminate surface. Exhausting the budget never reports clean.

Carries #5703.
Closes #5701.

Co-authored-by: Konstantinos <37538071+konstantinosbotonakis@users.noreply.github.com>
lidge-jun added a commit that referenced this pull request Sep 24, 2026
…, sub-agent identity, agent-message recovery, 98% main lock (#5743)

* docs(devlog): plan lane L4 Codex integration, CLI and service bundle

* fix(service): preserve connected client key on uninstall (#5713)

ocx service uninstall unconditionally deleted service-api-token, which on a
remote-hub client is the per-client data-plane key issued by the hub (#5699).
Uninstall now deletes the token only under the client lifecycle and config
mutation locks, and only when persisted client state is disconnected and no
pending connect marker owns the current key's fingerprint. Connect publishes
that fingerprint marker before writing the key and clears it on commit or
rollback. Lock, state-read and unlink failures report cleanup as unverified.

On top of #5713: rollback checks the pending marker against its own
fingerprint, so a valid marker for a different key is left alone instead of
adding a spurious rollback failure.

Carries #5713.
Closes #5699.

Co-authored-by: 정우철 <oocheol@naver.com>

* fix(codex): bound aggregate rollout inspection during startup (#5703)

The native-residue classifier limited each referenced rollout to 64 MiB but
not the total, so the read-only startup preflight could parse gigabytes of
conversation history on every Codex launch (#5701). One observation now reads
at most 64 MiB of rollout content across the history database and backup
manifest together; a file that would exceed the remaining budget is reported
indeterminate before it is read, and classification stops at the first
indeterminate surface. Exhausting the budget never reports clean.

Carries #5703.
Closes #5701.

Co-authored-by: Konstantinos <37538071+konstantinosbotonakis@users.noreply.github.com>

* fix(codex-home): import path expansion without the config facade (#5548)

src/codex/home.ts imported expandUserPath from the ../config barrel, which
re-enters the config facade before the Codex home resolver has initialized.
On a fresh WSL process that import cycle could fail before the home was
resolved. The resolver now imports expandUserPath from src/config/paths.ts
directly, and a fresh-process regression test loads the resolver first with
a tilde CODEX_HOME.

Only the import fix, its structure note and the fresh-process test are
carried; the PR's cli-help, service and docker expectation edits target the
documented container-only failures and stay out.

Carries #5548 (partial).

Co-authored-by: Vadevious <Vadevious@users.noreply.github.com>

* docs(remote-hub): translate the service uninstall key-retention note

Adds the #5713 uninstall paragraph to the fr, ja, ru, tr, zh-cn and zh-tw remote hub guides so no locale describes the old unconditional token deletion.

Co-authored-by: 정우철 <oocheol@naver.com>

* fix(identity): name the worker's own model when a sub-agent is spawned (#5221)

Codex stores a session's instruction block once and replays it verbatim into
a sub-agent spawned on a different model, so a worker inherited the parent's
identity sentence and answered identity questions with the parent's model id
(#5217).

The catalog no longer bakes a model id into base_instructions; it writes the
model-neutral line. The destination is named at request time: the request
parser names the neutral line or a stale routed sentence in the top-level
instructions and developer items, adapters that call identifyRoutedModel still
settle on the wire model id, and the Responses passthrough renames on a routed
destination and drops the sentence on a native/forward one. Only sentences
this proxy generated are matched.

Rebuilt on current dev. On top of #5221: the parser also covers instructions,
so adapters that build their own system text (devin, ollama-native, cursor,
qoder, codebuddy, mimo-free) keep a named identity; a forward strip that
empties instructions or a developer item removes it instead of sending it
blank; catalog cases live in a sibling test file because codex-catalog.test.ts
is at its size cap; slug-codec and configured-native-models expectations
follow the neutral catalog.

Carries #5221.
Closes #5217.

Co-authored-by: sbc1-code <207095575+sbc1-code@users.noreply.github.com>

* fix(responses): recover FOLLOWUP_TASK and FINAL_ANSWER agent messages (#5009)

Opt-in encrypted agent-task recovery handled only NEW_TASK and MESSAGE, so an
unreadable FOLLOWUP_TASK or FINAL_ANSWER item either failed without recovery
or, for an unrecognised envelope shape, measured as readable and was
forwarded as raw ciphertext. Recovery and the unreadable-ciphertext guard now
recognise all four codex-rs agent-message types, including a FINAL_ANSWER
without a Task name line.

Sender and admission checks are unchanged: credential admission still runs
before any cache access, the structured author must equal the header sender,
and the recipient is cross-checked whenever the header names one. The cache
key is a JSON-encoded fixed-order tuple that binds the structured recipient,
and a recovered assignment that echoes the other routing family is rejected.
agentTaskRecovery stays default-off.

Carries #5009.

Co-authored-by: Zhaofeng Li <lzfxxx@gmail.com>

* docs(subagents): describe four-type agent-task recovery in every locale

The localized sub-agent guides still said agentTaskRecovery loses message-type follow-ups, which contradicts the #5009 recovery of FOLLOWUP_TASK and FINAL_ANSWER.

Co-authored-by: Zhaofeng Li <lzfxxx@gmail.com>

* feat(codex): lock the main account at 98% by default (#5694)

Codex Desktop disables its send button once the ChatGPT account window reads
0% remaining, even when the user routes every request to another provider.
The main-account hard lock already stops ocx from admitting new
identity-matched main-account requests at a usage threshold, but it was
opt-in at 99%.

The lock is now on by default at 98%: an absent or true
codexMainAccountHardLock enables it, and only an explicit false opts out.
Every gate reads one resolver (isMainAccountHardLockEnabled); the settings
route persists false and deletes the key for true, like fastRows; a
malformed hand edit falls back to the default; the blocked-request message
derives its percent from the constant.

Trade-offs: while blocked, the main account's Luna Reserve cannot activate;
switching the setting off lets the main account run to exhaustion. The old
switch deleted the key when turned off, so an earlier opt-out now reads as
on and has to be switched off once more.

GUI copy (all locales), the English and Korean accounts reference, and
structure/providers/openai-tiers.md describe the new default.

Refs #5694.

* docs(devlog): record L4 publish plan

* test(gui): expect the 98% hard-lock blocked label

The main-card blocked label follows the #5694 threshold change.

* fix(identity): drop the neutral catalog line on native destinations

After #5221 the catalog stores the model-neutral identity line, so a native worker spawned from a routed parent received "Do not claim to be GPT-5 or to be made by OpenAI", contradicting Codex's own model_switch identity. The forward strip now removes the neutral line as well as a routed sentence; an instructions value that becomes empty is still removed.

Co-authored-by: sbc1-code <207095575+sbc1-code@users.noreply.github.com>

* fix(gui): use the Turkish locative -de after %98

Ninety-eight ends in a front vowel, so the hard-lock title reads %98’de.

* fix(identity): name the final wire model and system-role items

Two review findings on the #5221 carry. The request parser names the
identity sentence from the client selector, which can be an alias, a
namespaced slug or a combo name, and adapters that never call
identifyRoutedModel (devin, ollama-native, cursor, qoder, codebuddy,
coding-agent) shipped that name. The final route normalization now renames
our sentence to route.modelId, where the dispatched id is known and every
dispatch path reads the context afterwards. The parser also names
system-role input items, not only developer items.

Co-authored-by: sbc1-code <207095575+sbc1-code@users.noreply.github.com>

---------

Co-authored-by: 정우철 <oocheol@naver.com>
Co-authored-by: Konstantinos <37538071+konstantinosbotonakis@users.noreply.github.com>
Co-authored-by: Vadevious <Vadevious@users.noreply.github.com>
Co-authored-by: sbc1-code <207095575+sbc1-code@users.noreply.github.com>
Co-authored-by: Zhaofeng Li <lzfxxx@gmail.com>
@lidge-jun

Copy link
Copy Markdown
Owner

Carried into bundle #5743, which is now on dev (squash-merged as 6c171aa) with a Co-authored-by trailer for you, so this PR is closing as landed. Thank you for the fix. If something from this branch did not make it in, the bundle description lists what was changed during the carry.

@lidge-jun lidge-jun closed this Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants