codex exec Reading additional input from stdin 멈춤 해결
Reading additional input from stdin...은 codex exec가 표준 입력을 읽으러 들어갔다는 안내이고, 입력의 끝이 오지 않으면 실행은 그 자리에서 기다립니다. 예약 작업은 돌았는데 이 줄 뒤로 로그가 늘지 않는다면, 프롬프트 내용보다 프로세스에 연결된 표준 입력을 먼저 볼 일입니다. Codex를 cron과 스크립트로 운영에 넣으려고 Debian 13 서버에서 codex exec를 열린 파이프, 닫힌 입력, 쓰기 허용 폴더라는 조건으로 나눠 실행했고, 입력 대기 없이 끝나는지와 요청한 파일이 생기는지는 종료 값과 파일 목록으로 가렸습니다. 프롬프트를 명령 인자로 넘겨도 이 대기는 생기며, 아래 재현은 입력을 보내지 않는 열린 파이프로 그 조건을 만듭니다. 확인은 EOF 전달, 작업 경로, 샌드박스 권한을 하나씩 떼어서 했고, 종료 상태와 산출물을 같이 봤습니다.
시험한 서버와 Codex 버전
| 항목 | 테스트 환경 |
|---|---|
| OS | Debian GNU/Linux 13 (trixie) |
| 주요 도구 버전 | codex-cli 0.162.1 |
| 실행 시간 제한 도구 | GNU coreutils timeout |
| 로그인 | ChatGPT 계정 로그인 |
| 모델 | -m gpt-6-astra |
| 확인일 | 2026-10-15 KST |
추가 입력을 받지 않는 자동 실행이라면 명령에 < /dev/null을 붙여 입력을 닫아 두는 규칙이 필요하고, 이번 실험도 그 규칙이 왜 필요한지 보여 주는 데 목적이 있습니다. 입력이 끝났다는 사실을 명시하고, 실행 로그와 최종 답변을 나누어 보관한 다음, 종료 상태와 실제 산출물을 함께 확인합니다. 성공 여부를 터미널에 표시된 문장 하나로 판단하지 않도록 구성합니다.
명령 구성의 근거는 Codex 비대화형 실행 문서 (확인일 2026-10-15)이고, CLI 사용법은 Codex CLI 문서 (확인일 2026-10-15)를 참고했습니다. 아래 결과는 위 환경에서 남긴 실행 기록 그대로입니다. 다른 환경과 견줄 때도 버전과 실행 옵션부터 맞춰 두어야 증상이 섞이지 않습니다.
열린 stdin 앞에서 멈추는 codex exec
파이프를 열어 둔 채 실행해 보기
재현에는 sleep 60 파이프를 사용합니다. cron·CI 또는 다른 프로그램의 자식 프로세스에서는 표준 입력이 열린 채 전달되는 경우가 있으므로, 입력을 보내지 않으면서 파이프가 바로 닫히지도 않는 조건을 만듭니다. 아래 명령의 M에는 실험 모델을 지정합니다. 실제 캡처의 셸 변수도 같은 모델을 가리킵니다.
M=gpt-6-astra
sleep 60 | timeout 25 codex exec --skip-git-repo-check -s read-only -m "$M" "Reply with OK only"
echo "codex_exit=${PIPESTATUS[1]}"
재현 스크립트는 두 가지를 잇달아 봅니다. 열린 표준 입력 때문에 생기는 대기, 그리고 존재하지 않는 작업 디렉터리 때문에 생기는 즉시 실패입니다. 앞 명령은 입력을 보내지 않는 파이프를 열어 둔 채 EOF를 기다리게 하고, 뒤 명령은 < /dev/null로 입력을 닫은 상태에서 없는 경로를 지정합니다. 아래 출력의 codex_exit=124와 codex_exit=1이 그 차이입니다. 앞의 것은 기다리다 끊긴 실행이고, 뒤의 것은 시작 조건에서 바로 실패한 실행입니다.
$ codex --version
codex-cli 0.162.1
$ sleep 60 | timeout 25 codex exec --skip-git-repo-check -s read-only -m $M "Reply with OK only"; echo "codex_exit=${PIPESTATUS[1]}"
Reading additional input from stdin...
codex_exit=124
$ timeout 30 codex exec --skip-git-repo-check -C /srv/no-such-workdir -m $M "Reply with OK only" < /dev/null; echo "codex_exit=$?"
Error: No such file or directory (os error 2)
codex_exit=1

