여러 Claude Code로부터 Codex의 브라우저 조작을 순차적으로 보내기: Markdown과 zsh
요약
본 글은 여러 AI 에이전트(Claude Code)가 공유된 브라우저 자원(Codex의 Chrome 작업)을 순차적으로 사용해야 할 때 발생하는 동기화 문제를 해결하는 방법을 제시합니다. 모든 주체가 볼 수 있는 Markdown 형식의 '순서표'를 도입하여, 각 요청은 바로 앞 줄이 '완료' 상태일 때만 작업을 시작하도록 제어합니다.
핵심 포인트
- 공유 자원 사용 시 순차적 실행을 위한 중앙 집중식 큐(Queue) 시스템 구축
- Markdown 파일에 전용 형식으로 작업 순서와 상태를 기록하여 모든 주체가 공유
- zsh 스크립트를 활용하여 현재 줄의 완료 여부를 확인하고 다음 작업을 진행하는 로직 구현
- 번호 체계(예: 14a, 14b)를 사용하여 기존 순서를 유지하며 새로운 요청을 추가할 수 있음
CHIERO 주식회사 치에로입니다.
저희 회사에서는 영상 제작이나 기사 제작 등 업무 계통별로 Claude Code 세션을 나누어 사용하고 있습니다.
여기서 이 계통을 '선(線)'이라고 부릅니다.
각 세션은 화면 조작이 필요할 때 하나의 Codex에게 Chrome 작업을 요청합니다.
사용하는 Chrome은 공통적이기 때문에, 여러 개의 요청이 겹치면 작업에 실패할 수 있습니다.
지난 글에서는 요청에 .chrome이라는 표시를 하고, 사용 중일 경우 CODEX_BUSY를 반환하는 방법을 작성했습니다.
이렇게 해도 전송을 기다리게 할 수는 있지만, 다음에 어떤 '선'이 보낼지는 결정할 수 없습니다.
비어 있을 때 먼저 보낼 수 있는 '선'이 계속 진행되어, 마감 기한이 있는 영상 교체가 뒤로 밀리는 경우도 발생합니다.
2026년 10월 12일에는 매일 19:10에 올릴 예정이었던 X(구 트위터) 게시물이 20:36까지 올라오지 않았습니다.
당일 기록을 보면, Chrome이 비어있지 않아 5분 간격으로 전송 시도가 BUSY 상태였음이 남아 있었습니다.
다른 송신 주체는 1분 간격으로 재시도하는 경우도 있어, 기다리는 간격을 정하는 것만으로는 순서를 조정할 수 없었습니다.
그래서 모두가 볼 수 있는 Markdown 형식의 순서표를 하나 두었습니다.
송신 주체는 표의 순서에 따라 자신의 바로 앞 줄이 '완료' 상태가 된 후에 전송합니다.
사람도 AI도 같은 표를 읽고, 마감 기한에 맞춰 줄의 위치를 바꿉니다.
아래는 10월 12일~13일 운영 기록과 10월 15일에 확인한 스크립트 발췌본입니다.
표 파일명은 CHROME_QUEUE.md입니다.
각 줄에는 번호, 선, 요청 이름, 상태를 이 순서로 작성합니다.
다음은 실제 5줄의 예시입니다.
선과 요청 이름을 일반화하고, 중간의 다른 줄과 긴 보충 설명은 생략했습니다.
상태는 읽은 시점의 '완료' 상태 그대로입니다.
14a|영상 담당|video_upload_1013|완료
14b|영상 담당|video_description_1013|완료
14c|영상 담당|video_upload_1014|완료
...
Markdown 파일에는 전각의 |로 구분된 줄을 작성합니다.
반각의 |를 사용하는 Markdown 표준 표와는 형식이 다릅니다.
나중에 올릴 정규 표현식도 이 전각 구분자를 읽습니다.
video_upload_1013의 결과는 video_upload_1013.done.md에 작성하도록 합니다.
이는 요청서, 전송 기록, 결과를 같은 요청 이름으로 대응시키기 위함입니다.
상태란에는 '대기', '실행 중', '완료'의 3가지 단어로 한정했습니다.
게시물 URL이나 도중에 멈춘 이유는 결과 파일과, 선들 간에 공유하는 메모 파일(기록 수납함)에 작성합니다.
번호는 줄을 찾는 표시입니다.
14a와 같은 가지 번호를 사용하면 기존 번호를 재설정하지 않고도 작업을 추가할 수 있습니다.
송신 주체는 숫자의 크기를 비교하지 않고, 파일 위에서 읽어 바로 앞줄을 확인합니다.
줄의 위치를 바꾸면, 그 다음에 순서를 확인할 송신 주체가 기다리는 상대도 바뀝니다.
각 줄에 zsh 송신 주체를 하나 배치했습니다.
주로 소개할 실제 예시를 여기서는 queue_runner.sh라고 부릅니다.
이 스크립트는 줄의 번호와 요청 이름(argument)을 받아, 순서 확인, 전송, 결과 기록을 수행합니다.
실제로는 한 줄에 여러 요청을 모아 인자 순서대로 하나씩 보낼 수도 있습니다.
다음은 이전 줄을 보는 prev_done과 자신의 상태를 변경하는 mark의 발췌본입니다.
실제 함수의 코드는 개행과 들여쓰기를 정리하여 첨부했습니다.
변수 정의는 생략했습니다.
$ROW는 자신의 줄 번호, $Q는 순서표 경로입니다.
prev_done() {
ROW="$ROW" perl -CSD -Mutf8 -ne '
BEGIN {
...
prev_done은 번호로 시작하는 줄만 읽습니다.
제목이나 빈 줄을 건너뛰고, 자신의 줄에 도달할 때까지 바로 앞 줄을 $prev에 기억합니다.
이전 줄이 '완료'라면 종료 코드는 0, 대기 또는 실행 중이라면 1, 자신의 줄이 없으면 2입니다.
자신이 첫 번째 작업이라면, 이전 줄이 없으므로 0을 반환합니다.
호출하는 쪽은 0이 될 때까지 1분 간격으로 확인합니다.
2인 경우에는, 줄을 찾지 못했다는 내용을 로그에 기록하며 기다립니다.
표에는 일본어와 전각 구분자가 있으므로, Perl로 문자 처리를 지정했습니다.
처음에는 macOS의 sed로 수정했지만, 전각 '완료' 표시를 할 수 없어서 Perl로 변경했습니다.
-CSD는 표준 입출력과 파일 입출력을 UTF-8로 처리하며, -Mutf8은 코드 내의 일본어를 다루도록 지정합니다.
환경 변수에서 전달하는 번호와 상태에는 decode를 사용하고 있습니다. 이 지정의 의미는 Perl 공식 문서인 perlrun과 utf8에서 확인할 수 있습니다.
이러한 지정들은 입력 파일이 올바른 UTF-8인지 검사하는 것은 아닙니다.
mark 실행 중은 자신의 줄 끝만 덮어씁니다. 전송을 시도하기 직전에 '실행 중'으로 설정하고, BUSY가 반환되면 즉시 '대기'로 되돌리고, 해당 줄의 처리가 끝나면 '완료'로 만듭니다.
이 표의 '완료'는 그 줄의 순서를 마쳤다는 의미입니다. 작업이 도중에 멈춰 있어도 '완료'로 만드는 경우가 있으므로, 성공 여부는 done.md 등의 본문에서 확인합니다.
전송할 차례가 온 후에도, 다른 라인에 할당된 시간대는 대기합니다. 이 전송 담당자는 12:20부터 13:00 직전까지는 전송하지 않습니다.
또한, Mac의 메모리 여유율이 12% 미만이거나, 빈 디스크가 6GB 미만이면 1분 기다린 후 다시 확인합니다. 사용하고 있는 memory_pressure와 df -g는 macOS 명령어입니다. 이 Mac에서 정한 기준의 경위는 이전 기사에 작성되어 있습니다.
다음은 그 확인부터 전송까지의 루프입니다.
$D는 요청과 결과가 놓이는 곳, $N은 요청명, $REQ는 요청서입니다.
say는 로그에 날짜와 문장을 쓰는 함수로, 정의는 생략했습니다. 전송 대장(送信台帳)에 쓸 담당명만 일반적인 표현으로 바꿨습니다.
while true; do
HM=$(date +%H%M)
if [ $HM -ge 1220 ] && [ $HM -lt 1300 ]; then sleep 60; continue; fi
...
send_codex.sh는 자체 전송 스크립트입니다. 이 루프가 전제하는 것은, Chrome이 사용 중일 때 출력에 CODEX_BUSY를 포함하여 반환하는 것뿐입니다.
Chrome 사용 여부를 조사하고, 비어 있으면 Codex에 요청을 전달하고, 결과나 정지 알림을 기다립니다. CODEX_BUSY인 경우에는 자신의 줄을 '대기'로 되돌리고 1분 후에 재시도합니다. 이전 기사의 표시를 통한 확인 역시 이 전송 직전에도 사용합니다.
이 전송 담당자에게는, 이 루프보다 앞에 요청서의 유무와 재전송 확인 과정이 있습니다. 이미 done.md가 있는 요청은 보내지 않습니다. 전송 대장에 이름이 있는데 done.md가 없는 경우도 기록을 남기고 전송을 보류합니다. 결과 파일이 없다는 이유만으로 같은 작업을 다시 요청하지 않기 위함입니다.
실물을 읽어보면, 비어있는 값이 얻어지지 않았을 때의 처리는 담당자마다 다릅니다. 위의 ${FREE:-100}와 ${DISK:-100}는 값이 비어있으면 100으로 판정합니다. 다른 한 개의 전송 담당자는 기본값이 0이므로, 값에 얻지 못해도 기다립니다.
측정 실패 시까지 전송을 멈추려면, 위의 발췌문에는 그 변경이 필요합니다.
한 건만 보내는, 또 다른 실물을 여기서는 queue_send_one.sh라고 부릅니다. 이것은 줄 번호, 요청명, 요청서 세 가지를 받습니다. 시간대 제한이나 여러 요청을 처리하는 루프는 없습니다.
다음은 전송 스크립트에서 돌아온 후의 처리입니다. $NAME은 요청명, $CS는 Codex에 작업을 수행하는 보조 스크립트, $W는 작업 디렉토리입니다. $W는 실물의 절대 경로를 대체한 것이며, 각 변수의 정의는 생략했습니다.
if [ ! -f "$D/$NAME.done.md" ]; then
J=$(cd "$W" && node $CS status 2>/dev/null | grep "running" | grep -o "task-[a-z0-9-]*" | head -1)
[ -n "$J" ] && (cd "$W" && node $CS cancel $J >/dev/null 2>&1)
...
이전 기사의 실패 1에 적었듯이, 전송 담당자 프로세스를 멈춰도 Codex 내부의 작업이 남아있는 경우가 있었습니다. 그래서 done.md가 없는 경우에는 status에서 실행 중인 ID를 가져와 cancel을 보내도록 했습니다. 요청명, 종료 코드, 대상 ID는 stalled.md
기록하고, 그 줄을 '완료'로 만듭니다.
다시 시작해야 할 작업은 기록을 읽고서 별도로 처리합니다.
이 발췌본은 실행 중인 목록에서 첫 번째 항목을 선택했습니다.
요청명과 ID의 대조나 취소 후 상태 확인도 하지 않았습니다.
다른 작업이 동시에 작동하는 환경으로 옮길 때는 대상 요청의 ID를 특정하여 종료를 확인하는 처리가 필요합니다.
표의 '완료'나 done.md 파일의 존재만으로는 Codex의 처리 완료까지 확인할 수 없습니다.
queue_runner.sh는 각 요청 후에 90초 동안 기다리지만, 이 고정 시간도 종료 확인과는 별개입니다.
결과가 담긴 done.md가 있어도 표의 상태가 업데이트되지 않아 뒤쪽 작업이 대기하는 경우가 있었습니다.
그래서 queue_reconcile.sh라는 정합 담당 스크립트를 1분마다 실행하도록 했습니다.
실행 방법은 예를 들어, for i in {1..720}; do zsh queue_reconcile.sh; sleep 60; done를 백그라운드에서 한 번 돌리는 형태입니다.
표에서 요청명을 추출하고, 대응하는 done.md가 있으면 상태를 '완료'로 만듭니다.
다음은 변수 정의와 주석을 제외한 실제 코드입니다.
$D와 $Q는 지금까지와 같은 의미입니다.
-CSD -Mutf8 -i -pe 'if (/^([0-9]+[a-z]?)|([^|]*)|(.*)|(待ち|実行中)\s*$/) {my ($row,$line,$body)=($1,$2,$3);...```
한 줄에 여러 요청명이 있으면, 추출된 모든 이름의 `done.md`가 필요합니다.
요청명을 추출하지 못한 줄은 그대로 남겨둡니다.
마지막 한 줄은 '완료(보충)'을 '완료'로 되돌리는 처리로, 다음 실패를 거치며 추가한 것입니다.
대상은 전각 괄호뿐입니다.
이 정합 담당 스크립트에도 이름 표기 방식에 대한 의존성이 있습니다.
정규식은 `video_upload_1013`처럼 끝 부분이 네 자리 숫자인 부분을 추출합니다.
`video_upload_1013b` 같은 이름은 `b` 앞까지를 잡아버립니다.
그 이름을 사용할 경우에는 요청명 추출 방식을 수정해야 합니다.
또한, `done.md`의 내용은 읽지 않고 파일 유무만으로 판정합니다.
**1. 상태란에는 세 단어만 기재한다.**
10월 12일 밤부터 13일에 걸쳐, 전달하는 사람이 상태에 괄호 설명을 추가했고, 다른 선이 계속 대기했습니다.
'완료(Zenn・Qiita・note 무료 기사)'와 같은 보충 설명이 붙으면, 줄 끝의 '완료'를 보는 판정에 일치하지 않습니다.
사람에게는 끝난 것처럼 보여도, 전달하는 사람은 이전 줄을 미완료로 판단합니다.
상태는 '대기/실행 중/완료'로 통일하고, 자세한 결과는 `done.md`와 기록 수납함에 적도록 했습니다.
정합 담당 스크립트는 남아있던 괄호 설명도 제거합니다.
**2. 표를 수정한 후 자신의 줄을 다시 읽는다.**
10월 13일 낮, 두 개의 세션이 몇 분 사이에 각기 다른 줄을 표의 끝으로 옮겼습니다.
그 도중에 표를 동시에 수정한 형태가 되어, 한 줄이 사라졌습니다.
저장 순서까지는 기록하지 않았습니다.
나중에 저장한 쪽이 이전 변경 사항을 포함하지 않은 내용으로 덮어썼다고 생각합니다.
이때는, 수정 후 수십 초를 두고 표를 다시 읽어보면서 자신의 줄이 없는 것을 깨닫고 다시 넣었습니다.
다음부터는, 수정한 후에 반드시 다시 읽어보고 자신의 줄이 남아있는지, 줄 수를 확인합니다.
이 확인은 덮어쓰기에 알아차리기 위한 것입니다.
게재된 스크립트에는 표의 동시 업데이트를 막는 잠금장치가 없습니다.
정합 담당 스크립트도 표를 수정하기 때문에, 저장 직후 내용만 보고 안심할 수는 없습니다.
줄 수가 같아도 다른 줄이 사라질 수 있기 때문에, 자신의 줄 내용도 읽습니다.
**3. 잠시 보내지 않는 작업은 끝으로 옮긴다.**
10월 13일에는, 이용 가능한 칸의 사정으로, 어떤 작업을 15일까지 중단하게 되었습니다.
그 줄이 '대기' 상태로 중간에 있으면, 바로 다음 전달하는 사람은 전송할 수 없습니다.
더 뒤쪽의 줄도 이전 줄을 기다리기 때문에, 마감 기한이 있는 영상 교체까지 멈춥니다.
이때는, 보류하는 작업의 전달하는 것을 멈추고, 줄을 끝으로 옮겼습니다.
급하지 않은 작업도, 마감 기한이 있는 줄 뒤에 놓습니다.
**4. 다시 시작할 작업은 오늘 끝나는 줄 바로 다음에 놓는다.**
같은 날, 멈췄던 작업을 재개하기로 판단했습니다.
다만, 끝으로 계속 두면, 앞에 배열된 며칠 후 예약 작업까지 기다리게 됩니다.
각 전달하는 사람은 직전의 줄만 보지만, 그 직전의 줄도 더 이전의 줄을 기다리기 때문에, 대기가 연쇄됩니다.
그래서, 당일 진행하는 영상 교체 직후로 되돌아갔습니다.
보류할 때와 오늘 보낼 때는 놓는 장소를 바꿀 필요가 있었습니다.
이러한 재배치는 아직 순서를 기다리는 송신자에게 대한 조정입니다.
`queue_runner.sh`
은 이전 줄의 확인을 한 번 통과하면, 전송 루프 내에서는 다시 확인하지 않습니다.
이미 대기열을 빠져나간 송신자가 있는 경우에는, 그 상태도 확인한 후에 재배치합니다.
표를 놓으니, 다음에 보낼 작업과 그 작업을 기다리는 선이 보이게 되었습니다.
사람 또는 전체를 취합하는 세션이 행을 움직이면, 순서를 조정할 수 있습니다.
송신자는 표의 배열을 보기 때문에, 번호를 다시 매길 필요도 없습니다.
마감일을 읽고 자동으로 재배열하는 처리는 넣지 않았습니다.
오늘 어떤 작업을 보낼지는 사람 또는 취합 세션이 판단하여 표에 반영합니다.
상태란의 형식과, 보류되는 행의 위치는 규칙으로 정했습니다.
수정한 후 다시 읽어보는 것은, 행이 사라진 날에는 수동으로 했습니다. 스크립트에는 아직 넣지 않았습니다.
대기 시간이나 실패 횟수가 얼마나 변했는지는 측정하지 못해서, 숫자로 된 효과는 아직 쓸 수 없습니다.
사용하기 시작할 때는, 의뢰명과 결과 파일명을 대응시키고, 각 송신자가 같은 상태의 세 단어를 읽도록 합니다.
중단된 결과도 별 파일에 남기고, 무엇을 재개할지 확인합니다.
-
**ai-setup-guide**(무료): Mac에 Claude Code, Codex, 제작 도구가 들어있는지 목록화하는 환경 점검과, 설치 방법을 안내합니다.
https://github.com/kounkt/ai-setup-guide -
**제작 일체**(note): 이 표로 Codex에 흘려보내는 작업 중, 이미지·LP·YouTube의 제작 방법을 절차와 도구 세트로 만든 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기