Corrupted deny_read_acl_state.json causes persistent "apply deny-read ACLs" sandbox failures · Issue #34276 · openai/codex
What version of the Codex App are you using (From “About Codex” dialog)? 0.145.0-alpha.18 What subscription do you have? ChatGPT Plus What platform is your computer? Microsoft Windows NT 10.0.26200...
github.com
Windows용 Codex App에서 Browser Use와 Computer Use가 갑자기 실행되지 않았다. 화면만 보면 Chrome 연결이나 Browser Use 기능 자체의 문제처럼 보였다. 오류 메시지를 따라 설정을 바꾸기 전에, 기능이 실행될 때 어떤 프로세스와 파일을 거치는지부터 확인했다.
직접 원인은 결국 이 파일 하나였다.
C:\Users\<user>\.codex\.sandbox\deny_read_acl_state.json
정상이라면 JSON이어야 할 파일이 실제로는 22바이트 전체가 0x00인 NUL 바이트 파일로 남아 있었다. Codex는 실행할 때마다 이 파일을 JSON으로 읽으려 했다.
expected value at line 1 column 1
파싱 오류가 발생하면서 Windows sandbox ACL 초기화가 중단됐다.
손상 파일을 백업한 뒤 Codex를 다시 실행하자 같은 경로에 정상 JSON이 재생성됐다. 이후 sandbox 초기화와 Browser Use도 정상 동작했다.
전체 조사 흐름은 이렇게 이어졌다.
먼저 복구만 필요한 경우
조사 과정보다 복구가 급하다면 아래 순서만 밟으면 된다.
1. Codex 완전 종료
Stop-Process -Name Codex -Force -ErrorAction SilentlyContinue
관련 프로세스가 남아 있는지 확인한다.
Get-CimInstance Win32_Process |
Where-Object {
$_.Name -match 'codex|sandbox'
} |
Select-Object Name, ProcessId, ParentProcessId, CommandLine |
Format-List
2. 상태 파일을 삭제하지 말고 백업
Rename-Item `
"$env:USERPROFILE\.codex\.sandbox\deny_read_acl_state.json" `
"deny_read_acl_state.json.broken"
원인을 다시 들여다볼 수 있도록 삭제 대신 이름만 바꿔 남겼다.
3. Codex 재실행
Codex를 다시 실행한 뒤 Browser Use 또는 Computer Use를 한 번 실행한다.
4. 상태 파일이 다시 생성됐는지 확인
Get-Content `
"$env:USERPROFILE\.codex\.sandbox\deny_read_acl_state.json" `
-Raw
내 환경에서는 이런 파일이 다시 만들어졌다.
{
"principals": {}
}
5. sandbox 로그 확인
최신 로그에는 세 줄이 남아 있었다.
errors=[]
setup binary completed
read ACL run completed
화면에서 기능이 다시 도는 것까지는 눈으로 보인다. 실패했던 sandbox 초기화 단계가 실제로 정상 완료됐는지까지 봤다.
1. 처음에는 Browser Use 자체의 문제처럼 보였다
문제가 났을 때 시작되지 않은 기능은 이랬다.
- Codex Browser Use
- Codex Computer Use
- Windows sandbox 초기화
- Chrome 탭 연결 전 단계의 브라우저 제어
- Windows 화면 제어
Chrome 로그인이나 확장 프로그램 연결 자체는 정상이었다. 그래서 처음에는 아래 흐름 중 어디에서 막히는지 짚어내지 못했다.