캡처에는 같은 터미널에서 난 두 실패가 나란히 있습니다. 열린 파이프를 물린 명령은 추가 입력 안내 뒤에 codex_exit=124를 남기고, 입력을 닫고 없는 작업 폴더를 지정한 명령은 os error 2와 함께 codex_exit=1로 끝납니다. 예약 작업 알림에서도 이 둘이 갈라져 있어야 입력 연결을 고칠지 작업 폴더 설정을 고칠지 정해집니다.
프롬프트를 인자로 줘도 EOF는 따로 와야 한다
관찰한 동작을 기준으로 보면 codex exec는 stdin이 터미널이 아닐 때 추가 입력을 읽으려 하며, 입력의 끝을 뜻하는 EOF가 올 때까지 기다립니다. 프롬프트를 이미 인자로 전달했더라도 파이프의 입력 종료까지 대신 전달한 것은 아니므로 대기가 생깁니다. 재현에서는 timeout 25가 실행을 종료하면서 codex_exit=124를 남깁니다. 이 값은 해당 실험에서 timeout에 의한 강제 종료를 나타냅니다.
같은 출력의 뒤쪽에는 Error: No such file or directory (os error 2)와 codex_exit=1이 나타납니다. 이 명령은 이미 stdin에 /dev/null을 연결했지만, -C /srv/no-such-workdir가 가리키는 디렉터리가 없어 바로 실패합니다. 앞의 대기와 뒤의 경로 오류는 고칠 곳이 다르니, 같은 멈춤으로 묶지 않습니다.
입력은 닫고, 답변은 파일로 받기
추가 표준 입력이 필요 없는 작업에서는 명령 끝에 < /dev/null을 붙여 즉시 EOF를 전달합니다. 이어서 -o out.md를 지정해 최종 답변을 파일로 저장합니다. 다음 명령은 해결 스크립트의 실행 구성을 작업 디렉터리 안에서 사용할 수 있도록 정리한 형태입니다. 먼저 존재하는 작업 폴더로 이동한 상태에서 실행합니다.
M=gpt-6-astra
timeout 120 codex exec --skip-git-repo-check -s read-only -m "$M" -o out.md "Reply with OK only" < /dev/null > run.log 2>&1
echo "codex_exit=$?"
cat out.md
grep -c "Reading additional input from stdin" run.log
ls -la
< /dev/null을 붙이면 상위 프로세스가 넘긴 열린 stdin 때문에 기다리는 일이 사라집니다. 이 구성은 프롬프트를 인자로 넘기고 추가 입력을 받지 않는 작업용입니다. 파이프로 작업 내용을 보내야 한다면, 보낸 내용 뒤에 입력 종료까지 제대로 가는지 따로 점검해야 합니다.
-o는 최종 답변을 따로 떼어 주므로 로그를 결과물로 가공하는 수고가 줄어듭니다. 대신 답변과 로그를 어디에 두는지 정해 두지 않으면 실행이 끝난 뒤 기록을 찾기 어렵습니다. 이전 실행의 파일을 새 결과로 착각하지 않도록 실행마다 출력 위치를 나누고, 종료 상태와 내용을 맞대어 봅니다.
해결 스크립트의 실제 실행에서는 codex_exit=0으로 종료하고 out.md에 OK를 저장합니다. grep -c 결과가 1이라는 점도 함께 확인합니다. 안내 문구는 남지만 곧바로 EOF를 만나 진행하므로, 이 문구가 찍혔다는 것만으로 장애라고 보지 않습니다. 파일 목록에도 답변과 로그가 따로 남았습니다.
$ timeout 120 codex exec --skip-git-repo-check -s read-only -m $M -o $R/wb/out.md "Reply with OK only" < /dev/null > run.log 2>&1; echo "codex_exit=$?"
codex_exit=0
$ cat out.md; echo; grep -c "Reading additional input from stdin" run.log
OK
1
$ ls -la
total 16
drwxr-xr-x 2 box box 4096 Oct 15 23:39 .
drwxr-xr-x 6 box box 4096 Oct 15 23:39 ..
-rw-r--r-- 1 box box 2 Oct 15 23:39 out.md
-rw-r--r-- 1 box box 489 Oct 15 23:39 run.log

