giip
SES 안건 등록
16분 읽기

스케줄러 에이전트 관리 가이드

프로젝트(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= 값을 읽고 나에게 묻지 마.
각 단계 확인 결과를 보고해.

설치 절차

  1. 사전점검 — 실행 → git --version, node --version, claude --version, Windows 는 powershell -Command $PSVersionTable.PSVersion, Linux 는 pwsh --version. 모두 PATH 에 있어야 다음 단계로.
  2. giip-fde-agent 확보 — 실행 → 없으면 git clone https://github.com/LowyShin/giip-fde-agent.git, 있으면 git pull --ff-only. 로컬 변경이 있으면 중단하고 보고.
  3. 정본 스펙 읽기 — docs/60-operations/hourly-issue-scheduler.md §4~§5 를 읽고 CSN→workdir 매핑, heartbeat 블록, SK 위치를 확인.
  4. csn-projects.json 작성 — scripts/gissue/csn-projects.json.example 을 csn-projects.json 으로 복사. 대상 CSN 의 workdir/enabled: true 채우고, 최상위 heartbeat 블록에 lssn(서버번호)/hostname(이 PC 식별자)/skFile(sk= 형식 파일 경로) 필수로 지정. 없으면 이 화면에 행이 나타나지 않음.
  5. 시크릿 준비 — 상위 폴더 giipAgent.cnf(또는 giipAgent.cfg)에서 sk= 값을 읽음. 사용자에게 되묻지 않고, 출력에도 기록하지 않음. slack-bot/.secrets/giip-accounts.json(.sample.json 복사)도 준비.
  6. 드라이런 — scripts/gissue/manage-gissue-scheduler.ps1 -Action DryRun -OnlyCsn <CSN> 실행 → [PREFLIGHT-FAIL] 로그 없이 끝나면 성공.
  7. 등록
    • 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 링크 등)을 점검합니다.
  8. 검증 — 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(정체) 탐지 결과를 반영합니다.

색상표시 라벨조건의미
🟢 초록RUNNINGcurrentStatus = RUNNING이고 stale 아님정상적으로 실행 중
🔴 빨강STALEcurrentStatus = RUNNING 이면서 isStale = true실행 중으로 표시돼 있지만 heartbeat가 너무 오래 끊김 — 멈춰버린/좀비 실행으로 보임
🟠 주황STALEcurrentStatus = STALE(명시적 값)백엔드가 실행 자체를 stale로 판정함
⚪ 회색ENDEDcurrentStatus = 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 훅에서 아래 절차를 실행하도록 구성합니다.

  1. 사전조건: 위 공통 사전조건(대상 CSN용 SK) + 세션을 실행하는 도구(Claude Code 등)에 SessionStart/SessionEnd 훅을 등록할 수 있는 설정 권한.
  2. 세션 시작(SessionStart) 시 — SchedulerAgentUpsert 호출로 identity 등록/갱신 후 SchedulerAgentRunStart 호출로 실행 시작을 기록합니다.
  3. 세션 종료(SessionEnd) 시 — SchedulerAgentRunEnd 호출로 실행 종료를 기록하고, SchedulerAgentUpsert를 isActive=0으로 다시 호출해 오프라인으로 전환합니다.

agentKey 생성 규칙(그대로 적용, 판단 불필요): csn + agentKey 조합이 DB에서 UNIQUE 제약이므로, 같은 세션을 재시작해도 항상 같은 값이 나와야 합니다(그래야 재시작 시 새 행이 아니라 기존 행이 갱신됩니다). 아래 순서를 그대로 적용해 계산하십시오.

  1. 원본 ID: 훅 stdin JSON의 session_id 필드 값이 있으면 그 값을 쓴다. 없으면 fallback_<COMPUTERNAME 환경변수 값>_<현재 프로세스 PID> 를 원본 ID로 쓴다.
  2. 정규화: 원본 ID에서 영숫자(a-z, A-Z, 0-9)가 아닌 문자를 모두 제거한다(정규식 치환: [^a-zA-Z0-9] → '').
  3. 자르기(1차): 정규화된 값이 24자를 넘으면 앞 24자만 남긴다.
  4. 조합: agentKey = "claude_interactive_" + <COMPUTERNAME 환경변수 값> + "_" + <3단계 결과값>
  5. 자르기(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/--print 플래그가 있는지로 판정하십시오. Windows PowerShell 예시(그대로 적용):

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/--print 확인)를 해당 OS의 프로세스 조회 방법으로 구현하십시오. 이 판정은 "등록 자체를 할지 말지"(Start/End 스킵 여부)에만 쓰고, 아래 sessionUrl 관련 판정과는 완전히 별개입니다 — 혼동하지 마십시오.

이 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 등록/갱신SchedulerAgentUpsertagentKey(호스트+세션별 고유 식별자), displayName, hostIdentifier(호스트명), windowsTaskName, projectName, scheduleDesc, isActive=1, lssn, osType, agentType(예: claude_interactive_session), version, capabilities(JSON 문자열, 예: {"sessionUrl":"<세션을 열 수 있는 URL>"})
시작 — 실행(run) 시작SchedulerAgentRunStartrunIdKey(이 실행의 고유 키), agentKey, executionMode=interactive, totalIssueCount
종료 — 실행(run) 종료SchedulerAgentRunEndrunIdKey, agentKey, status=ENDED, processedCount, skippedCount, failedCount
종료 — identity 오프라인 전환SchedulerAgentUpsertagentKey, 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 규격은 러너가 내부적으로 쓰는 호출이며, 직접 구현해야 할 때만 참고하십시오.

  1. 사전조건: 위 공통 사전조건(대상 CSN용 SK).
  2. 최초 등록/주기적 identity 갱신: SchedulerAgentUpsert(방법 A와 동일 필드 — agentKey/displayName/hostIdentifier/osType/agentType/version/capabilities 등)를 프로세스 시작 시 1회 호출하고, 그 뒤로는 최대 5분 간격(권장: 2분 간격)으로 반복 호출합니다.
  3. 실행(run) 시작: SchedulerAgentRunStart(runIdKey, agentKey, executionMode=scheduled, totalIssueCount).
  4. 실행 도중 진행 상황 갱신(heartbeat): SchedulerAgentHeartbeatPut — runIdKey, agentKey, processedCount, skippedCount, failedCount, currentPhase, currentIssueNum, totalIssueCount. 처리 단위(예: 이슈 1건 처리 완료)마다 호출하고, 처리할 항목이 없어 유휴 상태이더라도 최대 2분 간격으로 호출하십시오 — 5분 이상 heartbeat가 끊기면 화면에 STALE로 표시됩니다.
  5. 실행 종료: SchedulerAgentRunEnd(runIdKey, agentKey, status, processedCount, skippedCount, failedCount, exitCode, summary). status 값은 SUCCEEDED / TIMED_OUT / FAILED, exitCode는 숫자 문자열('0' 또는 '1').
SP 이름인증용도
SchedulerAgentUpsertSKidentity 등록/갱신
SchedulerAgentRunStartSK실행 시작 기록
SchedulerAgentHeartbeatPutSK실행 중 진행률/heartbeat 갱신
SchedulerAgentRunEndSK실행 종료 기록
SchedulerAgentListAK(로그인 세션)이 화면이 목록을 조회할 때 사용 — 등록에는 필요 없음

위치 인자 순서(방식 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-heartbeat API)을 사용하며, 이 화면과는 무관합니다. 이 화면(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