스케줄러 에이전트 관리 가이드
프로젝트(CSN)별로 등록된 자동화 실행기(gissue 스케줄러 등 백그라운드 에이전트)의 온라인/오프라인 상태, 실행 상태·이력, 실시간 로그를 한 화면에서 관리합니다.
🤖 AI 에게 맡겨 gissue 스케줄러 만들기
이 문서를 AI 에게 주면, AI 가 giip-fde-agent 저장소(https://github.com/LowyShin/giip-fde-agent.git)를 clone하거나 최신화한 뒤 gissue 처리용 스케줄러를 설치·등록·검증까지 자동으로 처리합니다. 처음부터 스크립트를 새로 짜지 말고 이 절차를 사용합니다.
프롬프트(복사해서 그대로 사용):
이 문서(/help/catquest-schedulers.ko.md)를 읽고, 아래 '설치 절차'대로 gissue 스케줄러를 이 PC 에 설치해줘.
giip-fde-agent 가 없으면 clone, 있으면 git pull --ff-only 로 최신화. 로컬 변경이 있으면 덮어쓰지 말고 중단하고 보고해.
대상 CSN 은 <CSN>, 작업 폴더는 <경로> 로 지정해.
SK 는 giipAgent.cnf(또는 giipAgent.cfg)에서 sk= 값을 읽고 나에게 묻지 마.
각 단계 확인 결과를 보고해.
설치 절차
- 사전점검 — 실행 →
git --version,node --version,claude --version, Windows 는powershell -Command $PSVersionTable.PSVersion, Linux 는pwsh --version. 모두 PATH 에 있어야 다음 단계로. - giip-fde-agent 확보 — 실행 → 없으면
git clone https://github.com/LowyShin/giip-fde-agent.git, 있으면git pull --ff-only. 로컬 변경이 있으면 중단하고 보고. - 정본 스펙 읽기 —
docs/60-operations/hourly-issue-scheduler.md§4~§5 를 읽고 CSN→workdir 매핑, heartbeat 블록, SK 위치를 확인. - csn-projects.json 작성 —
scripts/gissue/csn-projects.json.example을csn-projects.json으로 복사. 대상 CSN 의workdir/enabled: true채우고, 최상위heartbeat블록에lssn(서버번호)/hostname(이 PC 식별자)/skFile(sk= 형식 파일 경로) 필수로 지정. 없으면 이 화면에 행이 나타나지 않음. - 시크릿 준비 — 상위 폴더
giipAgent.cnf(또는giipAgent.cfg)에서sk=값을 읽음. 사용자에게 되묻지 않고, 출력에도 기록하지 않음.slack-bot/.secrets/giip-accounts.json(.sample.json 복사)도 준비. - 드라이런 —
scripts/gissue/manage-gissue-scheduler.ps1 -Action DryRun -OnlyCsn <CSN>실행 →[PREFLIGHT-FAIL]로그 없이 끝나면 성공. - 등록
- Windows —
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\register-hourly-issue-scheduler.ps1 -Action Register -RepoRoot <giip-fde-agent 루트> - Linux — 전용 등록 스크립트는 없고,
/etc/cron.d/gissue-scheduler같은 cron 한 줄로 등록합니다(실제 docker 배포와 같은 형식):7,27,47 * * * * <일반 사용자> cd <루트> && pwsh -NoProfile -NonInteractive -File "<루트>/scripts/gissue/run-gissue-claude.ps1" -OnlyCsn <CSN> >> <루트>/scripts/gissue/logs/cron.log 2>&1. 반드시 root 가 아닌 일반 사용자로 등록합니다(claude 가 root 에서--dangerously-skip-permissions를 거부해 이슈가 모두 실패함). 그 사용자의 claude 로그인 정보도 필요합니다. - Docker 인스턴스 — 컨테이너 기동 시
docker/entrypoint.sh가 위 cron 을 자동 등록합니다(GIIP_ENABLE_SCHEDULER기본 true,-OnlyCsn은csn-projects.json에서 파생). 이 경우 7단계는 이미 끝나 있으므로/etc/cron.d/gissue-scheduler존재만 확인하고scripts/bootstrap-instance.sh로 환경(pwsh 링크 등)을 점검합니다.
- Windows —
- 검증 —
manage-gissue-scheduler.ps1 -Action RunOnce -OnlyCsn <CSN>로 한 번 실행하거나 다음 :07 을 기다린 뒤 이 화면을 새로고침 →gissue_csn<CSN>행이 online 이고 History 에 실행이 남아 있으면 성공. 안 보이면scripts/gissue/logs/gissue_csn<CSN>.log의[WARN][SchedulerState-...]줄을 확인.
AI 가 하지 말아야 할 것
- giip-fde-agent 의 러너(
run-gissue-claude.ps1)를 새로 작성하지 않는다 - SP 를 직접 호출하는 코드를 따로 짜지 않는다(설치된 러너가 처리한다)
- SK 값을 문서나 로그에 남기지 않는다
- giip-fde-agent 코드 자체를 수정하지 않는다
📋 개요
스케줄러 에이전트 관리는 현재 선택된 프로젝트(CSN)에 등록된 모든 자동화 에이전트 — 예를 들어 gissue의 매시간 스케줄러, 대화형 Claude Code 세션 등을 보여줍니다. giip 이슈를 고양이로 시각화하는 /admin/catquest와 달리, 이 화면은 자동화 작업을 실행하는 에이전트 자체를 관리하는 운영 대시보드입니다: 각 에이전트가 살아있는지, 현재 실행이 정상인지, 로그는 어디서 볼 수 있는지를 확인합니다.
🎨 상태 색상
에이전트마다 서로 독립적인 두 가지 상태 신호가 표시됩니다 — 혼동하지 마세요.
Identity 상태 (에이전트 이름 옆 작은 점)
개별 실행(run)과 무관하게, 에이전트 프로세스 자체에 접근 가능한지를 나타냅니다(tSchedulerAgent.status).
| 색상 | 상태 | 의미 |
|---|---|---|
| 🟢 초록 | online | 에이전트에 접근 가능하며 정상 보고 중 |
| 🔴 빨강 | error | 에이전트가 오류 상태를 보고함 |
| ⚪ 회색(slate-400) | offline | 현재 에이전트에 접근할 수 없음 |
| ⚪ 연한 회색(slate-300) | unknown | 아직 상태가 보고되지 않음 |
실행 상태 (Status 컬럼의 배지)
가장 최근 스케줄러 *실행(run)*의 상태(currentStatus)와 stale(정체) 탐지 결과를 반영합니다.
| 색상 | 표시 라벨 | 조건 | 의미 |
|---|---|---|---|
| 🟢 초록 | RUNNING | currentStatus = RUNNING이고 stale 아님 | 정상적으로 실행 중 |
| 🔴 빨강 | STALE | currentStatus = RUNNING 이면서 isStale = true | 실행 중으로 표시돼 있지만 heartbeat가 너무 오래 끊김 — 멈춰버린/좀비 실행으로 보임 |
| 🟠 주황 | STALE | currentStatus = STALE(명시적 값) | 백엔드가 실행 자체를 stale로 판정함 |
| ⚪ 회색 | ENDED | currentStatus = ENDED | 종료됨(정상 완료 또는 정상적인 중지) |
| ⚪ 회색 | (원본 값) | 그 외 / 실행 이력 없음 | 알 수 없음 / 데이터 없음 |
아래 실행이력 모달에서는 완료된 실행이 빨간색 FAILED로도 표시될 수 있습니다 — ENDED(정상 종료)나 stale/타임아웃과는 구분되는, 오류로 종료된 실행입니다.
🔍 화면 구성
1. 상단 헤더
/admin/catquest로 돌아가는 뒤로가기 화살표(←).- Refresh: 현재 CSN의 에이전트 목록을 다시 불러옵니다.
2. Open Interactive Sessions
활성 상태인 claude_interactive_session 에이전트 — 즉 현재 호스트에서 실행 중으로 등록된 대화형 Claude Code 세션 — 이 하나 이상 있을 때만 별도 카드로 표시됩니다. 각 행은 세션 이름, 호스트, 마지막 통신 시각, 그리고 에이전트의 capabilities에 세션 URL이 등록돼 있을 때 표시되는 "Open session" 링크를 보여줍니다.
3. 에이전트 목록 (표)
스케줄러 에이전트당 한 행씩 표시되며, 다음 정보를 담습니다.
- Name — identity 상태 점, 에이전트 이름, OS/에이전트 유형/버전 배지,
lssn(연결된 서버), 잘린 capabilities 요약, 마지막 오류(있는 경우). - Host —
hostIdentifier. - Windows Task — 해당되는 경우, 이 에이전트를 실행하는 Windows 작업 스케줄러 작업 이름.
- Project — 프로젝트 이름.
- Active — ON/OFF 배지(
isActive). - Last Comm — 마지막 heartbeat/통신 시각.
- Status — 위에서 설명한 실행 상태 배지와, 보고된 경우 현재 단계(phase) 텍스트.
하단 통계 푸터는 목록에 표시된 모든 에이전트의 처리(Processed) / 스킵(Skipped) / 실패(Failed) 건수 합계를 보여줍니다.
4. 실행이력 (Run History)
표 아래 "View History" 카드에 모든 에이전트가 다시 나열되며, 행의 History 버튼을 클릭하면 해당 에이전트의 최근 실행 목록을 모달로 보여줍니다 — 실행 모드, 상태(FAILED 포함), 시작/종료 시각, 소요 시간, 처리/스킵/실패 건수, 단계, 요약.
5. 실시간 로그 뷰어 (Live Log Viewer)
두 영역으로 구성된 실시간 tailing 화면입니다.
- 좌측(에이전트 → 스트림 트리): 에이전트별로 그룹화되며, 각 에이전트를 펼치면 알려진 로그 스트림 목록이 나옵니다(스트림은
streamKey로 식별되며streamType과 로테이션 세대 번호를 가집니다). 에이전트는 마지막 통신 시각을 기준으로 online/offline이 표시됩니다. - 우측(로그 스트림 카드): 스트림을 선택하면 최근 로그 라인이 표시되고, 실시간 tailing을 위해 계속 갱신됩니다.
➕ 다른 호스트 추가하기
이 화면에는 호스트를 수동으로 "추가"하는 버튼이나 입력 폼이 없습니다 — 목록은 전적으로 아래 두 가지 자동 등록 경로의 결과입니다. 두 경로 모두 **CSN(프로젝트)별로 발급된 Secret Key(SK)**로 giipApiSk4 엔드포인트를 호출해 tSchedulerAgent/tSchedulerAgentRun에 자신을 등록(upsert)하는 방식이며, 유효한 SK 없이는 호출 자체가 401 Unauthorized로 실패하므로 아래 절차를 알아도 임의로 남의 프로젝트에 가짜 에이전트를 등록할 수는 없습니다.
⚠️ 아래 SK/AK 값은 항상 예시 플레이스홀더입니다. 실제 값은 절대 문서·코드에 하드코딩하지 말고, 발급받은 값은 각자 안전한 방식(시크릿 파일, 환경변수, 시크릿 매니저 등)으로 보관하세요.
공통 사전조건
| 항목 | 설명 |
|---|---|
| 대상 CSN | 에이전트를 등록할 프로젝트 번호. 이 페이지는 세션의 현재 CSN 기준으로 목록을 보여줍니다. |
| SK (Secret Key) | 해당 CSN에 대해 쓰기(등록) 권한을 가진 비밀키. 프로젝트 관리자에게 해당 CSN용 SK 발급을 요청하세요 — 이 화면이나 API로 스스로 발급받을 수 없습니다. |
| 등록 엔드포인트 | https://giipfaw.azurewebsites.net/api/giipApiSk4 (anonymous 인증 — Function key 불필요, 요청 폼의 SK 토큰으로 인증) |
콘솔을 이 화면에 등록하기 — 그대로 복사해서 콘솔에 붙여넣기
이 화면의 "Open Interactive Sessions" 에 지금 쓰는 Claude Code 콘솔이 보이지 않으면, 아래 요청문을 콘솔(Claude Code)에 그대로 붙여넣으면 됩니다. 콘솔이 아래 "방법 A" 의 규격(agentKey 규칙, SP 호출)만으로 등록을 수행하므로 별도 스크립트나 다른 저장소가 필요 없습니다. <CSN> 은 등록할 프로젝트 번호로 바꿔 넣으세요.
사전조건: 대상 CSN 용 SK 가 이 PC 의 상위 폴더
giipAgent.cnf(또는giipAgent.cfg)에sk=로 있어야 합니다(없으면 프로젝트 관리자에게 해당 CSN 용 SK 발급을 요청). 콘솔은 SK 를 사용자에게 되묻지 않고 그 파일에서 읽습니다.
요청문 1 — 지금 열린 이 콘솔을 한 번 등록
giip 스케줄러 에이전트 화면(/ko/admin/catquest/schedulers)에 지금 열려 있는 이 콘솔 세션을 CSN <CSN> 의 대화형 세션으로 한 번 등록해줘.
이 문서(/help/catquest-schedulers.ko.md)의 "방법 A — 대화형 Claude Code 세션 등록"을 읽고 그 규격 그대로 따라. register-interactive-session.ps1 같은 별도 스크립트는 없는 것으로 보고, 문서에 적힌 giipApiSk4 호출(HTTP POST, 폼 필드 text/token/jsondata)을 직접 실행해.
1. agentKey: 문서의 "agentKey 생성 규칙" 5단계대로 만들어. 원본 ID 는 이 세션의 session_id 를 알면 그 값, 모르면 fallback_<COMPUTERNAME>_<PID>. COMPUTERNAME 환경변수가 없으면 hostname 명령 결과를 써.
2. SchedulerAgentUpsert: text 는 "SchedulerAgentUpsert agentKey displayName hostIdentifier windowsTaskName projectName scheduleDesc isActive lssn osType agentType version". jsondata 는 agentKey, displayName="Claude Interactive Session (<호스트명>)", hostIdentifier=<호스트명>, windowsTaskName/projectName/scheduleDesc 는 빈 문자열, isActive="1", lssn="0", osType=Windows 또는 Linux, agentType="claude_interactive_session", version="". 환경변수 CLAUDE_CODE_BRIDGE_SESSION_ID 가 있으면 text 와 jsondata 맨 끝에 capabilities={"sessionUrl":"https://claude.ai/code/<그 값>"} 를 추가하고, 없으면 capabilities 를 통째로 빼. 다른 값으로 sessionUrl 을 만들어내지 마.
3. SchedulerAgentRunStart: text 는 "SchedulerAgentRunStart runIdKey agentKey executionMode totalIssueCount". runIdKey=<agentKey>_<현재시각 yyyyMMddHHmmss>, executionMode="interactive", totalIssueCount="0".
4. SK 는 상위 폴더의 giipAgent.cnf(없으면 giipAgent.cfg)의 sk= 값을 읽어 token 으로 써. 나에게 되묻지 말고, SK 값은 화면·로그·파일 어디에도 출력하거나 기록하지 마.
5. 확인: 같은 giipApiSk4 로 text "SchedulerAgentList csn", jsondata {"csn":"<CSN>"} 를 호출해 방금 만든 agentKey 의 행이 is_active=true, current_status=RUNNING 인지 확인해. 결과와 사용한 agentKey/runIdKey 를 알려줘(값 중 SK 는 제외).
요청문 2 — 앞으로 모든 세션이 자동 등록되게 훅 설정
이 PC 에서 Claude Code 세션이 열리고 닫힐 때마다 giip 스케줄러 에이전트 화면에 자동으로 등록/해제되도록 SessionStart/SessionEnd 훅을 설정해줘. 대상 CSN 은 <CSN>.
이 문서(/help/catquest-schedulers.ko.md)의 "방법 A — 대화형 Claude Code 세션 등록"을 읽고 그 규격만으로 직접 구현해. 다른 저장소에서 스크립트를 가져오려 하지 마.
1. 훅 처리 스크립트를 사용자 홈 아래 한 곳(예: ~/.claude/giip-console-hook.ps1)에 만들어. 인자로 Start 또는 End 를 받고, 표준입력으로 훅 이벤트 JSON 을 읽어. Windows 가 아니면 pwsh 나 bash 로 작성해도 돼.
2. 훅 등록은 문서의 JSON 대로 ~/.claude/settings.json(전역)의 hooks.SessionStart 와 hooks.SessionEnd 에 command 훅을 추가해. matcher 는 넣지 마. 이미 있는 hooks 항목은 덮어쓰지 말고 배열에 추가하고, 문서 JSON 의 timeout 값을 그대로 써.
3. 헤드리스 제외: 스크립트는 먼저 부모 프로세스를 거슬러 올라가 claude 프로세스의 커맨드라인에 -p 또는 --print 가 있으면 아무것도 하지 않고 종료해(문서의 헤드리스 판정). CLAUDE_CODE_BRIDGE_SESSION_ID 의 유무로 헤드리스를 판정하지 마.
4. Start: agentKey 는 문서의 "agentKey 생성 규칙"대로(원본 ID = stdin 의 session_id, 없으면 fallback_<COMPUTERNAME>_<PID>) 만든 뒤, SchedulerAgentUpsert(agentType="claude_interactive_session", isActive="1", 필드와 순서는 문서의 예시와 동일)와 SchedulerAgentRunStart(executionMode="interactive", totalIssueCount="0")를 호출해. runIdKey=<agentKey>_<현재시각 yyyyMMddHHmmss> 이고, End 에서 쓰도록 임시 폴더의 파일에 저장해. CLAUDE_CODE_BRIDGE_SESSION_ID 가 있을 때만 capabilities.sessionUrl 을 넣고, 없으면 capabilities 를 뺀 채 등록을 계속해.
5. End: 같은 규칙으로 agentKey 를 다시 계산해 SchedulerAgentRunEnd(status="ENDED", processedCount/skippedCount/failedCount="0")를 호출하고, 이어서 SchedulerAgentUpsert 를 isActive="0" 으로 호출해 오프라인으로 전환해.
6. 호출은 모두 giipApiSk4(text/token/jsondata)로 하고, 훅이 실패해도 세션이 막히지 않게 항상 종료코드 0 으로 끝내.
7. SK 값은 스크립트에 넣지 말고, 실행할 때마다 giipAgent.cnf(없으면 giipAgent.cfg)의 sk= 에서 읽도록 그 파일의 절대경로만 적어. 나에게 되묻지 말고, SK 값은 출력·로그·파일에 남기지 마.
8. 끝나면 만들거나 바꾼 파일의 경로 목록과, 새 세션에서 등록됐는지 확인하는 방법을 알려줘.
- 요청문 1 로 등록한 세션은 종료 훅이 없으므로, 닫을 때 해제하려면 콘솔에게 "알려준 agentKey/runIdKey 로 SchedulerAgentRunEnd 를 호출하고 SchedulerAgentUpsert 를 isActive=0 으로 호출해줘" 라고 요청하세요. 훅(요청문 2)을 쓰면 종료 시 자동으로 해제됩니다.
- 성공 확인: 화면 새로고침 시 "Open Interactive Sessions" 카드와 에이전트 목록에 세션이 나타납니다. 상세 규격은 아래 "방법 A" 를 참고하세요.
방법 A — 대화형 Claude Code 세션 등록
Claude Code 같은 대화형 AI 코딩 세션을 "Open Interactive Sessions" 카드에 나타나게 하려면, 세션의 SessionStart/SessionEnd 훅에서 아래 절차를 실행하도록 구성합니다.
- 사전조건: 위 공통 사전조건(대상 CSN용 SK) + 세션을 실행하는 도구(Claude Code 등)에 SessionStart/SessionEnd 훅을 등록할 수 있는 설정 권한.
- 세션 시작(SessionStart) 시 —
SchedulerAgentUpsert호출로 identity 등록/갱신 후SchedulerAgentRunStart호출로 실행 시작을 기록합니다. - 세션 종료(SessionEnd) 시 —
SchedulerAgentRunEnd호출로 실행 종료를 기록하고,SchedulerAgentUpsert를isActive=0으로 다시 호출해 오프라인으로 전환합니다.
agentKey 생성 규칙(그대로 적용, 판단 불필요): csn + agentKey 조합이 DB에서 UNIQUE 제약이므로, 같은 세션을 재시작해도 항상 같은 값이 나와야 합니다(그래야 재시작 시 새 행이 아니라 기존 행이 갱신됩니다). 아래 순서를 그대로 적용해 계산하십시오.
- 원본 ID: 훅 stdin JSON의
session_id필드 값이 있으면 그 값을 쓴다. 없으면fallback_<COMPUTERNAME 환경변수 값>_<현재 프로세스 PID>를 원본 ID로 쓴다. - 정규화: 원본 ID에서 영숫자(
a-z,A-Z,0-9)가 아닌 문자를 모두 제거한다(정규식 치환:[^a-zA-Z0-9]→''). - 자르기(1차): 정규화된 값이 24자를 넘으면 앞 24자만 남긴다.
- 조합:
agentKey = "claude_interactive_" + <COMPUTERNAME 환경변수 값> + "_" + <3단계 결과값> - 자르기(2차): 4단계 결과가 100자를 넘으면 앞 100자만 남긴다. 이 최종 값을
agentKey로 쓴다.
SessionStart/SessionEnd 훅 등록 위치(그대로 붙여넣을 JSON): Claude Code에서는 프로젝트 루트의 .claude/settings.json(해당 프로젝트에만 적용) 또는 사용자 홈의 ~/.claude/settings.json(모든 프로젝트에 전역 적용) 중 하나를 고른 뒤, 그 파일의 최상위에 아래 hooks 키를 병합하십시오. 파일에 이미 hooks 키가 있다면 SessionStart/SessionEnd 배열 안에 아래 객체를 추가하십시오(기존 항목을 덮어쓰지 마십시오). matcher 필드는 넣지 않습니다 — 이 훅은 모든 세션에 전역 적용됩니다.
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "<세션 시작 시 실행할 command. 예: powershell -NoProfile -ExecutionPolicy Bypass -File \"<등록 스크립트의 절대경로>\" -Action Start>",
"timeout": 15000
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "<세션 종료 시 실행할 command. 예: powershell -NoProfile -ExecutionPolicy Bypass -File \"<등록 스크립트의 절대경로>\" -Action End>",
"timeout": 15000
}
]
}
]
}
}
⚠️ 전역(matcher 없는) 훅으로 등록한 경우 헤드리스 오판정 주의(giip #1837): 위 훅은 matcher가 없어 이 호스트에서 실행되는 모든 claude 프로세스(대화형 세션뿐 아니라, 무인 스케줄러가
claude -p로 여는 헤드리스 실행까지 전부)에서 발화합니다. 그래서 등록 스크립트 안에 "지금 이 실행이 대화형인지 헤드리스인지" 판정 로직이 사실상 필수입니다. 이때$env:CLAUDE_CODE_BRIDGE_SESSION_ID(Remote Control 브릿지 연결 여부)를 판정 기준으로 쓰지 마십시오 — 이 값은 브릿지 연결 여부만 나타낼 뿐 헤드리스 여부와 무관하며, 진짜 대화형 세션도 아직/remote-control을 실행/연결하지 않았다면 이 값이 없을 수 있습니다(그런 세션까지 헤드리스로 오판해 등록 자체가 스킵되는 사고가 실제로 있었습니다 — giip #1837). 올바른 판정 방법: 이 훅을 실행하는 프로세스 자신의 부모를 거슬러 올라가 실제claude/claude.exe프로세스를 찾고, 그 프로세스의 커맨드라인에-p/function Test-IsHeadlessClaudeInvocation { try { $checkPid = $PID for ($i = 0; $i -lt 6; $i++) { $proc = Get-CimInstance Win32_Process -Filter "ProcessId=$checkPid" -ErrorAction Stop if (-not $proc) { return $false } if ($proc.Name -eq 'claude.exe') { $cmd = $proc.CommandLine if ($cmd -and ($cmd -match '(^|\s)(-p|--print)(\s|$)')) { return $true } return $false } $checkPid = $proc.ParentProcessId if (-not $checkPid -or $checkPid -eq 0) { return $false } } return $false } catch { return $false } }Windows가 아닌 환경에서는 동일한 원리(부모 프로세스의 커맨드라인에서
-p/
이 command는 표준입력(stdin)으로 훅 이벤트 JSON(session_id 등 포함)을 받습니다. 아래 이 문서에 적힌 SP 호출 스펙(호출 규약, 필드 목록, agentKey 생성 규칙)만으로 자기 완결적으로 구현 가능합니다 — 이 문서를 관리하는 운영자가 개인적으로 참고하는 구현체가 있더라도 그 저장소는 비공개라 다른 세션이 접근할 수 없으니, 코드를 어딘가에서 가져오려 하지 말고 이 문서의 스펙만으로 직접 작성하십시오.
이때 agentKey와 capabilities.sessionUrl은 서로 다른 값에서 나오는 별개의 ID라는 점에 주의하세요 — 이 둘을 혼동하면 "세션 ID가 없어서 URL을 못 만든다"는 착각에 빠져 등록 자체를 포기하게 될 수 있습니다:
agentKey— 훅 stdin의session_id(하니스 내부 UUID, 예:~/.claude/projects/*/<uuid>.jsonl파일명에 쓰이는 값) 기반으로 구성합니다. "재시작해도 같은 세션이면 같은 키가 나오게" 하기 위한 용도일 뿐, URL이 아닙니다.capabilities.sessionUrl— 있다면$env:CLAUDE_CODE_BRIDGE_SESSION_ID(예:session_01JGmSiTajXu9iZmjVZC27Nt같은 ULID 스타일 값 — 훅 stdin의session_id와는 값이 다른 별개의 ID)를 이용해https://claude.ai/code/<이 값>형태로 구성합니다.
이 환경변수는 모든 세션에 있는 게 아닙니다 — 예를 들어 claude.ai/code 브릿지를 거치지 않은 로컬 CLI 세션에는 없을 수 있습니다. $env:CLAUDE_CODE_BRIDGE_SESSION_ID가 없다면 sessionUrl을 만들 수 없다는 뜻이지, 등록이 막힌다는 뜻이 아닙니다 — 그럴 때는 capabilities 필드 자체를 호출에서 통째로 빼고 나머지 필드만으로 등록을 계속 진행하면 됩니다(아래 "sessionUrl을 만들 수 없을 때" 참고).
CLAUDE_CODE_BRIDGE_SESSION_ID가 언제 생기는지, 없을 때 사후에 얻을 수 있는지(공식 문서 https://code.claude.com/docs/en/remote-control 기준, 그대로 실행 가능한 절차):
이 환경변수는 세션이 Anthropic의 "Remote Control" 기능에 연결돼 있을 때만 존재합니다. 아래 조건을 하나라도 만족하지 못하면 원천적으로 얻을 수 없으며, 이는 결함이 아니라 정상 상태입니다 — 이 경우 다른 시도를 하지 말고 곧바로 capabilities를 생략하고 등록을 계속하십시오.
- 헤드리스(
claude -p) 세션에는 이 기능 자체가 없습니다. Remote Control은 대화형 세션 전용이므로, gissue 스케줄러처럼claude -p로 도는 무인 프로세스는 이 값을 절대 가질 수 없습니다. - API 키(
ANTHROPIC_API_KEY) 인증 세션은 Remote Control을 지원하지 않습니다. claude.ai 계정 로그인(claude실행 후/login)이 필요합니다. - Amazon Bedrock / Google Vertex / Microsoft Foundry 경유이거나,
ANTHROPIC_BASE_URL이api.anthropic.com외의 값(LLM 게이트웨이·프록시 등)으로 설정돼 있으면 지원되지 않습니다. DISABLE_TELEMETRY,DO_NOT_TRACK,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC,DISABLE_GROWTHBOOK중 하나라도 설정돼 있으면 지원되지 않습니다(설정돼 있다면 그 변수를 해제해야 함).
위 조건을 전부 만족하는 일반 대화형 세션인데도 이 변수가 없다면, 아직 Remote Control이 연결만 안 된 것뿐입니다. 세션을 재시작할 필요 없이, 그 세션 안에서 다음 명령을 실행하면 즉시 연결되고 그 순간부터 $env:CLAUDE_CODE_BRIDGE_SESSION_ID가 설정됩니다:
/remote-control
(/rc도 동일) 다음 세션부터 매번 자동으로 연결되게 하려면 세션 안에서 /config를 실행해 "Enable Remote Control for all sessions"를 켜거나, 사용자 홈의 ~/.claude/settings.json에 다음을 추가하십시오:
{
"remoteControlAtStartup": true
}
여기서도 절대 금지 (guide 147행 원칙과 동일): 위 조건을 만족하지 못해(헤드리스 실행, API 키 인증 등) CLAUDE_CODE_BRIDGE_SESSION_ID를 얻을 수 없는 경우, 훅 stdin의 session_id나 $env:CLAUDE_CODE_SESSION_ID 같은 다른 값으로 대체해 sessionUrl을 "만들어내려" 하지 마십시오 — 이 값들은 claude.ai/code의 실제 세션과 무관해 열리지 않는(404) 링크가 됩니다. 이 경우 유일한 정답은 capabilities를 생략하고 등록을 계속 진행하는 것이며, 별도 조치를 시도할 필요가 없습니다.
이 판단은 순전히 기계적입니다 — 사람에게 묻지 않습니다. 자신의 프로세스에서 $env:CLAUDE_CODE_BRIDGE_SESSION_ID 존재 여부만 확인해 분기하며, 어떤 값을 쓸지 사용자에게 선택지로 제시하거나 질문하는 것은 절대 금지입니다. 훅 stdin의 session_id(agentKey의 출처)를 sessionUrl로 재사용하는 것도 절대 금지입니다 — 서로 다른 ID이며 깨진/404 링크가 됩니다. 내부 도구 링크, 임의 URL 등 다른 값으로 sessionUrl을 대신 채우는 것도 금지입니다 — sessionUrl은 오직 claude.ai/code 브릿지 링크이거나, 아예 없는(생략된) 값이어야 합니다.
sessionUrl을 반드시 채우고 싶다면: 이 등록을 수행하는 세션 "자기 자신"이 Remote Control 브릿지에 연결돼 있어야 $env:CLAUDE_CODE_BRIDGE_SESSION_ID가 채워집니다. 이는 각 세션이 스스로 갖추는 상태이지, 다른 운영자의 PC 설정(예: 세션 시작 시 자동으로 브릿지에 연결하는 설정)에 의존할 수 없습니다 — 다른 사람의 PC 설정은 이 세션에 아무 영향도 주지 않습니다. 아직 연결 전이라면 /remote-control 명령을 실행하거나(대화형 세션 내에서) claude --remote-control/--rc 플래그로 다시 시작해 스스로 연결하십시오. 연결을 기다리는 동안에도 등록 자체(Upsert/RunStart)는 계속 진행해야 합니다 — 아래 "sessionUrl을 만들 수 없을 때(capabilities 생략)" 절의 방식대로 하고, 등록을 통째로 미루지 마십시오.
호출 규약(giipApiSk4 공통): HTTP POST, application/x-www-form-urlencoded 폼 필드 3개.
⚠️ 위치 기반 파라미터: 이 SP들(
pApiSchedulerAgentUpsertBySK등)은jsondata의 필드를 SP의 개별 위치 파라미터(@p0, @p1, ...)에 순서대로 바인딩합니다 — 중간 필드를 건너뛸 수 없습니다. 뒤쪽 필드(예:capabilities)까지 값을 채우려면 그 앞의 모든 필드를 함께 보내야 하며, 실제로 안 쓰는 필드는 안전한 자리값(예:windowsTaskName='',projectName='',scheduleDesc='',lssn='0',version='')으로 채웁니다. 정수형 파라미터(lssn등)에 빈 문자열을 보내면 변환 오류가 나므로 반드시 숫자 문자열을 써야 합니다. 다만capabilities는 이 필드 목록의 맨 끝에 있으므로, 값이 없으면(위에서 설명한$env:CLAUDE_CODE_BRIDGE_SESSION_ID부재 상황) 필드 자체를 통째로 생략해도 안전합니다 — 뒤에 이어지는 다른 필드가 없어 위치가 밀리지 않기 때문입니다. 위치 기반 제약은 어디까지나 중간 필드를 건너뛸 수 없다는 뜻이며, 끝 필드까지 무조건 채워야 한다는 뜻은 아닙니다.
| 폼 필드 | 값 |
|---|---|
text | "<SP이름> <필드1> <필드2> ..." (SP 이름은 BySK 접미사를 뺀 이름, 뒤에 이 호출에서 채울 필드명을 공백으로 나열) |
token | <YOUR_SECRET_KEY> (대상 CSN의 SK) |
jsondata | 필드명→값 매핑 JSON 문자열, 예: {"agentKey":"...","displayName":"..."} |
각 단계에서 실제로 채우는 필드:
| 단계 | SP 이름(text의 첫 토큰) | 주요 필드 |
|---|---|---|
| 시작 — identity 등록/갱신 | SchedulerAgentUpsert | agentKey(호스트+세션별 고유 식별자), displayName, hostIdentifier(호스트명), windowsTaskName, projectName, scheduleDesc, isActive=1, lssn, osType, agentType(예: claude_interactive_session), version, capabilities(JSON 문자열, 예: {"sessionUrl":"<세션을 열 수 있는 URL>"}) |
| 시작 — 실행(run) 시작 | SchedulerAgentRunStart | runIdKey(이 실행의 고유 키), agentKey, executionMode=interactive, totalIssueCount |
| 종료 — 실행(run) 종료 | SchedulerAgentRunEnd | runIdKey, agentKey, status=ENDED, processedCount, skippedCount, failedCount |
| 종료 — identity 오프라인 전환 | SchedulerAgentUpsert | agentKey, displayName, hostIdentifier, windowsTaskName, projectName, scheduleDesc, isActive=0 |
예시(PowerShell, 값은 모두 플레이스홀더):
$body = @{
text = 'SchedulerAgentUpsert agentKey displayName hostIdentifier windowsTaskName projectName scheduleDesc isActive lssn osType agentType version capabilities'
token = '<YOUR_SECRET_KEY>'
jsondata = (@{
agentKey = 'claude_interactive_<YOUR_HOST>_<UNIQUE_ID>'
displayName = 'Claude Interactive Session (<YOUR_HOST>)'
hostIdentifier = '<YOUR_HOST>'
windowsTaskName = ''
projectName = ''
scheduleDesc = ''
isActive = '1'
lssn = '0'
osType = 'Windows'
agentType = 'claude_interactive_session'
version = ''
capabilities = (@{ sessionUrl = '<세션 URL, 선택사항>' } | ConvertTo-Json -Compress)
} | ConvertTo-Json -Compress)
}
Invoke-WebRequest -Uri 'https://giipfaw.azurewebsites.net/api/giipApiSk4' -Method Post -Body $body
sessionUrl을 만들 수 없을 때(capabilities 생략): $env:CLAUDE_CODE_BRIDGE_SESSION_ID가 없어 capabilities를 구성할 수 없는 경우, 위 예시에서 text의 capabilities 토큰과 jsondata의 capabilities 필드만 빼고 나머지 11개 필드는 그대로 사용하면 됩니다. sessionUrl 없이도 등록 자체는 완전히 정상적으로 성공합니다 — 다만 "Open Interactive Sessions" 카드에 "Open session" 링크만 나타나지 않을 뿐입니다(아래 "문제 해결" 절 참고).
$body = @{
text = 'SchedulerAgentUpsert agentKey displayName hostIdentifier windowsTaskName projectName scheduleDesc isActive lssn osType agentType version'
token = '<YOUR_SECRET_KEY>'
jsondata = (@{
agentKey = 'claude_interactive_<YOUR_HOST>_<UNIQUE_ID>'
displayName = 'Claude Interactive Session (<YOUR_HOST>)'
hostIdentifier = '<YOUR_HOST>'
windowsTaskName = ''
projectName = ''
scheduleDesc = ''
isActive = '1'
lssn = '0'
osType = 'Windows'
agentType = 'claude_interactive_session'
version = ''
} | ConvertTo-Json -Compress)
}
Invoke-WebRequest -Uri 'https://giipfaw.azurewebsites.net/api/giipApiSk4' -Method Post -Body $body
다른 호스트에서 이 메커니즘을 재현하려면 다음 두 가지가 그대로 필요합니다(둘 다 고정 요구사항이며 재량의 여지가 없습니다): (a) 해당 호스트의 세션 실행 도구에도 위와 동일한 hooks.SessionStart/hooks.SessionEnd 구성, (b) 등록하려는 CSN에 대해 유효한 SK.
SK를 어디에 보관할지는(이 절차와 무관한) 순수 보안 재량 사항입니다 — 참고로 이 운영자의 개인 환경(lowyworkenv 저장소)에서는 이 SK를 slack-bot/.secrets/giip-accounts.json이라는 JSON 파일(Slack 채널ID → {login_id, sk, csn} 매핑 구조)로 보관하지만, 이는 참고용 예시 구조일 뿐입니다 — 이 특정 파일 경로를 그대로 따라 하라는 뜻이 아니며, 다른 호스트/운영자는 각자 안전한 방식(OS 자격 증명 저장소, 시크릿 매니저, 환경변수 등)으로 SK를 보관하면 됩니다.
성공 확인: 화면 새로고침 시 "Open Interactive Sessions" 카드에 세션이 나타납니다.
방법 B — 스케줄러 에이전트(자동화 실행기) 등록
gissue 스케줄러 같은 백그라운드 자동화 실행기를 등록하는 절차는 방법 A와 같은 SP 계열을 쓰되, 1회성이 아니라 주기적으로(heartbeat) 자신의 identity와 실행 상태를 갱신합니다.
표준 구현은 giip-fde-agent 입니다. 처음부터 짜지 말고 위 'AI 에게 맡겨 gissue 스케줄러 만들기' 절차를 따르십시오. 아래 SP 규격은 러너가 내부적으로 쓰는 호출이며, 직접 구현해야 할 때만 참고하십시오.
- 사전조건: 위 공통 사전조건(대상 CSN용 SK).
- 최초 등록/주기적 identity 갱신:
SchedulerAgentUpsert(방법 A와 동일 필드 —agentKey/displayName/hostIdentifier/osType/agentType/version/capabilities등)를 프로세스 시작 시 1회 호출하고, 그 뒤로는 최대 5분 간격(권장: 2분 간격)으로 반복 호출합니다. - 실행(run) 시작:
SchedulerAgentRunStart(runIdKey,agentKey,executionMode=scheduled,totalIssueCount). - 실행 도중 진행 상황 갱신(heartbeat):
SchedulerAgentHeartbeatPut—runIdKey,agentKey,processedCount,skippedCount,failedCount,currentPhase,currentIssueNum,totalIssueCount. 처리 단위(예: 이슈 1건 처리 완료)마다 호출하고, 처리할 항목이 없어 유휴 상태이더라도 최대 2분 간격으로 호출하십시오 — 5분 이상 heartbeat가 끊기면 화면에 STALE로 표시됩니다. - 실행 종료:
SchedulerAgentRunEnd(runIdKey,agentKey,status,processedCount,skippedCount,failedCount,exitCode,summary).status값은SUCCEEDED/TIMED_OUT/FAILED,exitCode는 숫자 문자열('0'또는'1').
| SP 이름 | 인증 | 용도 |
|---|---|---|
SchedulerAgentUpsert | SK | identity 등록/갱신 |
SchedulerAgentRunStart | SK | 실행 시작 기록 |
SchedulerAgentHeartbeatPut | SK | 실행 중 진행률/heartbeat 갱신 |
SchedulerAgentRunEnd | SK | 실행 종료 기록 |
SchedulerAgentList | AK(로그인 세션) | 이 화면이 목록을 조회할 때 사용 — 등록에는 필요 없음 |
위치 인자 순서(방식 1 기준):
- Upsert 인자 순서:
agentKey,displayName,hostIdentifier,windowsTaskName,projectName,scheduleDesc,isActive - RunStart 인자 순서:
runIdKey,agentKey,executionMode,totalIssueCount - HeartbeatPut 인자 순서:
runIdKey,agentKey,processedCount,skippedCount,failedCount,currentPhase,currentIssueNum
호출 규약은 두 가지 방식이 모두 유효합니다. 방식 1(러너 내부): giipApiSk2에 token=SK, text="SP이름 '값1' '값2' ..."(SP 파라미터 선언 순서의 위치 인자, 값은 작은따옴표 리터럴), jsondata는 빈 값. 방식 2(직접 구현 시, 방법 A 와 같은 형식): giipApiSk4에 token=SK, text에 필드명을 공백 나열, jsondata에 {"필드명":"값",...} JSON.
참고: 이 저장소가 관리하는 giipAgentWin/giipAgentLinux(서버 인프라 에이전트)는 이
tSchedulerAgent체계와는 별개의 등록 메커니즘(x-api-key 기반agent-register/agent-heartbeatAPI)을 사용하며, 이 화면과는 무관합니다. 이 화면(admin/catquest/schedulers)에 등록하려면 위 SP 계열을 호출하는 구현이 필요하며, gissue 매시간 스케줄러의 표준 구현은 giip-fde-agent 의run-gissue-claude.ps1입니다.
성공 확인: 다음 heartbeat/새로고침 후 에이전트 목록 표에 나타납니다.
💡 유의 사항
- 이 화면은 관리자 전용입니다.
AdminGuard(uLevel ≥ 70) 접근 권한이 필요합니다. - 에이전트 목록, 실행이력, 로그 카탈로그는 모두 세션의 현재 CSN을 기준으로 조회됩니다 — 다른 프로젝트의 에이전트를 확인하려면 상단 내비게이션에서 프로젝트를 전환하세요.
- 데이터 조회에는 인증된 세션(AK 토큰)이 필요합니다. 요청이 인증 오류로 실패하기 시작하면 다시 로그인 후 Refresh를 누르세요.
- 이 페이지는
/admin/catquest와는 별개의 도구입니다 — CatQuest 화면의 내비게이션을 통해 진입했다고 해서 CatQuest의 고양이 시각화 데이터나 대상 사용자를 공유하는 것은 아닙니다.
문제 해결
| 증상 | 원인 | 해결 |
|---|---|---|
| 대화형 세션에 등록을 지시했는데 "Open Interactive Sessions"에 전혀 안 나타남 | 전역 훅의 헤드리스 판정 로직이 $env:CLAUDE_CODE_BRIDGE_SESSION_ID 유무만으로 판단해, 브릿지 미연결 대화형 세션까지 헤드리스로 오인해 등록 자체를 스킵했을 가능성이 가장 큼(giip #1837) | 위 "전역 훅으로 등록한 경우 헤드리스 오판정 주의" 박스의 프로세스 커맨드라인 검사 방식으로 판정 로직을 교체하십시오. |
| "Authentication required" 오류 | AK/세션 토큰을 찾을 수 없음 | 다시 로그인한 뒤 페이지를 새로고침하세요. |
| 에이전트 목록이 비어 있음 | 이 CSN에 등록된 스케줄러 에이전트가 없거나, 프로젝트가 잘못 선택됨 | 상단 내비게이션에서 올바른 프로젝트(CSN)가 선택돼 있는지 확인하세요. |
| 작업이 유휴 상태인 걸 아는데도 행이 빨간색 STALE로 표시됨 | 에이전트가 ENDED로 전이되지 않은 채 실행 도중 heartbeat 전송이 멈춤(isStale = true) — 프로세스가 크래시했거나 강제 종료됐을 가능성 | 에이전트 호스트를 직접 확인하세요. 재시작 후 다시 보고를 시작하면 배지가 갱신됩니다. |
| Live Log Viewer에 "No streams yet."이 표시됨 | 에이전트가 아직 로그 스트림을 등록하지 않았거나, 카탈로그가 마지막으로 갱신된 이후 출력이 없음 | 에이전트의 다음 실행을 기다리거나, 에이전트 프로세스가 실제로 로그를 쓰고 있는지 확인하세요. |
| Open Interactive Sessions에 "Open session" 링크가 없음 | 해당 에이전트의 등록된 capabilities에 sessionUrl이 포함돼 있지 않음 | 열 수 있는 항목이 없습니다 — 해당 대화형 세션이 접근 가능한 URL을 등록하지 않았습니다. |
버전: 1.1
최종 업데이트: 2026-09-01
소스 파일: giipv3/public/help/catquest-schedulers.ko.md