Windows 이식에서 바뀐 게이트 구현
같은 게이트를 Windows에서 돌리며 바꾼 파서 구조, 경로와 리다이렉트 처리, 그리고 완화한 임계값
게이트는 맥에서 만들었다. 학습용으로 쓰던 Windows PC에도 Claude Code가 깔려 있어서 같은 규칙을 그쪽에도 걸기로 했다. 파일을 복사하자 셸 환경의 차이부터 드러났다.
기본 환경에 없던 sh
훅은 sh 스크립트다. 내가 쓰던 Windows 환경에는 sh가 기본으로
설치되어 있지 않았다. Git for Windows에 포함된 Git Bash의 sh를 사용했고,
훅 설정에서는 스크립트를 직접 실행하지 않고 sh에 넘겼다.
{
"type": "command",
"command": "sh \"$HOME/.claude/hooks/orch-file-gate.sh\"",
"timeout": 20
}
맥에서는 스크립트에 실행 권한을 주고 경로만 적으면 됐다. 내가 시험한
Windows 환경에서는 같은 방식이 동작하지 않았다. sh를 앞에 붙인 설정은
두 환경에서 모두 실행됐다.
파서를 파일로 떼어내다
맥 판은 셸 스크립트 하나에 파싱까지 다 들어 있었다. jq가 있으면 jq
필터를, 없으면 열 줄 남짓한 파이썬을 python3 -c의 인자로 넘기는 구조다.
셸 안에 다른 언어를 문자열로 넣으니 따옴표가 겹쳤고, 수정할 때마다 셸과
파이썬 중 어느 쪽에서 구문이 깨졌는지 확인해야 했다.
Windows 판에서는 판정을 파이썬 파일로 떼어냈다. 셸 스크립트는 킬스위치를 보고, 파이썬을 부르고, 돌아온 판정에 따라 종료 코드를 정하는 일만 한다. 둘 사이는 한 줄짜리 판정으로 주고받는다.
ALLOW subagent 호출이 서브에이전트 안에서 났다
ALLOW noncode 대상이 코드 파일이 아니다
BLOCK size <n> 단일 쓰기가 줄 수 제한을 넘었다
BLOCK badinput 입력이 깨졌다
COUNT <sid> 메인 세션의 코드 쓰기다. 셸이 카운트한다
분리한 뒤에는 판정 로직을 파이썬 파일에서 직접 읽고 고칠 수 있었다. 정규식이 많은 Bash 게이트도 셸의 인용 규칙과 분리해 다뤘다.
인터프리터를 찾는 순서도 바꿨다. 맥에서는 jq를 먼저 봤는데, Windows에는
jq가 없고 파이썬은 있을 가능성이 높다. python3, python, jq 순으로
찾고 셋 다 없으면 차단한다. Windows의 파이썬은 실행 파일
이름이 python인 경우가 많아 두 이름을 모두 본다.
같은 뜻의 다른 이름
Bash 게이트는 리다이렉트를 파일 쓰기로 본다. 다만 출력을 버리는
리다이렉트는 파일 쓰기가 아니므로 예외로 둔다. 맥 판이 예외로 둔 이름은
/dev/null, /dev/stdout, /dev/stderr 셋이다. Windows에서 같은 자리에
쓰는 이름은 NUL이라 목록에 넣었다.
if target.lower() in ("/dev/null", "nul"):
continue
이식 과정에서 예외 목록은 오히려 좁아졌다. NUL을 넣으면서
/dev/stdout과 /dev/stderr이 빠졌고, 그 둘로 보내는 명령은 Windows 판에서
파일 쓰기로 걸린다.
경로 구분자도 갈렸다. 맥 판은 경로 전체에 확장자 정규식을 걸었지만, 파이썬 판정기는 경로에서 파일 이름을 먼저 떼어 낸다. Windows 경로는 역슬래시로 들어오므로 이름을 떼기 전에 슬래시로 바꿔야 한다.
Codex 실행 파일을 알아보는 조건도 넓혔다. Codex 호출은 위임 경로라 항상
통과시켜야 하는데, Windows에서는 codex.exe로 들어오고 전체 경로가
붙기도 한다.
if re.match(r"\s*(\S*[/\\])?codex(\.exe)?(\s|$)", cmd) or "codex-companion.mjs" in cmd:
allow()
각 차이를 놓치면 게이트의 판정이 달라진다. NUL을
모르면 로그를 버리는 명령이 전부 파일 쓰기로 걸려 아무것도 못 하게 되고,
역슬래시를 모르면 코드 파일이 코드로 인식되지 않아 카운트를 빠져나간다.
100줄에서 400줄로
임계값도 손봤다. 단일 쓰기 제한을 100줄에서 400줄로 올렸고, 코드로
취급하는 확장자 목록을 다시 짰다. 맥 쪽 목록은 카메라 앱을 만들며 정한
것이라 Objective-C와 셰이더 확장자가 들어 있었다. Windows에서는 Lua로
게임을 만들고 있어서 그 목록이 맞지 않았다. 쓰지 않게 된 m, mm,
metal, glsl을 빼고 필요한 확장자를 더해 스물여섯 개에서 서른네 개가
됐다.
lua c h cc cpp hpp cs js jsx mjs cjs ts tsx py rb go rs java kt swift
sh bash zsh ps1 psm1 php pl sql zig vue svelte html css scss
100줄 제한은 맥에서 자주 걸렸다고 기억한다. 큰 파일 하나를 통째로 쓰는 정당한 작업까지 막혀서 그때마다 위임하거나 킬스위치를 켜야 했다. 400줄로 올린 뒤에는 그 상황을 덜 만났다. 걸린 횟수를 세어 두지는 않았다.
값은 환경 변수로도 바꿀 수 있게 뺐다.
GATE_LIMIT="${ORCH_EDIT_LIMIT:-2}"
GATE_MAX_LINES="${ORCH_MAX_WRITE_LINES:-400}" # 0 disables the size sub-gate
두 환경을 비교해 찾은 차이
같은 규칙을 두 환경에 적용하자 오탐의 범위를 비교할 수 있었다. 맥에서만 재현되면 맥 설정을, 양쪽에서 재현되면 공통 규칙을 먼저 확인했다.
에이전트 정의도 이 무렵 두 번 고쳐 썼다. 처음 문안은 카메라 앱 전용이라 “이 프로젝트의 최고 추론 전문가” 같은 자리에 프로젝트 이름이 그대로 박혀 있었다. 사용자 스코프로 올리면서 저장소 이름을 뺀 문장으로 바꿨고, Windows 판에서는 오케스트레이션에서 이 에이전트가 맡는 일을 설명하도록 다시 썼다. 한 기계에서 쓰던 프로젝트 전용 표현도 이때 제거했다.
참고
Comments
아직 댓글이 없습니다. 첫 댓글을 남겨주세요.
검토 대기 중