해결 스크립트 캡처에는 codex_exit=0과 out.md의 OK가 함께 보입니다. 추가 입력 안내를 센 결과는 1이라, 안내 자체가 사라진 것은 아닙니다. 파일 목록에는 out.md와 run.log가 모두 남아 답변 저장과 실행 기록 보관이 됐음을 보여 줍니다. 그래서 저는 안내 문자열이 있느냐보다 프로세스 종료와 답변 내용을 성공 기준으로 삼습니다.
없는 작업 폴더는 곧바로 실패한다
cron·CI에서 작업 폴더를 잘못 지정했다면 /dev/null 적용 여부와 별도로 경로를 고칩니다. 존재하는 디렉터리를 -C에 전달하거나 스크립트에서 해당 폴더로 이동한 뒤 실행합니다. 다음은 점검 스크립트에서 사용하는 전용 작업 폴더를 준비하는 핵심 구성입니다. R은 실행 스크립트가 있는 폴더를 뜻하며, Bash 스크립트 안에서 설정합니다.
R=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
M=gpt-6-astra
mkdir -p "$R/wc"
timeout 120 codex exec --skip-git-repo-check -s workspace-write -C "$R/wc" -m "$M" -o ww.md "Create a file hello.txt containing OK" < /dev/null
전용 폴더를 만든 뒤 -C로 지정하면, 예약 작업을 어디서 시작했느냐에 따라 대상이 달라지는 혼선이 줄어듭니다. 이 단계가 해결하는 것은 작업 경로가 있느냐 없느냐뿐입니다. 저장소 검사나 샌드박스 쓰기 권한은 이어지는 실행 메시지로 따로 판단합니다.
저장소 검사, 쓰기 권한, 실행 시간 상한
검사 통과와 파일 쓰기는 별개
입력 대기를 없앤 다음에는 저장소 검사와 파일 쓰기 권한을 확인합니다. 점검 스크립트는 Git 저장소 밖에서 검사 옵션 없이 실행하는 경우, 읽기 전용 상태에서 파일 생성을 요청하는 경우, 전용 폴더에 쓰기를 허용하는 경우를 차례로 비교합니다. 모두 stdin 대기 문제와 분리해서 읽어야 하는 결과입니다. 검사 옵션을 빼면 저장소 검사 메시지가 나오고, 읽기 전용 실행에서는 쓰기 거부가 나오므로 파일 목록과 내용까지 대조합니다.
$ timeout 60 codex exec -m $M "Reply OK" < /dev/null 2>&1 | tail -1
Not inside a trusted directory and --skip-git-repo-check was not specified.
$ timeout 120 codex exec --skip-git-repo-check -s read-only -C $R/wc -m $M -o ro.md "Create a file hello.txt containing OK" < /dev/null > ro.log 2>&1; cat ro.md; echo; ls
Couldn’t create `hello.txt`: the workspace is read-only, and the sandbox rejected the write.
ro.log ro.md
$ timeout 120 codex exec --skip-git-repo-check -s workspace-write -C $R/wc -m $M -o ww.md "Create a file hello.txt containing OK" < /dev/null > ww.log 2>&1; echo "codex_exit=$?"; ls; cat hello.txt
codex_exit=0
hello.txt ro.log ro.md ww.log ww.md
OK

