Repository navigation
Hashless Codex journal can overwrite later settings and become trusted on reinjection #4005
Description
Client or integration
Other
Area
Other
Summary
A Codex journal without recorded injected-state hashes can overwrite later native configuration edits or delete a later profile. restoreJournalState() currently treats a missing hash as evidence that the current artifact is unchanged, then restores the saved original. This state can be produced by a legacy record or an interruption between writeJournal() and markJournalInjectedState().
There is a second path: reinjection over a routed, hashless journal keeps its older original but assigns the new injected configuration's hash to it. A later restore can then accept that old original as if the new configuration belonged to the old snapshot. Native-restore and reconcile callers also need to distinguish an unverified snapshot from a verified partial restore.
Refs #2948. This is a reproducible narrower defect in the current source; it does not establish the cause of that historical machine's shutdowns or justify treating every restore log line as a configuration overwrite.
Reproduction
Use an isolated Codex home with the current dev source:
- Write a native
config.toml, callwriteJournal(), and do not callmarkJournalInjectedState(). - Change the config to another native configuration, adding a different user-selected model. Calling
restoreJournalState()overwrites those new bytes with the old original and reports completion. - In a separate case, create a profile after the snapshot. Restoration deletes that new profile because the snapshot recorded no original profile and no injected profile hash.
- For the reinjection case, place recognizable routed configuration plus new user settings over the same hashless snapshot. Current preflight accepts it and injection can fill the missing hash while retaining the older original.
The prepared regressions use temporary config/profile files and mock/local execution only. The proposed policy keeps a changed file and the journal when that file lacks its own injection hash, accepts already-original files without rewriting them, and permits a genuinely native configuration to establish a new snapshot. Routed reinjection must verify the retained snapshot before writing.
Version
402be7c; Bun 1.4.0 (34cbb9a40)
Operating system
Microsoft Windows 11 Pro, version 10.0.26200 (build 26200)
Provider and model
Not provider-specific. No live account, provider request, or reset credit is used.
Logs or error output
hashless interrupted snapshot preserves later native config edits:
expected current user settings; received the earlier original config
hashless interrupted snapshot preserves a later profile:
expected the current profile; received null
hashless native restore refuses instead of reporting an uncertain snapshot as restored:
expected success=false; received true
hashless routed snapshot is not promoted by reinjection after user edits:
expected preflight.success=false; received true
Six regressions failed against unchanged base source, including redundant rewriting and loss of an empty original profile's presence. The proposed correction preserves empty files separately from absent files.
Screenshots and supporting files
A focused source/test/documentation pull request will link this issue. This is a filesystem-state defect; no screenshot is needed to reproduce it.
Redacted configuration
model_provider = "openai"
model = "user-selected-model"Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
lidge-jun commentedon Sep 8, 2026
리뷰 · 우선순위 68 / 80
이 이슈는 Codex 저널에
injectedConfigHash/injectedProfileHash가 없는(해시 없는) 스냅샷이 생기면, 그 뒤에 사용자가 고친 네이티브 설정이나 새로 만든 프로필을restoreJournalState()가 ‘우리 주입이 그대로다’라고 잘못 보고 예전 원본으로 덮어쓰거나 지워 버리는 구멍입니다. 지금devHEAD402be7c1f(package 2.48.0)의src/codex/journal.ts를 보면 그 판단이 한 줄에 드러납니다. config는!journal.injectedConfigHash || sha256(currentConfig) === journal.injectedConfigHash이고, profile은journal.injectedProfileHash === undefined || …입니다. 해시가 없으면 왼쪽 조건이 바로 참이 되어 ‘변경 없음’으로 취급하고, 원본config.toml을 다시 쓰거나(원본에 프로필이 없었으면) 나중에 생긴 프로필 파일을 지웁니다.재현도 HEAD 기준으로 좁고 깨끗합니다. 격리된 Codex 홈에서
writeJournal()만 하고markJournalInjectedState()를 건너뛴 뒤(중단·레거시 기록과 같은 상태), 설정을 바꾸고 다른 모델을 고르거나 프로필을 만든 다음 restore를 돌리면 새 바이트가 사라지고 ‘복원 완료’로 보고됩니다. 작성자가 붙인 실패 로그(expected current user settings/expected the current profile/expected success=false)가 그 계약을 그대로 보여 줍니다. 닫힌 이슈 #2948(임의로 오래된 설정을 되살리는 큰 이야기)을 참고만 하고, 그 역사적 원인이나 모든 restore 로그 줄을 덮어쓰기라고 단정하지 않은 점도 좋습니다. 여기는 ‘해시 검증 없는 스냅샷’이라는 지금 소스의 좁은 결함입니다.두 번째 경로는 더 교묘합니다. 해시 없는 저널 위에 라우팅된(주입된) 설정을 다시 주입하면,
markJournalInjectedState의injectedConfigHash ??= sha256(config)가 비어 있던 자리에 새 주입 해시를 채우면서originalConfig는 예전 스냅샷을 그대로 둡니다. 이후 restore는 ‘지금 파일이 그 새 해시와 같다’고 보고 예전 원본을 신뢰된 복원으로 받아들입니다. 즉 검증되지 않은 스냅샷이 reinjection만으로 ‘검증된 것처럼’ 승격됩니다. 제안 정책(해시 없는 파일이 바뀌었으면 파일과 저널을 유지, 이미 원본이면 다시 쓰지 않음, 진짜 네이티브만 새 스냅샷 허용, 라우팅 reinjection은 보존된 스냅샷을 먼저 검증)은 현재 코드의writeJournal네이티브 전제·??= 첫 해시 고정의도와도 맞습니다.호출부도 같이 봐야 합니다.
restoreJournal()은restoreJournalState().complete만 보고,reconcileJournal()은 죽은 PID/다른 클라이언트일 때 restore를 돌린 뒤 경고를 찍습니다. 해시 없는 스냅샷을 ‘완전 복원 성공’으로 보고하면 저널이 지워지고(if (complete) removeJournal()), 나중에는 무엇을 덮어썼는지 추적할 증거도 사라집니다. 그래서 네이티브 restore와 reconcile 호출자가 ‘검증된 부분 복원’과 ‘검증 안 된 스냅샷 거절’을 구분해야 한다는 이슈의 요구는 맞습니다. 빈 원본 프로필과 ‘원래 프로필 파일 자체가 없음’을 구분하자는 점도, 지금originalProfile: null이면 존재하던 프로필을 지우는 분기와 직결됩니다.우선순위 68로 둔 이유는, 매 요청마다 터지는 프록시 버그는 아니지만 unclean shutdown·주입 중단 창에서 사용자
config.toml/ 프로필을 되돌리거나 지울 수 있는 데이터 손실형이기 때문입니다. types.ts/config.ts 분할 캠페인과 무관하고, 지금 열린 짝 PR은 아직 이 이슈 번호로 연결되어 있지 않습니다(작성자가 곧 소스·테스트·문서 PR을 링크하겠다고 했습니다). 테스트는 임시 설정/프로필과 mock/local만 쓰겠다고 해서 실계정·reset credit 오염 위험도 없습니다.경로
src/codex/journal.tsrestoreJournalState약 215행 - 해시 부재를 unchanged로 취급해 원본을 덮어쓴다.경로
src/codex/journal.tsmarkJournalInjectedState약 144행 - 해시 없는 저널에 새 주입 해시만 채우면 옛 original이 ‘검증된 스냅샷’처럼 승격된다.경로
src/codex/journal.tsreconcileJournal/restoreJournal- complete=true면 저널을 지워 증거까지 사라질 수 있다.관련 #2948 - 이미 닫힌 큰 복원 이야기와 겹치지만, 이 이슈는 HEAD의 해시 검증 구멍만 겨냥하므로 중복으로 닫지 않는다.
메인테이너의 판단이 필요한 지점
너의 추천
이슈는 open으로 두고, 작성자가 올리는 회귀 테스트 포함 PR을 기다린다. 머지 조건의 최소안은 (1) 해시 없는 config/profile은 ‘변경 없음’으로 쓰지 말 것, (2) reinjection이 해시 없는 스냅샷을 승격하지 말 것, (3) restore/reconcile이 미검증 스냅샷을 complete 성공으로 보고하며 저널을 지우지 말 것이다. PR이 오면 #2948 재오픈이 아니라 이 이슈만 Closes로 묶으면 된다.
이 댓글은 grok-bot이 작성했습니다
wp1: land the luvs01 fixture and bug-fix train (9 PRs) (#4049)
lidge-jun commentedon Sep 8, 2026
Fixed on
devin cfff026 (via #4049, carrying #4006 by @luvs01).A journal entry without
injectedConfigHashno longer counts as proof the file is unmodified.restoreJournalStatenow reportsunverifiedand preserves both the current files and the journal instead of overwriting your configuration with the saved original, and injection refuses to write when the journal has no verified baseline. Recovery for that case is documented in the Codex integration guide under "Recovery without injection hashes". Homes whose journals carry hashes — written sincec29ee783e(2026-06-27) — are unaffected.Verified at the merged head:
bun test tests/codex-integration/codex-journal.test.tspasses, and Cross-platform CI run 34256853348 was green across 19 jobs.Closing manually because pull requests here target
dev, and GitHub only auto-closes on merge intomain.lidge-jun commentedon Sep 8, 2026
Landed via #4049 at cfff026
wp1: land the luvs01 fixture and bug-fix train (9 PRs) (lidge-jun#4049)