훅
훅은 Codex를 확장하기 위한 프레임워크입니다. 에이전트 루프에 자체 스크립트를 삽입해 다음과 같은 기능을 만들 수 있습니다.
- 대화를 사용자 지정 로깅 또는 analytics 엔진으로 전송
- 팀 프롬프트를 검사해 API 키를 실수로 붙여 넣는 상황 차단
- 대화를 요약해 지속 기억을 자동 생성
- 대화 턴이 멈출 때 사용자 지정 검증을 실행해 표준 강제
- 특정 디렉터리에서 프롬프트 동작 사용자 지정
훅은 config.toml의 기능 플래그 뒤에 있습니다.
[features]
codex_hooks = true
런타임 동작:
- 여러 파일에서 일치하는 훅은 모두 실행됩니다.
- 같은 이벤트에 대해 여러 명령 훅이 일치하면 동시에 시작되므로, 한 훅이 다른 훅의 시작을 막을 수 없습니다.
PreToolUse,PermissionRequest,PostToolUse,UserPromptSubmit,Stop은 턴 범위에서 실행됩니다.
Codex가 훅을 찾는 위치
Codex는 활성 구성 계층 옆에서 다음 형식 중 하나로 훅을 찾습니다.
hooks.jsonconfig.toml안의 inline[hooks]테이블
설치된 플러그인은 플러그인 manifest 또는 기본 hooks/hooks.json 파일을 통해 수명주기 구성을 묶을 수도 있습니다. 패키징 규칙은 플러그인 만들기를 참고하십시오.
실제로 자주 쓰는 위치는 다음 네 곳입니다.
~/.codex/hooks.json~/.codex/config.toml{repo}/.codex/hooks.json{repo}/.codex/config.toml
둘 이상의 훅 소스가 있으면 Codex는 일치하는 훅을 모두 로드합니다. 우선순위가 높은 구성 계층이 낮은 계층의 훅을 대체하지 않습니다. 단일 계층에 hooks.json과 inline [hooks]가 모두 있으면 Codex는 이를 병합하고 시작 시 경고합니다. 계층마다 하나의 표현만 사용하는 것을 권장합니다.
프로젝트 로컬 훅은 프로젝트 .codex/ 계층이 신뢰된 경우에만 로드됩니다. 신뢰되지 않은 프로젝트에서도 Codex는 사용자 및 시스템 훅을 각자의 활성 구성 계층에서 로드합니다.
구성 형태
훅은 세 단계로 구성됩니다.
PreToolUse,PostToolUse,Stop같은 훅 이벤트- 해당 이벤트가 언제 일치하는지 결정하는 matcher 그룹
- matcher 그룹이 일치할 때 실행되는 하나 이상의 훅 handler
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"hooks": [
{
"type": "command",
"command": "python3 ~/.codex/hooks/session_start.py",
"statusMessage": "Loading session notes"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py\"",
"statusMessage": "Checking Bash command"
}
]
}
],
"PermissionRequest": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/permission_request.py\"",
"statusMessage": "Checking approval request"
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/post_tool_use_review.py\"",
"statusMessage": "Reviewing Bash output"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/user_prompt_submit_data_flywheel.py\""
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "/usr/bin/python3 \"$(git rev-parse --show-toplevel)/.codex/hooks/stop_continue.py\"",
"timeout": 30
}
]
}
]
}
}
참고:
timeout은 초 단위입니다.timeout을 생략하면 Codex는600초를 사용합니다.statusMessage는 선택 사항입니다.- 명령은 세션
cwd를 작업 디렉터리로 실행됩니다. - 저장소 로컬 훅은
.codex/hooks/...같은 상대 경로보다 git 루트 기준으로 해석하는 방식을 권장합니다. Codex가 하위 디렉터리에서 시작될 수 있으므로 git 루트 기반 경로가 더 안정적입니다.
config.toml에 같은 구성을 inline TOML로 작성하면 다음과 같습니다.
[features]
codex_hooks = true
[[hooks.PreToolUse]]
matcher = "^Bash$"
[[hooks.PreToolUse.hooks]]
type = "command"
command = '/usr/bin/python3 "$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py"'
timeout = 30
statusMessage = "Checking Bash command"
[[hooks.PostToolUse]]
matcher = "^Bash$"
[[hooks.PostToolUse.hooks]]
type = "command"
command = '/usr/bin/python3 "$(git rev-parse --show-toplevel)/.codex/hooks/post_tool_use_review.py"'
timeout = 30
statusMessage = "Reviewing Bash output"
requirements.toml의 관리형 훅
엔터프라이즈 관리형 요구 사항은 [hooks] 아래에 훅을 inline으로 정의할 수도 있습니다. 관리자가 훅 구성을 강제하면서 실제 스크립트는 MDM이나 다른 디바이스 관리 시스템으로 배포하려 할 때 유용합니다.
[features]
codex_hooks = true
[hooks]
managed_dir = "/enterprise/hooks"
windows_managed_dir = 'C:\enterprise\hooks'
[[hooks.PreToolUse]]
matcher = "^Bash$"
[[hooks.PreToolUse.hooks]]
type = "command"
command = "python3 /enterprise/hooks/pre_tool_use_policy.py"
timeout = 30
statusMessage = "Checking managed Bash command"
관리형 훅 참고 사항:
managed_dir은 macOS와 Linux에서 사용됩니다.windows_managed_dir은 Windows에서 사용됩니다.- Codex는
managed_dir의 스크립트를 배포하지 않습니다. 엔터프라이즈 도구가 별도로 설치하고 갱신해야 합니다. - 관리형 훅 명령은 구성된 관리형 디렉터리 아래의 절대 스크립트 경로를 사용해야 합니다.
Matcher 패턴
matcher 필드는 훅이 언제 실행되는지 필터링하는 정규식 문자열입니다. 지원 이벤트의 모든 발생에 일치시키려면 "*", ""를 사용하거나 matcher를 생략합니다.
현재 일부 Codex 이벤트만 matcher를 사용합니다.
| 이벤트 | matcher가 필터링하는 대상 | 참고 |
|---|---|---|
PermissionRequest | 도구 이름 | Bash, apply_patch, MCP 도구 이름 지원 |
PostToolUse | 도구 이름 | Bash, apply_patch, MCP 도구 이름 지원 |
PreToolUse | 도구 이름 | Bash, apply_patch, MCP 도구 이름 지원 |
SessionStart | 시작 출처 | 현재 런타임 값은 startup, resume, clear |
UserPromptSubmit | 지원 안 함 | 구성된 matcher는 무시됨 |
Stop | 지원 안 함 | 구성된 matcher는 무시됨 |
apply_patch의 경우 matcher는 Edit 또는 Write도 사용할 수 있습니다.
예:
Bash^apply_patch$Edit|Writemcp__filesystem__read_filemcp__filesystem__.*startup|resume|clear
공통 입력 필드
모든 명령 훅은 stdin으로 JSON 객체 하나를 받습니다.
일반적으로 사용하는 공통 필드는 다음과 같습니다.
| 필드 | 타입 | 의미 |
|---|---|---|
session_id | string | 현재 세션 또는 스레드 ID |
transcript_path | string 또는 null | 세션 transcript 파일 경로, 있으면 제공 |
cwd | string | 세션 작업 디렉터리 |
hook_event_name | string | 현재 훅 이벤트 이름 |
model | string | 활성 모델 slug |
턴 범위 훅은 이벤트별 표에 turn_id를 포함합니다.
전체 wire format이 필요하면 스키마를 참고하십시오.
공통 출력 필드
SessionStart, UserPromptSubmit, Stop은 다음 공통 JSON 필드를 지원합니다.
{
"continue": true,
"stopReason": "optional",
"systemMessage": "optional",
"suppressOutput": false
}
| 필드 | 효과 |
|---|---|
continue | false이면 해당 훅 실행을 stopped로 표시 |
stopReason | 중지 이유로 기록 |
systemMessage | UI 또는 이벤트 스트림에 경고로 표시 |
suppressOutput | 현재 파싱은 되지만 아직 구현되지 않음 |
출력 없이 exit code 0으로 종료하면 성공으로 처리되고 Codex가 계속 진행합니다.
PreToolUse와 PermissionRequest는 systemMessage를 지원하지만, continue, stopReason, suppressOutput은 현재 해당 이벤트에서 지원되지 않습니다.
PostToolUse는 systemMessage, continue: false, stopReason을 지원합니다. suppressOutput은 파싱되지만 현재 지원되지 않습니다.
이벤트별 훅
SessionStart
이 이벤트에서 matcher는 source에 적용됩니다.
공통 입력 필드에 더해 다음 필드가 제공됩니다.
| 필드 | 타입 | 의미 |
|---|---|---|
source | string | 세션 시작 방식: startup 또는 resume |
stdout의 일반 텍스트는 추가 developer context로 더해집니다.
stdout의 JSON은 공통 출력 필드와 다음 이벤트별 형태를 지원합니다.
{
"hookSpecificOutput": {
"hookEventName": "SessionStart",
"additionalContext": "Load the workspace conventions before editing."
}
}
additionalContext 텍스트는 추가 developer context로 더해집니다.
PreToolUse
PreToolUse는 Bash, apply_patch를 통한 파일 편집, MCP 도구 호출을 가로챌 수 있습니다. 다만 Codex가 다른 지원 도구 경로로 동등한 작업을 수행할 수 있으므로 완전한 강제 경계가 아니라 guardrail입니다.
아직 모든 shell 호출을 가로채지는 못하고 단순한 호출만 대상으로 합니다. 더 새로운 unified_exec 메커니즘은 풍부한 streaming stdin/stdout 처리를 지원하지만, interception은 아직 완전하지 않습니다. 마찬가지로 WebSearch나 shell/MCP가 아닌 다른 도구 호출은 가로채지 않습니다.
matcher는 tool_name과 matcher alias에 적용됩니다. apply_patch를 통한 파일 편집에는 apply_patch, Edit, Write를 사용할 수 있으며 훅 입력의 tool_name은 계속 "apply_patch"입니다.
공통 입력 필드에 더해 다음 필드가 제공됩니다.
| 필드 | 타입 | 의미 |
|---|---|---|
turn_id | string | Codex 확장: 활성 Codex 턴 ID |
tool_name | string | Bash, apply_patch, mcp__fs__read 같은 canonical 훅 도구 이름 |
tool_use_id | string | 이 호출의 도구 호출 ID |
tool_input | JSON value | 도구별 입력. Bash와 apply_patch는 tool_input.command를 사용하고 MCP 도구는 전체 args를 보냄 |
stdout의 일반 텍스트는 무시됩니다.
stdout의 JSON은 systemMessage를 사용할 수 있고 다음 이벤트별 형태로 Bash 명령을 차단할 수 있습니다.
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Destructive command blocked by hook."
}
}
기존 차단 형태도 허용됩니다.
{
"decision": "block",
"reason": "Destructive command blocked by hook."
}
또는 exit code 2를 사용하고 차단 이유를 stderr에 쓸 수 있습니다.
permissionDecision: "allow"와 "ask", legacy decision: "approve", updatedInput, additionalContext, continue: false, stopReason, suppressOutput은 파싱되지만 아직 지원되지 않으므로 fail open됩니다.
PermissionRequest
PermissionRequest는 shell escalation이나 관리형 네트워크 승인처럼 Codex가 승인을 요청하기 직전에 실행됩니다. 요청을 허용하거나 거부하거나, 결정하지 않고 일반 승인 prompt를 계속 표시하게 할 수 있습니다. 승인이 필요하지 않은 명령에는 실행되지 않습니다.
matcher는 tool_name과 matcher alias에 적용됩니다. 현재 canonical 값에는 Bash, apply_patch, mcp__server__tool 같은 MCP 도구 이름이 포함되며, apply_patch는 Edit와 Write에도 일치합니다.
공통 입력 필드에 더해 다음 필드가 제공됩니다.
| 필드 | 타입 | 의미 |
|---|---|---|
turn_id | string | Codex 확장: 활성 Codex 턴 ID |
tool_name | string | Bash, apply_patch, mcp__fs__read 같은 canonical 훅 도구 이름 |
tool_input | JSON value | 도구별 입력. Bash와 apply_patch는 tool_input.command를 사용하고 MCP 도구는 전체 args를 보냄 |
tool_input.description | string 또는 null | Codex가 보유한 사람이 읽는 승인 이유 |
stdout의 일반 텍스트는 무시됩니다.
요청을 승인하려면 다음을 반환합니다.
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow"
}
}
}
요청을 거부하려면 다음을 반환합니다.
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "deny",
"message": "Blocked by repository policy."
}
}
}
일치하는 훅이 여러 결정을 반환하면 어떤 deny든 우선합니다. 그렇지 않으면 allow가 승인 prompt를 표시하지 않고 요청을 진행시킵니다. 일치하는 훅이 아무 결정도 하지 않으면 Codex는 일반 승인 흐름을 사용합니다.
PermissionRequest에는 updatedInput, updatedPermissions, interrupt를 반환하지 마십시오. 이 필드는 향후 동작을 위해 예약되어 있으며 현재는 fail closed됩니다.
PostToolUse
PostToolUse는 Bash, apply_patch, MCP 도구 호출 등 지원 도구가 출력을 만든 뒤 실행됩니다. Bash의 경우 명령이 0이 아닌 상태로 종료되어도 실행됩니다. 이미 실행된 도구의 side effect를 되돌릴 수는 없습니다.
아직 모든 shell 호출을 가로채지는 못하고 단순한 호출만 대상으로 합니다. 더 새로운 unified_exec 메커니즘은 풍부한 streaming stdin/stdout 처리를 지원하지만, interception은 아직 완전하지 않습니다. 마찬가지로 WebSearch나 shell/MCP가 아닌 다른 도구 호출은 가로채지 않습니다.
matcher는 tool_name과 matcher alias에 적용됩니다. apply_patch 파일 편집에는 apply_patch, Edit, Write를 사용할 수 있으며 훅 입력의 tool_name은 계속 "apply_patch"입니다.
공통 입력 필드에 더해 다음 필드가 제공됩니다.
| 필드 | 타입 | 의미 |
|---|---|---|
turn_id | string | Codex 확장: 활성 Codex 턴 ID |
tool_name | string | Bash, apply_patch, mcp__fs__read 같은 canonical 훅 도구 이름 |
tool_use_id | string | 이 호출의 도구 호출 ID |
tool_input | JSON value | 도구별 입력. Bash와 apply_patch는 tool_input.command를 사용하고 MCP 도구는 전체 args를 보냄 |
tool_response | JSON value | 도구별 출력. MCP 도구에서는 MCP 호출 결과 |
stdout의 일반 텍스트는 무시됩니다.
stdout의 JSON은 systemMessage와 다음 이벤트별 형태를 사용할 수 있습니다.
{
"decision": "block",
"reason": "The Bash output needs review before continuing.",
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "The command updated generated files."
}
}
additionalContext 텍스트는 추가 developer context로 더해집니다.
이 이벤트에서 decision: "block"은 완료된 Bash 명령을 되돌리지 않습니다. 대신 Codex는 feedback을 기록하고 도구 결과를 해당 feedback으로 바꾼 뒤, 훅이 제공한 메시지에서 모델을 계속 진행시킵니다.
또는 exit code 2를 사용하고 feedback 이유를 stderr에 쓸 수 있습니다.
명령이 이미 실행된 뒤 원래 도구 결과의 일반 처리를 멈추려면 continue: false를 반환합니다. Codex는 도구 결과를 사용자의 feedback 또는 stop 텍스트로 대체하고 거기서 계속합니다.
updatedMCPToolOutput과 suppressOutput은 파싱되지만 아직 지원되지 않으므로 fail open됩니다.
UserPromptSubmit
이 이벤트에서는 현재 matcher가 사용되지 않습니다.
공통 입력 필드에 더해 다음 필드가 제공됩니다.
| 필드 | 타입 | 의미 |
|---|---|---|
turn_id | string | Codex 확장: 활성 Codex 턴 ID |
prompt | string | 전송 직전의 사용자 프롬프트 |
stdout의 일반 텍스트는 추가 developer context로 더해집니다.
stdout의 JSON은 공통 출력 필드와 다음 이벤트별 형태를 지원합니다.
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "Ask for a clearer reproduction before editing files."
}
}
additionalContext 텍스트는 추가 developer context로 더해집니다.
프롬프트를 차단하려면 다음을 반환합니다.
{
"decision": "block",
"reason": "Ask for confirmation before doing that."
}
또는 exit code 2를 사용하고 차단 이유를 stderr에 쓸 수 있습니다.
Stop
이 이벤트에서는 현재 matcher가 사용되지 않습니다.
공통 입력 필드에 더해 다음 필드가 제공됩니다.
| 필드 | 타입 | 의미 |
|---|---|---|
turn_id | string | Codex 확장: 활성 Codex 턴 ID |
stop_hook_active | boolean | 이 턴이 이미 Stop으로 계속 진행되었는지 여부 |
last_assistant_message | string 또는 null | 사용 가능한 최신 assistant 메시지 텍스트 |
Stop은 exit code 0으로 종료할 때 stdout에 JSON을 기대합니다. 이 이벤트에서는 일반 텍스트 출력이 유효하지 않습니다.
stdout의 JSON은 공통 출력 필드를 지원합니다. Codex를 계속 진행하려면 다음을 반환합니다.
{
"decision": "block",
"reason": "Run one more pass over the failing tests."
}
또는 exit code 2를 사용하고 계속 진행할 이유를 stderr에 쓸 수 있습니다.
이 이벤트에서 decision: "block"은 턴을 거부하지 않습니다. 대신 Codex에 계속 진행하라고 지시하고, reason을 새 사용자 프롬프트 텍스트로 사용하는 새 continuation prompt를 자동 생성합니다.
일치하는 Stop 훅 중 하나라도 continue: false를 반환하면 다른 Stop 훅의 continuation 결정보다 우선합니다.
스키마
정확한 현재 wire format이 필요하면 Codex GitHub 저장소의 생성된 스키마를 참고하십시오.