화면에 드러나는 건 맨 끝의 브라우저 제어 실패뿐이었다. 그런데 브라우저가 안 된다는 현상만 보고 Chrome 설정부터 바꾸면 조사 범위가 너무 넓어진다. 그래서 Browser Use를 실행할 때 실제로 어떤 프로세스가 먼저 움직이는지부터 봤다.
2. 프로세스 흐름을 보고 장애 위치를 Chrome보다 앞 단계로 좁혔다
먼저 Codex와 sandbox 관련 프로세스를 훑었다.
Get-CimInstance Win32_Process |
Where-Object {
$_.Name -match 'codex|sandbox'
} |
Select-Object Name, ProcessId, ParentProcessId, CommandLine |
Format-List
실시간으로 가볍게 볼 때는 이 명령도 같이 썼다.
Get-Process |
Where-Object {
$_.ProcessName -match 'codex|sandbox'
} |
Select-Object ProcessName, Id, Path
이어서 sandbox 로그에서 helper 프로세스 실행 흔적을 뒤졌다.
$logDir = "$env:USERPROFILE\.codex\.sandbox"
Get-ChildItem $logDir -Filter "sandbox*.log" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1 |
ForEach-Object {
Select-String -Path $_.FullName `
-Pattern "START:|setup refresh: spawning|codex-windows-sandbox-setup"
}
로그에서는 Browser 명령이 실행되기 전에 filesystem helper와 Windows sandbox setup 프로그램이 먼저 실행되고 있었다.
START:
C:\Users\<user>\.codex\.sandbox-bin\codex.exe
--codex-run-as-fs-helper
setup refresh: spawning
...\codex-windows-sandbox-setup.exe
이 시점에서 문제 범위가 한 겹 줄었다.

Chrome에 붙기도 전에, 그보다 앞선 Windows sandbox 준비 과정에서 이미 막히고 있었다.
3. 상위 오류 메시지는 실패한 단계를 가리킬 뿐이었다
최신 sandbox 로그에서 error, deny-read, helper_unknown_error를 검색했다.
$latestLog = Get-ChildItem "$env:USERPROFILE\.codex\.sandbox" `
-Filter "sandbox*.log" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
Select-String -Path $latestLog.FullName `
-Pattern "setup error|apply deny-read ACLs|helper_unknown_error"
맨 위에 걸린 오류는 이것이었다.
setup error: apply deny-read ACLs
별도 오류 상태에도 같은 문구가 남아 있었다.
{
"code": "helper_unknown_error",
"message": "apply deny-read ACLs"
}
처음에는 ACL 권한 자체가 잘못됐다고 볼 수도 있었다. 다만 이 문구는
ACL을 적용하지 못했다.
는 실패 단계를 말할 뿐,
왜 ACL 적용에 실패했는가?
까지 설명하지 않는다. 그래서 설정은 그대로 둔 채 로그의 하위 원인을 더 따라갔다.

4. Caused by를 따라가자 상태 파일 하나로 범위가 좁혀졌다
로그 전체에서 상태 파일과 JSON 파싱 관련 문구를 검색했다.
Select-String -Path $latestLog.FullName `
-Pattern "deny_read_acl_state.json|parse deny-read ACL state|expected value|EOF while parsing" `
-Context 4, 8
여기서 실제 하위 오류가 나왔다.
Caused by:
0: parse deny-read ACL state
C:\Users\<user>\.codex\.sandbox\deny_read_acl_state.json
1: expected value at line 1 column 1
같은 오류는 Codex를 다시 실행해도 그대로 반복됐다. 흐름은 여기까지 좁혀졌다.

이제 Browser, Chrome, 인증 같은 영역은 조사 대상에서 일단 뺐다. 남은 대상은 deny_read_acl_state.json 하나였다.
5. 빈 파일 말고도 후보는 여럿이었다
expected value at line 1 column 1은 JSON 파서가 첫 번째 위치부터 유효한 값을 읽지 못했다는 뜻이다. 이 메시지만으로 파일 상태를 확정할 수는 없었다. 같은 오류를 낼 수 있는 경우가 여러 가지였다.

따라서 파일의 존재 여부 → 크기 → 일반 텍스트 내용 → 실제 바이트 순서로 확인했다.
6. 파일 크기는 22바이트인데 화면에는 아무것도 나오지 않았다
먼저 파일 메타데이터부터 봤다.
$stateFile = "$env:USERPROFILE\.codex\.sandbox\deny_read_acl_state.json"
Get-Item $stateFile |
Select-Object Name, Length, IsReadOnly, Exists, LastWriteTime
찍힌 값은 이랬다.
Name : deny_read_acl_state.json
Length : 22
IsReadOnly : False
Exists : True
LastWriteTime : 2026-07-18 오후 5:53:15
파일은 존재했고 0바이트도 아니었다. 그런데 일반 텍스트로 읽으면 아무것도 출력되지 않았다.
Get-Content "$env:USERPROFILE\.codex\.sandbox\deny_read_acl_state.json"
type "$env:USERPROFILE\.codex\.sandbox\deny_read_acl_state.json"
여기가 다음 단계로 넘어간 판단 지점이었다.

파일이 비었다면 0바이트여야 한다. 정작 실제로는 22바이트가 자리를 차지한 채 화면에는 아무것도 보이지 않았다. 출력되지 않는 제어 문자나 NUL 바이트가 들어 있을 가능성을 의심한 건 이 지점이었다.
Hex dump까지 내려간 이유도 여기에 있다.
파일 크기와 텍스트 출력 결과가 서로 맞지 않았다.
7. Hex dump로 추정을 실제 바이트까지 확인했다
텍스트 출력으로는 파일 내부를 설명할 수 없어서 Format-Hex로 바이트를 직접 찍었다.
Format-Hex $stateFile
출력은 두 줄이었다.
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00000010 00 00 00 00 00 00
22바이트가 모두 00이었다. NUL 바이트 개수도 따로 세어봤다.
$bytes = [System.IO.File]::ReadAllBytes($stateFile)
[PSCustomObject]@{
Length = $bytes.Length
NullBytes = ($bytes | Where-Object { $_ -eq 0 }).Count
}
결과:
$bytes = [System.IO.File]::ReadAllBytes($stateFile)
[PSCustomObject]@{
Length = $bytes.Length
NullBytes = ($bytes | Where-Object { $_ -eq 0 }).Count
}
이제 원인 흐름이 확정됐다.
apply deny-read ACLs는 표면에 드러난 오류였다. 실제 직접 원인은 그 아래에 있었다. ACL 상태를 읽는 데 필요한 JSON 파일이 손상돼 있었다.
8. 원인 파일은 지우지 않고 격리했다
원인을 찾았다고 문제 파일을 바로 삭제하지는 않았다. 복구가 실패하거나 추가 조사가 필요해질 수 있으니 원본 상태를 남겨두는 편이 낫다고 판단했다.
먼저 Codex 프로세스를 종료했다.
Stop-Process -Name Codex -Force -ErrorAction SilentlyContinue
관련 helper가 남아 있는지도 같이 봤다.
Get-CimInstance Win32_Process |
Where-Object {
$_.Name -match 'codex|sandbox'
} |
Select-Object Name, ProcessId, CommandLine
그다음 파일을 삭제하지 않고 이름만 바꿨다.
Rename-Item `
"$env:USERPROFILE\.codex\.sandbox\deny_read_acl_state.json" `
"deny_read_acl_state.json.broken"
손을 댄 순서는 이랬다.

복구만 놓고 보면 삭제해도 된다. 그래도 조사 중에는 원인 증거를 보존한 상태에서 재생성 여부를 확인하는 쪽이 더 안전했다.
9. 실패했던 흐름을 거꾸로 밟아 복구를 검증했다
Codex를 다시 실행하자 같은 경로에 새로운 deny_read_acl_state.json이 생성됐다.
Get-ChildItem "$env:USERPROFILE\.codex\.sandbox" `
-Filter "deny_read_acl_state.json*" |
Select-Object LastWriteTime, Length, Name
결과:
LastWriteTime Length Name
------------- ------ ----
2026-07-20 오후 4:07:48 22 deny_read_acl_state.json
2026-07-18 오후 5:53:15 22 deny_read_acl_state.json.broken
새 파일도 22바이트였다. 그 점이 걸렸다. 하지만 Hex 값은 완전히 달랐다.
00000000 7B 0A 20 20 22 70 72 69 6E 63 69 70 61 6C 73 22
00000010 3A 20 7B 7D 0A 7D
문자로 읽으면 이 JSON이다.
{
"principals": {}
}
JSON 파싱도 다시 걸어봤다.
Get-Content $stateFile -Raw |
ConvertFrom-Json |
ConvertTo-Json -Depth 10
마지막으로 처음 실패했던 sandbox 로그까지 되짚었다.
$latestLog = Get-ChildItem "$env:USERPROFILE\.codex\.sandbox" `
-Filter "sandbox*.log" |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
Select-String -Path $latestLog.FullName `
-Pattern "errors=\[\]|setup binary completed|read ACL run completed|setup error"
정상 로그는 이렇게 남았다.
errors=[]
setup binary completed
read ACL run completed
복구 확인은 원인을 찾던 방향을 거꾸로 밟은 셈이다.

브라우저가 켜졌다는 화면 결과만으로 판단을 끝내지 않았다. 처음 장애가 발생했던 중간 단계가 정상으로 돌아온 것까지 확인하고 나서야 복구됐다고 판단했다.
10. 파일이 왜 손상됐는지는 별개의 문제였다
여기서 구분해야 할 것이 하나 있다. 이번 조사로 직접 원인은 확인했다.
남은 질문은 하나였다.
왜 이 파일이 22개의 NUL 바이트로 만들어졌는가?
당시 로그만으로는 여기까지 확정할 수 없었다.
확인된 사실
- 손상 파일도 정상 재생성 파일도 길이는 똑같이 22바이트였다.
- 손상 파일은 그 22바이트 전체가 0x00이었다.
- Codex 재시작만으로는 손상 파일을 복구하지 않았다.
- 기존 파일을 격리하자 정상 JSON이 새로 생성됐다.
- 새 파일에서는 sandbox 초기화가 정상 완료됐다.
확인할 수 없었던 것
손상 시점의 write 오류나 프로세스 종료 로그가 남아 있지 않았다. 정확히 어떤 코드 경로가 파일을 NUL 바이트로 만들었는지는 이 증거만으로 알 수 없었다.
직접 원인과 최초 발생 원인을 섞어 말하지 않는 것이 중요했다.
11. 당시에는 비원자적 저장 또는 쓰기 중단 가능성을 의심했다
정상 파일과 손상 파일의 길이가 모두 정확히 22바이트라는 점은 특이했다. 정상 JSON 역시 줄바꿈과 공백을 포함하면 22바이트였다. 다음 사진처럼 유추를 해봤다.

떠오른 후보는 이랬다.
- 상태 파일 쓰기 도중 앱/helper 프로세스 종료
- 기존 파일을 직접 덮어쓰는 비원자적 저장
- 버퍼 초기화 이후 flush 실패
- 파일시스템 필터나 보안 프로그램 개입
- 특정 예외 경로의 저장 로직 버그
다만 당시 로그로는 어느 쪽도 확인되지 않았다. 가능성일 뿐 확정된 원인으로 보지는 않았다.
12. 후속 조사에서 경쟁 상태 가능성도 확인됐다
후속 조사 과정에서는 같은 deny_read_acl_state.json을 여러 Windows sandbox helper가 동시에 갱신할 때 발생할 수 있는 경쟁 상태를 다룬 Codex GitHub 이슈도 찾았다.
해당 이슈에서는 여러 helper가 하나의 상태 파일을 동기화 없이 읽고 쓸 때,
- truncate와 write 사이의 상태를 다른 프로세스가 읽는 문제
- 서로 같은 이전 상태에서 시작해 일부 갱신이 사라지는 lost update
등이 발생할 수 있는 구조가 분석됐다.
개념만 그리면 이렇다.

그렇다면 이 상태 파일 문제는 "우연히 JSON 하나가 깨졌다"에서 끝나지 않을 수도 있다. 공유 상태 파일을 여러 프로세스가 다루는 방식과도 연관될 가능성이 있다.
다만 이번 내 환경에서 정확히 어떤 실행 순서로 22개의 NUL 바이트가 남았는지는 여전히 확정할 수 없다.