점검 스크립트 캡처를 보면 저장소 검사 통과와 작업 파일 생성은 서로 다른 조건입니다. 읽기 전용 실행에서도 ro.md는 남지만 요청한 hello.txt는 목록에 없습니다. 쓰기 허용 실행에서는 codex_exit=0 뒤에 hello.txt가 나타나고 내용도 OK로 읽힙니다. 답변 파일이 생겼는지만 감시하면, 실제 작업 파일을 만들지 못한 실행까지 성공으로 넘어가게 됩니다.
첫 실행의 Not inside a trusted directory and --skip-git-repo-check was not specified.는 저장소 검사에 관한 메시지입니다. 이번처럼 저장소 밖에서 수행하는 실험에는 --skip-git-repo-check를 명시합니다. 이 옵션과 샌드박스 옵션은 서로 다른 역할을 하므로, 저장소 검사를 통과하는 설정을 파일 쓰기 허용 설정으로 해석하지 않습니다.
--skip-git-repo-check는 저장소 밖 실험이 검사에 걸려 멈추는 조건을 처리합니다. 검사를 건너뛰는 만큼 실행 폴더는 스크립트에서 분명히 지정해 둡니다. 이 옵션을 넣어도 읽기 전용 작업 공간의 쓰기 요청이 허용되지는 않으니, 쓰기 거부 메시지가 남으면 권한 설정을 봅니다.
읽기 전용 실행에서는 모델 답변에 쓰기가 거부되었다는 내용이 나타나고, 목록에도 hello.txt가 없습니다. 반면 -s workspace-write와 -C를 함께 사용한 실행에서는 codex_exit=0, hello.txt 생성, 파일 내용 OK를 함께 확인합니다. 앞선 해결 스크립트에서 -o로 답변 파일을 저장한 것과, 모델이 작업 파일을 생성하는 요청은 구분해 판단합니다.
workspace-write와 전용 작업 폴더를 함께 쓰면 파일 생성이 필요한 요청이 지정한 위치에서 이뤄집니다. 쓰기를 허용한 실행에는 생성된 파일과 그 내용을 같이 검토하는 결과 수집 절차를 붙이고, 답변 텍스트만 필요한 작업은 읽기 전용 설정으로 둡니다.
운영에서는 쓰기 작업을 -C로 지정한 전용 폴더에 한정하는 구성을 사용합니다. --dangerously-bypass-approvals-and-sandbox는 사용하지 않습니다. 권한 설정은 Codex 보안 문서 (확인일 2026-10-15)를 기준으로 봤습니다. CLI 프로젝트 자체는 openai/codex 저장소 (확인일 2026-10-15)에 있고, 이 글의 관찰 결과는 테스트 환경 표에 적은 버전 기준입니다.
끝났다는 것과 성공했다는 것
실행 시간 상한에는 timeout을 사용합니다. 입력 대기를 재현할 때의 25와 정상 실행 예제의 120은 각 실험 명령에 사용한 값으로 구분합니다. 운영 작업에서도 상한을 두되, 상한에 도달했다는 사실과 작업이 성공했다는 사실을 혼동하지 않아야 합니다. 종료 상태 해석은 GNU timeout 매뉴얼 (확인일 2026-10-15)을 참고합니다.
점검은 종료 코드, 로그 메시지, 결과 파일 내용 순서로 합니다. 해결 스크립트에서는 정상 종료와 답변 파일의 OK가 맞는지 보고, 점검 스크립트에서는 답변 파일뿐 아니라 요청한 hello.txt가 실제 목록에 있는지도 봅니다. 실행 완료 문구에서 멈추지 말고 ls와 파일 읽기까지 해야 요청이 이뤄졌는지 알게 됩니다. 대기 문구만 남았다면 stdin을, 곧바로 경로 오류가 났다면 -C를, 쓰기 거부가 나왔다면 샌드박스를 먼저 봅니다.
CLI를 갱신한 뒤의 감시 규칙
CLI를 갱신할 때는 실행 옵션과 로그 문구를 전제로 만든 감시 규칙도 같이 손봐야 합니다. 이번 기록에서 추가 입력 안내는 대기한 실행에도, 정상 종료한 실행에도 나왔습니다. 헬스체크를 이 문자열이 있느냐에만 걸어 두면 정상 작업까지 장애로 분류됩니다. 갱신 뒤에는 입력을 닫은 실행의 종료 상태와 답변 저장부터 확인하고, 파일 생성 작업은 요청한 산출물이 실제로 생겼는지까지 봅니다. 옵션이 바뀌었는지는 사용 중인 CLI의 안내와 대조하고, 작업 경로를 지정하는 -C와 답변 경로를 지정하는 -o가 실행 스크립트에서 의도한 위치를 가리키는지도 함께 맞춥니다.
컨테이너에서 예약 작업을 돌린다면, 프로세스가 떠 있다는 것과 작업 결과가 준비됐다는 것도 다른 이야기입니다. k8s probe는 호출을 받는 프로세스의 상태를 보고, 배치 작업의 성공 여부는 종료 상태와 저장된 결과로 따로 모으는 구성이 맞습니다. 실시간 요청을 받는 경로에는 작업 접수와 상태 조회를 두고, Codex 실행은 별도 작업 프로세스로 떼어 내는 방안을 권합니다. 그러면 입력 대기나 실행 시간 상한에 걸린 작업이 요청 처리 상태와 섞이지 않습니다.
열린 stdin은 /dev/null로 닫고, 작업 위치는 -C로 못 박기
실험 결과를 운영 점검 순서로 옮긴 것이 아래 표입니다. 입력 대기는 EOF 전달로, 경로 오류는 존재하는 작업 폴더 지정으로 대응하고, 저장소 검사와 쓰기 거부에는 각각의 설정을 적용합니다. 겉으로는 같은 예약 작업 실패여도, 출력에 남은 증상을 보면 고칠 지점이 좁혀집니다.
| 상황 | 실행에서 확인한 결과 | 적용할 구성 |
|---|---|---|
| 열린 파이프의 stdin | 추가 입력 대기 후 codex_exit=124 | 추가 입력이 없으면 < /dev/null을 사용합니다. |
| 빈 stdin과 답변 저장 | codex_exit=0, out.md 내용 OK | < /dev/null과 -o를 함께 사용합니다. |
| 존재하지 않는 작업 경로 | os error 2, codex_exit=1 | 존재하는 -C 경로 또는 cd를 사용합니다. |
| 저장소 밖의 검사 실패 | trusted directory 관련 메시지 | 해당 실행에 –skip-git-repo-check를 명시합니다. |
| read-only에서 파일 생성 요청 | 쓰기 거부, hello.txt 미생성 | 쓰기 필요 여부를 먼저 판단합니다. |
| 전용 폴더에서 쓰기 요청 | workspace-write에서 hello.txt 내용 OK | -s workspace-write와 -C를 함께 사용합니다. |
이번 실험에서 예약 작업에 먼저 고정해야 할 것은 입력 종료와 작업 위치였습니다. 답변만 필요한 cron·스크립트에는 read-only, < /dev/null, -o, timeout을 조합하는 구성을 추천합니다. 파일 생성 요청에는 존재하는 전용 폴더를 -C로 지정하고 workspace-write를 적용합니다. 저장소 밖 실행에 필요한 검사 옵션은 쓰기 권한과 따로 관리합니다. 안내 문구가 남아도 종료 상태와 답변이 정상이라면, 그 문구 하나로 입력 대기 장애라고 보지 않는다는 것이 이번 실험의 판단입니다.
운영 스크립트에는 실행별 답변과 로그를 보관하고, 요청한 작업 파일을 읽어 결과를 확인하는 절차를 넣어 둡니다. 실행 시간 상한에 걸린 작업은 같은 입력 연결을 둔 채 되풀이하기보다 stdin과 경로부터 봐야 원인을 찾기 쉽습니다. 실시간 서비스에 연결한다면 요청을 받는 부분과 Codex를 실행하는 작업 프로세스를 나누고, 완료된 결과를 전달하는 구성을 권합니다. 서버 증설을 따지기 전에 열린 입력 때문에 기다리는 상황부터 없애야 실행 지연의 원인이 제대로 보입니다. 이 서버에서 < /dev/null을 붙인 실행은 정상 종료했고 workspace-write 실행에서는 hello.txt가 생겼으므로, 저는 이 구성을 예약 작업의 기본 실행 방식으로 삼겠습니다.






